互斥锁、条件变量

场景描述:
多线程环境下,少部分线程作为生产者产生event(event事件产生速度较慢),其他大部分线程作为消费者(消费需求旺盛,频繁轮询event队列),这时候就会产生一个问题,消费者线程不断的轮询lock去查看event队列的情况,导致锁频繁的申请释放,大量消耗系统性能,这种场景下(伪代码如下:),单一的锁已经无法解决问题,这就需要引入另一种技术—条件变量

生产者线程:
while(1){
	sleep(10);
	lock
	queued.push(node);
	unlock
}

消费者线程:
while(1){
	lock
	if(queued.empty())
		printf("producter is not ready\n");
	else
	{
		printf("producter is ready\n");
		node = queued.pop();
	}
	unlock
}

条件变量:
关于条件变量,就是为了解决上述问题而产生的,首先,条件变量需要与一个互斥锁配合使用,在调用pthread_cond_wait前要先获取锁。pthread_cond_wait函数执行时先自动释放指定的锁,然后等待条件变量的变化。在函数调用返回之前,自动将指定的互斥量重新锁住。

pthread_cond_signal通过条件变量cond发送消息,若多个消息在等待,它只唤醒一个。pthread_cond_broadcast可以唤醒所有。调用pthread_cond_signal后要立刻释放互斥锁,因为pthread_cond_wait的最后一步是要将指定的互斥量重新锁住,如果pthread_cond_signal之后没有释放互斥锁,pthread_cond_wait仍然要阻塞。

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁 (PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁 (pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开 pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

消费者线程:
pthread_mutex_lock(&m_mutex);   
pthread_cond_wait(&m_cond,&m_mutex);   
pthread_mutex_unlock(&m_mutex);

生产者线程:
模型一:
pthread_mutex_lock(&m_mutex);   
pthread_cond_signal(&m_cond); 
do something  
pthread_mutex_unlock(&m_mutex);   
模型二:
pthread_mutex_lock(&m_mutex); 
do something  
pthread_mutex_unlock(&m_mutex);  
pthread_cond_signal(&m_cond);  

利用条件变量的特性,每次消费者都会先获取互斥锁,然后调用pthread_cond_wait解锁然后进入等待信号量的状态,这样就可以让出cpu资源,直到生产者生产完毕,调用pthread_cond_signal才会换返回;这就大大节省了消费者线程的频繁申请释放锁的资源,伪代码如下:

生产者线程:
while(1){
	sleep(10);
	lock
	queued.push(node);
	unlock
	pthread_cond_signal
}

消费者线程:
while(1){
	lock
	while(queued.empty())
	{
		printf("producter is not ready\n");
		pthread_cond_wait(&m_cond,&m_mutex);
		break;
	}
	printf("producter is ready\n");
	node = queued.pop();
	unlock
}

消费者线程的两种模型优缺点分析:
模型一:pthread_cond_signal在锁之间
pthread_mutex_lock(&m_mutex);
pthread_cond_signal(&m_cond);
do something
pthread_mutex_unlock(&m_mutex);
缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于此时cond_signal的时候生产者还处于lock状态,消费者wait唤醒尝试加锁lock,发现锁被占用,然后又重新休眠等待锁)然后又回到内核空间(因为cond_wait返回后会有原子加锁的 行为),所以一来一回的内核切换会有性能的问题。但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗,所以在Linux中推荐使用这种模式。
注释:mutex如果拿取不到锁,则会进⼊休眠,放在锁“等待”的队列上,这样操作会涉及到进程上下⽂的切换,效率不⾼,那么⾃旋锁则不是⼀种休眠等待的⽅式,⽽是⼀种忙等待的过程,什么意思呢?就是⾃旋锁的pthread_spin_lock⾥有⼀个死循环,这个死循环⼀直检查锁的状态,如果是lock状态,则继续执⾏死循环,否则上锁,结束死循环。

模型二:pthread_cond_signal在锁之后
pthread_mutex_lock(&m_mutex);
do something
pthread_mutex_unlock(&m_mutex);
pthread_cond_signal(&m_cond);
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值