实例11-1
为在Ubuntu12.04上的运行结果与书中描述的不一致呢?
从pid来看这两个线程属于同一个进程,且线程ID也是指针形式的,Google后得知,书上讲的是以前的LinuxThreads实现,现在linux使用NPTL线程。
参考:Linux 线程模型的比较:LinuxThreads 和 NPTL
习题11.4
在回答该问题之前,我觉得得先弄清楚“互斥量”与“条件变量”之间的关系。因为书上说这两者一般是配套使用的。
考虑如下情况:
子线程B和子线程C都通过同一个条件变量cond等待主线程A发送信号,若判断测试条件为假,则这两个线程都会继续阻塞休眠。但是,判断条件不正确和休眠这之间有个时间窗口,假如当这两个子线程刚判断条件为假后但并未进入阻塞休眠是时,CPU切换到了主线A,线程A使条件变为真了。那么当CPU切换会B、C线程时,线程还是会进入休眠(且错过了判断条件为真的时机,有可能再也无法等到条件为真)。也就是说在线程检查条件变量和进入休眠等待条件改变这两个操作之间存在一个时间窗口。这里存在着竞争。
我们知道互斥量是可以用来解决上面的竞争问题的,所以“条件变量本身是由”互斥量“来保护的。
既然判断和睡眠是由互斥量来保护从而成为一个原子操作,那么其他改变条件的线程就应该以一致的方式修改条件,也就是说其他线程在改变条件状态前也必须首先锁住互斥量。(如果修改操作不是用互斥量来保护的话,那么判断和休眠使用互斥量来保护也就没有意义。因为其他线程还是可以在两个操作的空隙中改变条件。但是如果修改操作也使用互斥量。因为判断和休眠之前先加锁了。那么修改操作就只能等到判断失败和休眠两个操作完成才能进行而不会在这之间修改)
大致的流程如下:
线程B:lock->判断条件(假)->wait(休眠&unlock)->lock->执行相应操作
线程A:lock->使条件为真->unlock->发送信号
看个具体的例子
#include <stdio.h> #include <pthread.h> unsigned int test_signal = 0; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void *thread1(void *arg) { pthread_mutex_lock(&mutex); while (1000 != test_signal) { pthread_cond_wait(&cond,&mutex); } pthread_mutex_unlock(&mutex); printf("This is thread 1\n"); pthread_exit((void *)0); } void *thread2(void *arg) { pthread_mutex_lock(&mutex); while (1000 != test_signal) { pthread_cond_wait(&cond,&mutex); } pthread_mutex_unlock(&mutex); printf("This is thread 2\n"); pthread_exit((void *)0); } int main(void) { pthread_t tid1, tid2; pthread_create(&tid1, NULL, thread1, (void *)0); pthread_create(&tid2, NULL, thread2, (void *)0); while (1) { pthread_mutex_lock(&mutex); test_signal++; pthread_mutex_unlock(&mutex); if (1000 == test_signal) { pthread_cond_broadcast(&cond); break; } } pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_cond_destroy(&cond); pthread_mutex_destroy(&mutex); exit(0); }
OK,现在回到问题本身,这两种步骤其实都是可以的 但是都存在一些不足。
a步骤:
(1)对互斥量加锁(pthread_mutex_lock)
(2)改变互斥量保护的条件。(对应上面的例子就是在主线程中的test_signal++
操作)
(3)向等待条件的线程发送信号(pthread_cond_broadcast)
(4)对互斥量解锁(pthread_mutex_unlock)
也就是主线程在发送条件成立信号在解锁前。那么也就是主线程发送信号后还是持有锁的,当子线程收到信号后会结束休眠但是前面说过pthread_cond_wait返回时会再次获得锁,但是主线程还并未释放锁,所以会造成子线程收到信号开始运行但立即被阻塞。
b步骤:
(1)对互斥量加锁(pthread_mutex_lock)
(2)改变互斥量保护的条件。(对应上面的例子就是在主线程中的 i++ 操作)
(3)对互斥量解锁(pthread_mutex_unlock)
(4)向等待条件的线程发送信号(pthread_cond_broadcast)
主线程在释放锁后才发送信号。上面的例子就是这么做的。但是这也存在一个问题但释放锁后,其他线程很可能会在发送信号之前获得锁并修改 test_signal 导致条件再次不成立,但是主线程却并不知情,导致仍会发送信号给子线程。子线程认为条件满足从休眠中醒来开始运行,但此时条件是不满足的。
所以在上面的例子中
while (1000 != test_signal) { pthread_cond_wait(&cond,&mutex); }
让子线程醒来后再次判断条件是否成立还是很有必要的,这样就可以避免了上面的问题。