Q1:
在编写一个自己的MutexLock,MutexLockGuard,Condition_var时,为何pthread的condition的wait及signal操作需要传入mutex?
A1:
具体的可见这个帖子:c - Does pthread_cond_wait(&cond_t, &mutex); unlock and then lock the mutex? - Stack Overflow
必须知道条件变量并不需要互斥锁来保护,网络上说需要保护条件变量的都是胡扯.回答中这样说道:
The condition variable doesn't need mutual exclusion protection; the predicate data does.
那为什么在wait和signal的时候总是在外层包一层mutex呢?
那是因为我们必须要保护谓词
(predicate),我们使用条件变量的时候是要和谓词一起用的,要是我们想实现这样一个功能:一旦资源数大于0就进行处理,我们可以有下面的代码(这其实也是信号量的实现原理)
extern int rc_count; // 表示资源值
pthread_mutex_t mutex;//是为了保护rc_count,即rc_count就是谓词
pthread_cond_t cond;
pthread_mutex_lock(&mutex);
while(rc_count <= 0)
{
pthread_cond_wait(&cond,&mutex);//只有当资源量大于0时才可能跳出循环.
}
pthread_mutex_unlock(&mutex);
简单来说就是condition的wait和signal是一种简单的信号机制,在wait时,就将当前线程阻塞,并且还要把负责将mutex打开,否则遵循mutex机制的其他乖孩子线程无法更改谓词,和发出signal了.这样wait的线程就会一直阻塞,无法进行下一步操作.而在wait退出的时候,会负责将mutex锁上,从而wait的线程从wait刚退出就自动拥有这个mutex所保护的谓词.帖子中说道:
Never change, nor check, the predicate condition unless the mutex is locked. Ever.
注意,我这里所说的一直阻塞其实只会发生在谓词改变不了的时候.我们一般在signal的线程中采用下面的两种规范写法:
pattern1
pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cv);
pattern2
pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
想想如果我们的线程都是乖孩子,意味着只有获取锁才能改变谓词,这样的话如果因为某些粗心的程序员写出了下面的代码:
some code
pthread_cond_signal(&cv)//错误添加了这一行
some code
pthread_mutex_lock(&mtx);
TODO: change predicate state here as needed.
pthread_cond_signal(&cv);
pthread_mutex_unlock(&mtx);
由于我们wait端一般是这样写的:
while(rc_count <= 0)
{
pthread_cond_wait(&cond,&mutex);
}
这样的话,即使错误信号发出,只要谓词不满足条件一样是线程阻塞.
下面是一个例子:
int WaitForPredicate()
{
// lock mutex (means:lock access to the predicate)
pthread_mutex_lock(&mtx);
// we can safely check this, since no one else should be
// changing it unless they have the mutex, which they don't
// because we just locked it.
while (!predicate)
{
// predicate not met, so begin waiting for notification
// it has been changed *and* release access to change it
// to anyone wanting to by unlatching the mutex, doing
// both (start waiting and unlatching) atomically
pthread_cond_wait(&cv,&mtx);
// upon arriving here, the above returns with the mutex
// latched (we own it). The predicate *may* be true, and
// we'll be looping around to see if it is, but we can
// safely do so because we own the mutex coming out of
// the cv-wait call.
}
// we still own the mutex here. further, we have assessed the
// predicate is true (thus how we broke the loop).
// take whatever action needed.
// You *must* release the mutex before we leave. Remember, we
// still own it even after the code above.
pthread_mutex_unlock(&mtx);
}