深入理解Solaris内核中互斥锁(mutex)与条件变量(condvar)之协同工作原理

在Solaris上写内核模块总是会用到互斥锁(mutex)与条件变量(condvar), 光阴荏苒日月如梭弹指一挥间,Solaris的大船说沉就要沉了,此刻心情不是太好(Orz)。每次被年轻的有才华的同事们(比如Letty同学)问起mutex和cv怎么协同工作的,我总是不能给出一个非常清晰的解释。直到今天,看了cv_wait()的源代码之后,我终于可以给他们一个清楚明白的回答了。

Solaris的源码无法被公开粘贴出来,幸好还有OpenSolaris的继承者illumos。 先贴cv_wait()的源码,再讲互斥锁(mutex)与条件变量(condvar)的协同工作原理。

185 /*
186  * Block on the indicated condition variable and release the
187  * associated kmutex while blocked.
188  */
189 void
190 cv_wait(kcondvar_t *cvp, kmutex_t *mp)
191 {
192     if (panicstr)
193             return;
194     ASSERT(!quiesce_active);
195
196     ASSERT(curthread->t_schedflag & TS_DONT_SWAP);
197     thread_lock(curthread);                 /* lock the thread */
198     cv_block((condvar_impl_t *)cvp);
199     thread_unlock_nopreempt(curthread);     /* unlock the waiters field */
200     mutex_exit(mp);
201     swtch();
202     mutex_enter(mp);
203 }

注意: 198, 200-202行,等会儿再解释。

首先,一个典型的使用mutex和cv的例子是这样子滴,

 1 static kmutex_t         mutex;
 2 static kcondvar_t       condv;
 3 static unsigned int     ready = 0;
 4 
 5 /* 1. init mutex and cv */
 6 mutex_init(&mutex, NULL, MUTEX_DRIVER, NULL);
 7 cv_init(&condv, NULL, CV_DRIVER, NULL);
 8 
 9 /* 2. use mutex and cv */
10 
11   /* Thread 1 */               | /* Thread 2 */
12   mutex_enter(&mutex);         | mutex_enter(&mutex);
13   while (ready == 0)           | ready = 1;
14       cv_wait(&condv, &mutex); | cv_signal(&condv);
15   mutex_exit(&mutex);          | mutex_exit(&mutex);
16 
17 /* 3. destroy mutex and cv */
18 cv_destroy(&condv);
19 mutex_destroy(&mutex);
  1. 在Thread 1中,首先获得互斥锁,然后判断条件(ready==0)是否成立,如果成立,则调用cv_wait(&condv, &mutex)进入睡眠;
  2. 在Thread 2中,首先获得互斥锁,然后将ready赋值为1,调用cv_signal(&condv)唤醒正在睡眠的Thread1,同时释放持有的互斥锁;
  3. Thread1一旦醒来,会重新判断条件(ready==0)是否成立,如果不成立,则释放互斥锁。 (当然,如果成立,则将再次进入睡眠,等待下次被唤醒)

然后, 问题(Letty同学曾经问过我的来了既然Thread 1在L12行获得了互斥锁然后睡过去了,那么Thread 2怎么可能获得互斥锁?

This is a good question, a really good question! (ps. 老美每次被问住了的时候都这么说)

在今天之前我无法回答,或者只能估摸着回答说"只能看具体实现了"。 好了,我今天就是真看完了具体实现。在cv_wait()的源代码中,

198    cv_block((condvar_impl_t *)cvp);
...
200
mutex_exit(mp); 201 swtch(); 202 mutex_enter(mp);

第198行将自己(currthread)加入睡眠队列,第200行将互斥锁释放,然后在第201行进入睡眠,等待被唤醒。一旦被唤醒,在第202行重新获得互斥锁。

也就是说,睡前释放互斥锁,醒来再获取互斥锁。这样别的线程就有机会获得互斥锁后干活,活干完后将睡眠的线程唤醒。

这也解释了为什么cv_wait()函数不仅仅只有一个参数kcondvar_t *cvp, 还包含参数kmutex_t *mp。

行文至此,我想用一句话作为总结,"The source code is the final world." 如果你想成为一个非常优秀的程序员,请记住RTFSC

PS:

1. 如果想进一步弄懂为什么要将条件变量和互斥锁一起使用保证同步,请自行google或阅读OS相关的book。

2. 所有关于互斥锁和条件变量的协同工作原理应该是一致的,比如POSIX的pthread_mutex和pthread_cond,Linux内核mutex和completion variable等。

原文地址:https://www.cnblogs.com/idorax/p/6285337.html