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

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

/*冲突的例子*/
#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;
}

我们会观察到:

【线程同步与互斥】互斥锁(mutex)_第1张图片

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

【线程同步与互斥】互斥锁(mutex)_第2张图片

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

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


你可能感兴趣的:(Linux)