在多线程访问共享数据的时候可能会发生冲突,例如:
/*冲突的例子*/
#include
#include
#include
#include
int common_data=0;//公共数据
void increase_data(void* vptr){
//让common_data自增10次
//两个线程都来执行它,如果不冲突的话common_data最后会等于20
int 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
#include
#include
#include
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
#include
#include
#include
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。