【线程同步与互斥】互斥锁(mutex)

来源:互联网 发布:mac如何打拼音声调 编辑:程序博客网 时间:2024/06/07 10:36

在多线程访问共享数据的时候可能会发生冲突,例如:

/*冲突的例子*/#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>int common_data=0;//公共数据 void increase_data(void* vptr){//让common_data自增10次//两个线程都来执行它,如果不冲突的话common_data最后会等于20int val; for(int i=0;i<10;i++){ val=common_data; sleep(vptr); printf("线程%d:common_data=%d\n",(unsigned int)pthread_self(),common_data); common_data=val+1;}return NULL;}int main(void){pthread_t tid1,tid2;int err;err=pthread_create(&tid1,NULL,(void*)increase_data,(void *)1);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}err=pthread_create(&tid2,NULL,(void*)increase_data,(void *)2);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;}

我们会观察到:


数据完全乱套了,没有得到预期的20。

和这个类似的还有一个有趣的例子:

/*没有加锁但是没有冲突的情况*/#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>int common_data=0;//公共数据 void pthread1(void* arg);//第一个线程 void pthread1(void* arg);//第二个线程 void pthread1(void* arg){//线程1要修改公共的数据common_data//按照预期,执行后common_data=10 for(int i=0;i<10;i++){common_data++;//sleep(2);printf("pthread1:第%d次循环,common_data=%d\n",i,common_data);}}void pthread2(void* arg){//线程2要修改公共的数据common_data//按照预期,执行后common_data=10//如果两个线程不发生冲突,common_data最后等于20 for(int i=0;i<10;i++){common_data++;//sleep(1);printf("pthread2:第%d次循环,common_data=%d\n",i,common_data);}}int main(void){pthread_t tid1,tid2;int err;err=pthread_create(&tid1,NULL,(void*)pthread1,NULL);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}err=pthread_create(&tid2,NULL,(void*)pthread2,NULL);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}pthread_join(tid1,NULL);pthread_join(tid2,NULL);return 0;}

这个例子运行以后没有冲突,这是为什么呢?!在和同学讨论并经过自己动手实践以后证实,其实只要把循环次数增加,比如增加到10000000,还是会观察到冲突的,如下图:

也就是说,当数据量小的时候,执行速度较快,给人的错觉是“顺序”执行的,但是数据量一旦大起来,总有某次读写操作相互过不去......。然后common_data的值就乱了。这也体现了所谓“量变引起质变”吧。

为了解决多个线程访问同一个数据(可能)会出现的冲突问题,一个行之有效的方法就是加互斥锁(MUTual EXclusive lock,mutex)。相关的函数:

(1)pthread_mutex_init

用来申请一个互斥锁。

例如:

pthread_mutex_t mutex;pthread_mutex_init(&mutex,NULL);
第一个参数传入pthread_mutex_t类型的指针,第二个指定了需要的属性,一般用NULL。

(2)pthread_mutex_lock

给代码段加锁。加锁意味着当前只有这个锁定这个代码段的线程能够执行它,如果其他线程要执行这些代码,这些线程就会被挂起。

(3)pthread_mutex_trylock

功能同(2),但是这个函数是“尝试加锁”,也就是说如果加锁失败了,函数会立刻返回,线程继续执行。

(4)pthread_mutex_unlock

解锁(但锁还在,区别下面的pthread_mutex_destroy),和(3)、(4)配合使用。

(5)pthread_mutex_destroy

彻底销毁锁,功能同解锁,如果我们不再需要某个锁了就可以销毁它。

有了互斥锁的机制,就可以解决刚才的冲突问题了:

/*没有加锁但是没有冲突的情况*/#include<stdio.h>#include<stdlib.h>#include<pthread.h>#include<unistd.h>int common_data=0;//公共数据 pthread_mutex_t mutex; void pthread1(void* arg);//第一个线程 void pthread1(void* arg);//第二个线程 void pthread1(void* arg){//线程1要修改公共的数据common_data//按照预期,执行后common_data=10 for(int i=0;i<10;i++){pthread_mutex_lock(&mutex);//加锁 common_data++;printf("pthread1:第%d次循环,common_data=%d\n",i,common_data);pthread_mutex_unlock(&mutex);//解锁 }}void pthread2(void* arg){//线程2要修改公共的数据common_data//按照预期,执行后common_data=10//如果两个线程不发生冲突,common_data最后等于20 for(int i=0;i<10;i++){pthread_mutex_lock(&mutex);//加锁 common_data++;printf("pthread2:第%d次循环,common_data=%d\n",i,common_data);pthread_mutex_unlock(&mutex);//解锁 }}int main(void){pthread_t tid1,tid2;int err;pthread_mutex_init(&mutex,NULL);//初始化锁 err=pthread_create(&tid1,NULL,(void*)pthread1,NULL);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}err=pthread_create(&tid2,NULL,(void*)pthread2,NULL);if(err!=0){//创建失败 printf("failed to create thread\n");exit(1);}pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_mutex_destroy(&mutex);//将锁彻底销毁 return 0;}
运行效果:


这一次尽管执行次数很大,但是没有冲突了,得到了预期的2000000。


原创粉丝点击