linux线程同步与互斥——互斥锁、条件变量、信号量

互斥锁、条件变量、信号量是线程间同步与互斥的三种基本方式。
互斥锁
互斥锁用于互斥操作,用于对临界资源进行互斥操作,运行机制如下:若线程A和线程B共享变量count,则若A和B要对count操作时必须都要先获取互斥锁,只要有一个线程获取互斥锁,则另一个线程在请求互斥锁时会被阻塞,直到互斥锁被释放才能获得它进而对count进行操作。
例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex;
int lock_var;
void pthread1(void*arg);
void pthread2(void*arg);

void pthread1(void*arg)
{
	int i;
	for(i=0;i<2;i++)
	{
		pthread_mutex_lock(&mutex);
		lock_var++;
		printf("pthread1:第%d次循环,第1次打印 lock_var=%d\n",i,lock_var);
		printf("pthread2:第%d次循环,第2次打印 lock_var=%d\n",i,lock_var);
		pthread_mutex_unlock(&mutex);
	}
}

void pthread2(void*arg)
{
	int i;
	for(i=0;i<5;i++)
	{
		pthread_mutex_lock(&mutex);
		lock_var++;
		printf("pthread2:第%d次循环 lock_var=%d\n",i,lock_var);
		pthread_mutex_unlock(&mutex);
	}
}
int main(int argc,char *argv[])
{
	pthread_t id1,id2;
	int ret;
	pthread_mutex_init(&mutex,NULL);
	ret = pthread_create(&id1,NULL,(void*)pthread1,NULL);
	if(ret != 0)
	{
		printf("pthread1 not created\n");
	}
	ret = pthread_create(&id2,NULL,(void*)pthread2,NULL);
	if(ret != 0)
	{
		printf("pthread2 not create\n"); 
	} 
	pthread_join(id1,NULL);
	pthread_join(id2,NULL);
	exit(0);
}
 

线程1和线程2对共享资源lock_var进行互斥操作,当其中任何一个线程使用pthread_mutex_lock函数获得互斥锁后,另一个线程试图用pthread_mutex_lock获取互斥锁时会被阻塞,直到有线程使用pthread_mutex_unlock释放该锁。
线程1和线程2的执行是无序的,退出线程可以使用pthread_exit或者return ,主线程中使用pthread_join来阻塞主线程,直到对应线程ID的线程返回,这和进程中的wait作用相同。
若使用pthread_mutex_trylock则进行非阻塞的获取操作,即若互斥锁被其他线程占用,则本线程不阻塞,而是直接返回。

条件变量
互斥锁只能进行获取锁---->处理----->释放锁的操作,若在处理阶段有一定的条件,以上面的程序举例,我们引入一个线程3,这个线程的功能是对count进行加1的操作,我们再对线程1和线程2 的操作限制一下——>“判断count是否>=1,若大于等于1,才进行count++操作”。在这种情况下,只要线程3不运行,线程1和2即使获得了运行机会,也只能进行无用的操作——“获取锁----判断count,结果count不大于等于1-----释放锁…获取锁----判断count,结果count不大于等于1-----释放锁…获取锁----判断count,结果count不大于等于1-----释放锁…”这显然是无用的执行!
由此引入条件变量,当线程1或线程2获取锁后发现count不是大于等于1的,此时线程1或2会被阻塞起来,直到线程3运行count++后进行一个唤醒操作,唤醒操作会使得线程1和2里的一个被唤醒,被唤醒后由于count大于等于1的条件满足了,线程1或2进行相应的处理。
因此条件变量主要是用于线程同步的。
条件变量里还有一些其他有意思的东西,这里先贴段代码

pthread_mutex_t count_lock;//互斥锁
pthread_cond_t count_nonzero;//条件变量
unsigned count;
decrement_count()
{
	pthread_mutex_lock(&count_lock);
	while(count==0)
	{
		pthread_cond_wait(&count_nonzero,&count_lock);
	}
	count = count -1;
	pthread_mutex_unlock(&count_lock);
}
increment_count()
{
	pthread_mutex_lock(&count_lock);
	if(count ==0)
	{
		pthread_cond_signal(&count_nonzero);
	}
	count= count+1;
	pthread_mutex_unlock(&count_lock);
}

decrement_count来实现count减1,increment_count实现count加1。初看这段代码会有两点疑惑:
1、若decrement先进行,则它获取互斥锁后,由于count为0,它会在pthread_cond_wait处被阻塞,此时代码中貌似并没有释放互斥锁,那么当increment尝试获取互斥锁时不是会发生死锁吗?
2、为什么使用信号量的操作中要加上互斥锁?而且在使用阻塞函pthread_cond_lock时要传入互斥锁?
如果能有第一点疑惑,那么你互斥锁是学懂了,实际上这两个问题是由关联的。
在实际运行时,若decrement先运行,当它获取锁后进行while条件判断(这就是条件变量中的运行条件,其意义类似于上面例子中进程1或2对count是否>=1进行判断),while条件成立,那么调用pthread_cond_wait将自身阻塞,在pthread_cond_wait内部,程序会先把自己加入等待唤醒的线程队列,然后释放自己占有互斥锁,所以虽然代码看上去没有释放互斥锁,但实际上是释放了的,这也是为什么pthread_cond_wait中要传入互斥锁的原因。当increment线程执行时,它先获取互斥锁,然后if判断条件成立,则进行pthread_cond_signal操作来唤醒队列里的一个线程,假设此时唤醒了decrement线程,则decrement会尝试获取互斥锁,获取不到则阻塞,因此只有increment中进行pthread_mutex_unlock操作后,decrement才会真正开始执行。
关于这点,如果还有疑惑,请参考为什么pthread_cond_wait需要传递mutex参数,写的很好,不懂的话多看几遍。
关于第二点为什么那么多的互斥锁操作,原因很简单,因为count是共享资源,你时时刻刻对它的操纵都必须用互斥锁进行包裹,具体不细说了,结合互斥锁体会一下就懂了。
这里还有个小问题,在decrement中的while循环判断能不能用if判断代替?答案是不能,因为可能发生“虚假唤醒”条件变量的虚假唤醒(spurious wakeups)问题,即假如现在有一个decrement2的线程和decrement一起被唤醒了,但decrement2先获得了互斥锁,decrement获取不到锁只能被阻塞,当decrement2进行count减1操作后它退出并释放互斥锁,这时decrement终于获得互斥锁了,然而此时由于decrement2已经将count减1了,所以即使decrement获取锁,它实际上也不可以进行count减1操作,因此使用while来循环判断避免这种情况,if是避免不了的。

信号量
sem_init(sem_t *sem,int pshared,unsigned int value)初始化一个信号量,初值为value。
sem_wait(sem_t *sem)阻塞线程,直到sem的值大于0,解除阻塞后sem的值会减1,表明公共资源数量减1。
sem_post(sem_t *sem),使被阻塞的一个线程不再阻塞,sem的值会加1
sem_destroy销毁信号量
信号量的典型使用就是生产者、消费者问题,具体使用的话,这里就伪代码简单示意一下

prodeuceur:
sem_wait(&empty)
pthread_mutex_lock(&lock)
生产操作
pthread_mutex_unlock(&lock);
sem_post(&full);

consumer:
sem_wait(&full)
pthread_mutex_lock(&lock);
消费操作
pthread_mutex_unlock(&lock);
sem_post(&empty);

full和empty是两个信号量,假设empty初始值为5,full初始值为0,则生产者每次生生产都消耗一个empty,并且增加一个full,而消费者的操作相反。lock是保证共享资源互斥访问的互斥锁。再具体一些的理解,结合伪代码走一遍即可,不赘述。
就先这样,有更深理解的话再修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值