Kernel Space

January 18, 2007

牛角尖之一 — Condition Variable & Mutex

Filed under: 编程珠玑 — agassi @ 8:19 pm

看到了sodme的“孔乙己”系列,觉得自己跟他很像,都非常喜欢钻牛角尖。他那个a[1]的数组用a[10000]来访问问题确实经典值得挖掘,他也确实在这个问题上“孔乙己”了N次了,有时间一定好好研究研究。

这次说说这个CV。以前看pthread只注意了create, mutex, join, detach, exit等函数,每次看到这个CV和一系列围绕CV的函数确实没怎么感冒,只是朦胧的觉得有这么个咚咚。最近终于知道了他的本来面目,顿时让我对线程模型有了新的认识,所以有了前一篇文章。

其实大可以把CV看成Semaphore,这样想自己就清楚多了(面试的时候不知道被问了多少次什么是semaphore)。不过他们在实现上肯定是有区别的,我们就暂时先不钻这个牛角尖了。为了简单,就从简单的单producer单consumer说起。

通常有一个global的结构包括了一个state,一个mutex和一个CV。在producer这边,常用的代码是:

                       int dosignal;
                       pthread_mutex_lock(&m);
                       dosignal = (state == unsatified);
                       state = satisfied;
                       pthread_mutex_unlock(&m);
                       if(dosignal)
                             pthread_cond_signal(&cv);

 consumer通常的代码是:

                       pthread_mutex_lock(&m);
                       while(state!=unsatisfied)
                            {
                                 pthread_mutex_unlock(&m);
                                wait_on_cv(&cv);
                                    pthread_mutex_lock(&m);
       }
                        state = unsatisfied;   //consumed
      pthread_mutex_lock(&m);

上面的wait_on_cv函数是我自己编造了,实际上Pthread中没有提供专门的在CV上wait的函数,而只提供了与mutex捆绑的pthread_cond_wait和pthread_cond_timedwait函数(《Win32 System Programming》提到说这是一个wise的选择)。事实上,“CV必须和一个mutex一起使用”的原则决定了这个。同时为了避免前一篇中提到的信号丢失的情况,上面黑体的两个操作被实现为原子操作。上面斜体的三步操作就是pthread_cond_wait。所以,consumer的代码是:

                       pthread_mutex_lock(&m);
                       while(state!=unsatisfied)
                                  pthread_cond_wait(&cv, &m);
                        state = unsatisfied;   //consumed
      pthread_mutex_lock(&m);

CV实现了一种semaphore的机制。但是想到mutex,好像也可以wake up一个被lock住的thread。当一个线程unlock的时候,相当于通知了一个另外的调用lock的线程。

仔细研究UNP Vol2,终于发现了一段比较mutex,CV和Semaphore的文字。 

  • 一个mutex只能在同一个线程中被unblock,而且这个线程必须是曾经lock过这个mutex。
  • 另外还有CV和Semaphore的区别:Semaphore执行post操作后,Semaphore的状态保持signaled,直到有wait操作发生。而CV在调用pthread_cond_signal时如果没有线程调用了pthread_cond_wait,这个信号将丢失。这也是pthread_cond_wait中要原子化两个重要操作的原因。

 抱着对以上文字怀疑的态度,于是有了下面这个测试程序:

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* producer(void *arg)
{
        sleep(2);   
        int ret = pthread_mutex_unlock(&mutex);
        printf(“producer return %d\n”,ret);
}
void* consumer(void *arg)
{
        printf(“consumer locking\n”);
        int ret = pthread_mutex_lock(&mutex);
        printf(“consumer return %d\n”,ret);
}
int main(int argc, char** argv)
{
        pthread_t tid1,tid2;
        pthread_mutex_lock(&mutex);
        pthread_create(&tid2,NULL,consumer,NULL);
        pthread_create(&tid1,NULL,producer,NULL);
        pthread_join(tid1,NULL);
        pthread_join(tid2,NULL);
        return 1;
}

实际的打印结果着实让我觉得意外。

consumer locking
(slept 2 sec here)
producer unlocking
consumer return 0
producer return 0

consumer线程被lock住了,这个是expected。但是producer依然可以用unlock函数来解锁,虽然之前它没有调用过lock。随后又做了一个试验,把上面两行斜体的代码交换一下,也就是先启动producer然后启动consumer,同时去掉sleep,输出结果为:

producer unlocking
producer return 0
consumer locking
consumer return 0

结果同样说明了,producer中的unlock依然可以将不是自己lock的mutex解锁,从而让consumer能够得到mutex,起到了signal的作用。

依此看来,pthread的mutex就是一个binary semaphore,可以在任何地方wait和post。当然实际应用当中还是应该遵循书中的规定,养成良好的使用mutex的习惯。

2 Comments »

  1. 学习了!
    以后技术上还要多向你请教
    呵呵,今后一定常来

    这次到LA,因为亲戚工作比较忙,自己不好给他们添麻烦,没去成你那里。回头电话聊

    Comment by vine — January 19, 2007 @ 11:18 am

  2. 没问题,以后多交流

    Comment by campos — January 19, 2007 @ 11:51 am


RSS feed for comments on this post. TrackBack URI

Leave a comment

Create a free website or blog at WordPress.com.