Linux利用mutex互斥锁实现线程的互斥

来源:互联网 发布:剑灵秦捏脸数据 编辑:程序博客网 时间:2024/06/09 20:17

在多个线程对共享数据进行处理时,可能会发生冲突从而导致数据结果与预期结果不一样,导致程序运行错误。

例:在一个主线程中有两个线程,这两个线程都对全局变量num进行+1操作。

注意:上述说的例子可能不会发生,只有当线程1还未将num的新值写入内存时突然被切换到线程2才可能发生。
           线程间的切换会在系统由内核态切换到用户态时可能发生。系统在运行系统调用时会进入内核态,运行用户代码时,会进入用户态。

实例代码:对全局num=0,线程1加5000次,线程2加5000次

#include<stdio.h>#include<pthread.h>int num = 0;//这个函数对num+1,打印,进行5000次void *Add(void *arg){    int i = 0;    while(i < 5000)    {    int tmp = num + 1;    //printf其实是对系统调用接口的封装,在这里系统会切换为内核态,在切换时就有可能发生线程的切换    printf("%d\n", tmp);    num = tmp;    i++;    }}int main(){    //定义两个线程    pthread_t thread1;    pthread_t thread2;    //创建两个线程 ,两个线程都去执行Add函数    pthread_create(&thread1, NULL, Add, NULL);    pthread_create(&thread2, NULL, Add, NULL);    pthread_join(thread1,NULL);    pthread_join(thread2,NULL);    return 0;}

运行结果:
     运行结果一直为6000多或者7000,从来没有一次运行正确,可以自行验证。

总结:
在多线程程序中,对于共享数据的处理要格外的小心,一定要保证一个线程对共享数据进行处理时其他线程不能对数据进行处理,也就是说对于能处理同一个共享数据的线程来说,他们对共享数据的访问和处理必须是互斥的。这样就能保证运行结果的正确性。为了能让线程之间发生互斥,我们可以使用互斥所锁来实现。获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

互斥锁的使用:
互斥锁用int pthread_mutex_t类型的变量表示。
初始化:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数描述:
mutex:代表要初始化的互斥锁
attr:为设定锁的属性,创建锁时默认为NULL
返回值:
成功为0,失败为错误码
如果锁是静态分配的(全局变量或者static变量),可以使用宏定义PTHREAD_MUTEX_INITALIZE来初始化。

销毁锁:
int pthrea_mutex_destroy(pthread_mutex_t *mutex);
成功返回0,失败返回错误码。

加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
//成功返回0,运行出错则返回错误码,申请不到锁会被挂起到等待队列
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//尝试加锁,与上面的区别在于,这个申请锁失败,会直接退出
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0,运行出错返回错误码

解决上述问题:
代码:
#include<stdio.h>#include<pthread.h>//创建一个互斥锁pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;int num = 0;void *Add(void *arg){    int i = 0;    while(i < 5000)    {    //在进入临界区前先申请锁资源    pthread_mutex_lock(&lock);    int tmp = num + 1;    printf("%d\n", tmp);    num =tmp;    i++;    //在出临界区之后释放锁资源    pthread_mutex_unlock(&lock);    }}int main(){    //两个线程    pthread_t thread1;    pthread_t thread2;    //创建两个线程    pthread_create(&thread1, NULL, Add, NULL);    pthread_create(&thread2, NULL, Add, NULL);    //等待线程    pthread_join(thread1,NULL);    pthread_join(thread2,NULL);    //销毁锁    pthread_mutex_destroy(&lock);    return 0;}

运行结果:
10000

互斥锁的实现原理:
互斥锁可以让线程之间实现互斥,那么它是怎样实现的呢?
假设mutex变量的值为1表示互斥锁空,这时某个进程调用lock就可以获得锁,而mutex为0表示互斥锁已经被某个线程获得,其他线程去进行lock操作时只能挂起等待。

注意:有可能有两个线程同时去进行lock操作,这时mutex是1,两个线程都判断mutex>0成立,结果两个线程都以为自己获得了锁。所以对mutex变量的读取、判断和修改应该是原子性的操作。

下面是互斥锁的伪代码实现:
movb 和 xchgb这两条指令都是原子性的,所以mutex的读取、判断和修改也都保证了其原子性。

原创粉丝点击