欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-14 16:25:52
...

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

/*冲突的例子*/
#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最后会等于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;
}

我们会观察到:

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

数据完全乱套了,没有得到预期的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,还是会观察到冲突的,如下图:

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

也就是说,当数据量小的时候,执行速度较快,给人的错觉是“顺序”执行的,但是数据量一旦大起来,总有某次读写操作相互过不去......。然后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;
}
运行效果:

【线程同步与互斥】互斥锁(mutex)
这一次尽管执行次数很大,但是没有冲突了,得到了预期的2000000。