设为首页 加入收藏

TOP

并行编程之条件变量(posix condition variables)(一)
2015-07-24 06:51:49 来源: 作者: 【 】 浏览:104
Tags:并行 编程 条件 变量 posix condition variables

在整理Java LockSupport.park()的东东,看到了个"Spurious wakeup",重新梳理下。

首先来个《UNIX环境高级编程》里的例子:

#include 
  
   
struct msg {
	struct msg *m_next;
	/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;
void process_msg(void) {
	struct msg *mp;
	for (;;) {
		pthread_mutex_lock(&qlock);
		while (workq == NULL)
			pthread_cond_wait(&qready, &qlock);
		mp = workq;
		workq = mp->m_next;
		pthread_mutex_unlock(&qlock);
		/* now process the message mp */
	}
}
void enqueue_msg(struct msg *mp) {
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;
	workq = mp;
	pthread_mutex_unlock(&qlock);
	pthread_cond_signal(&qready);
}
  

一个简单的消息生产者和消费者的代码。它们之间用condition同步。

这个代码最容易让人搞混的是process_msg函数里的pthread_mutex_lock 和 pthread_mutex_unlock 是一对函数调用,前面加锁,后面解锁。的确,是加锁解锁,但是它们两不是一对的。它们的另一半在pthread_cond_wait函数里。

pthread_cond_wait函数可以认为它做了三件事:

把自身线程放到condition的等待队列里,把mutex解锁;等待被唤醒(当其它线程调用pthread_cond_signal或者pthread_cond_broadcast时);被唤醒之后,对metex加锁,再返回。

mutex和condition实际上是绑定在一起的,一个condition只能对应一个mutex。在Java的代码里,Condition对象只能通过lock.newCondition()的函数来获取。

Spurious wakeup

所谓的spurious wakeup,指的是一个线程调用pthread_cond_signal(),却有可能不止一个线程被唤醒。为什么会出现这种情况?wiki和其它的一些文档都只是说在多核的情况下,简化实现允许出现这种spurious wakeup。。

在man文档里给出了一个可能的实现,然后解析为什么会出现。

假定有三个线程,线程A正在执行pthread_cond_wait,线程B正在执行pthread_cond_signal,线程C正准备执行pthread_cond_wait函数。

              pthread_cond_wait(mutex, cond):
                  value = cond->value; /* 1 */
                  pthread_mutex_unlock(mutex); /* 2 */
                  pthread_mutex_lock(cond->mutex); /* 10 */
                  if (value == cond->value) { /* 11 */
                      me->next_cond = cond->waiter;
                      cond->waiter = me;
                      pthread_mutex_unlock(cond->mutex);
                      unable_to_run(me);
                  } else
                      pthread_mutex_unlock(cond->mutex); /* 12 */
                  pthread_mutex_lock(mutex); /* 13 */


              pthread_cond_signal(cond):
                  pthread_mutex_lock(cond->mutex); /* 3 */
                  cond->value++; /* 4 */
                  if (cond->waiter) { /* 5 */
                      sleeper = cond->waiter; /* 6 */
                      cond->waiter = sleeper->next_cond; /* 7 */
                      able_to_run(sleeper); /* 8 */
                  }
                  pthread_mutex_unlock(cond->mutex); /* 9 */

线程A执行了第1,2步,这时它释放了mutex,然后线程B拿到了这个mutext,并且pthread_cond_signal函数时执行并返回了。于是线程B就是一个所谓的“spurious wakeup”。

为什么pthread_cond_wait函数里一进入,就释放了mutex?没有找到什么解析。。

查看了glibc的源代码,大概可以看出上面的一些影子,但是太复杂了,也没有搞明白为什么。。

/build/buildd/eglibc-2.19/nptl/pthread_cond_wait.c

/build/buildd/eglibc-2.19/nptl/pthread_cond_signal.c

不过从上面的解析,可以发现《UNIX高级编程》里的说明是错误的(可能是因为太久了)。


The caller passes it locked to the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks the mutex.


上面的伪代码,一进入pthread_cond_wait函数就释放了mutex,明显和书里的不一样。

wait morphing优化

在《UNIX环境高级编程》的示例代码里,是先调用pthread_mutex_unlock,再调用pthread_cond_signal。

void enqueue_msg(struct msg *mp) {
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;
	workq = mp;
	pthread_mutex_unlock(&qlock);
	pthread_cond_signal(&qready);
}
有的地方给出的是先调用pthread_cond_signal,再调用pthread_mutex_unlock:

void enqueue_msg(struct msg *mp) {
	pthread_mutex_lock(&qlock);
	mp->m_next = workq;
	workq = mp;
	pthread_cond_signal(&qready);
	pthread_mutex_unlock(&qlock);
}
先unlock再signal,这有个好处,就是调用enqueue_msg的线程可以再次参与mutex的竞争中,这样意味着可以连续放入多个消息,这个可能会提高效率。类似Java里ReentrantLock的非公平模式。
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇hdu2089(数位dp) 下一篇LeetCode Search a 2D Matrix

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: