linux线程锁的迷思

http://www.ibm.com/developerworks/cn/linux/thread/posix_thread3/#1

当多线程想要等待某一条件成立时,用pthread_cond_wait来阻塞线程,

  • 首先明确wait的作用,pthread的wait与lock都可以阻塞线程等待“条件”成立,区别在于:
wait之后的发送来的条件成立(pthread_cond_broadcast())才有效,之前发生都被丢弃了,也就是说wait的首次调用肯定会阻塞线程,不会因条件成立过而向下运行;

而lock的条件在成立(信号量未被别人lock )后会一直维持   unlock的状态,那么线程首次调用lock就不一定会阻塞,而取决于“条件”状态。

  • 那么问题来了,为什么pthread_cond_wait调用之前必须保证本线程已经被lock了?为什么wait要被设计成这样?

据说是因为wait时如果有条件达成,会因为wait未能及时加入到队列中而错失这个条件,只是这样吗?

试一下就知道了,本线程未被lock时调用wait也是可以的,wait也能正常工作啊。

下面用测试代码来说明这个问题:

void * foo1(void * p){
        while(1){
                printf("foo1 wait begin\n");
                pthread_cond_wait(&c,&m);
                printf("foo1 wait end\n");
sleep(1);
        }
}
void * foo2(void * p){
        while(1){
                printf("foo2 wait begin\n");
                pthread_cond_wait(&c,&m);
                printf("foo2 wait end\n");
sleep(1);
        }
}
void * foo3(void * p){
        while(1){
                getchar();
                pthread_cond_broadcast(&c);
        }
}
int pthreadTest(){
        pthread_t  tid[10];
        pthread_create(tid+2,NULL,foo1,NULL);
        pthread_create(tid+1,NULL,foo2,NULL);
        pthread_create(tid+3,NULL,foo3,NULL);
        pthread_join(*(tid+1),NULL);
        pthread_join(*(tid+2),NULL);
}
结果

foo2 wait end
foo2 wait begin
foo1 wait end
foo1 wait begin

foo2 wait end
foo2 wait begin
foo1 wait end
foo1 wait begin

从结果上看 wait唤醒的时候会lock住mutex,当线程循环一遍后再次调用wait而释放mutex时 再唤醒其他线程。

那么,线程间使用不同的mutex是否就可以同时唤醒两个线程的wait了呢,也就是是否就能够单纯的用cond控制wait唤醒?每个线程一个mutex,这样就不会被其他线程锁住自己的信号量了吧,下面测试一下:

void * foo1(void * p){
        while(1){
                printf("foo1 wait begin\n");
                pthread_cond_wait(&c,&m11111);
                printf("foo1 wait end\n");
sleep(1);
        }
}
void * foo2(void * p){
        while(1){
                printf("foo2 wait begin\n");
                pthread_cond_wait(&c,&m22222);
                printf("foo2 wait end\n");
sleep(1);
        }
}

结果:

foo2 wait end
foo2 wait begin

foo2 wait end
foo2 wait begin

这?????,只唤醒了一个线程(具体唤醒哪个线程取决于哪个线程先执行),pthread_cond_broadcast声称激活所有等待线程,但看来并不简单,可能是wait的时候同一个cond是事实上他的激活方式是串行的(也就是每次的pthread_cond_signal后根据wait的情况决定下一次是否signal),另一种可能是后运行的线程的mutex出现了问题。

那么将pthread_cond_broadcast改成pthread_cond_signal后测试看看,结果:

foo1 wait end
foo1 wait begin

foo2 wait end
foo2 wait begin

foo1 wait end
foo1 wait begin

foo2 wait end
foo2 wait begin

这是否说明了与mutex无关而激活方式确实是串行的呢?再试试每次pthread_cond_broadcast后调用unlock(&m11111),结果:


foo1 wait end
foo2 wait end

foo1 wait begin
foo2 wait begin

foo1 wait end
foo2 wait end

foo1 wait begin
foo2 wait begin

恩,看来应该是后运行的线程在wait唤醒时无法lock自己的mutex,但是根据刚才signal的结果看,这又是谁lock了它的mutex呢?

经过思考,终于把自己搞晕了......一个问题如果不能理解,那就会产生宗教,比如:“别问为什么,书上就是这么说的!”

所以   真正的原因就是:一个特定条件只能有一个互斥对象,而且条件变量应该表示互斥数据“内部”的一种特殊的条件更改。一个互斥对象可以用许多条件变量(例如,cond_empty、cond_full、cond_cleanup),但每个条件变量只能有一个互斥对象。

同时,wait也只有一个正确用法:如果线程正在等待某个特定条件发生,它应该如何处理这种情况?它可以重复对互斥对象锁定和解锁,每次都会检查共享数据结构,以查找某个值。但这是在浪费时间和资源,而且这种繁忙查询的效率非常低。解决这个问题的最佳方法是使用 pthread_cond_wait() 调用来等待特殊条件发生。

所以我们记住这个结论:照着他这么用就得了......否则只会走火入魔


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。