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時才可能跳出循環(huán).
}
pthread_mutex_unlock(&mutex);
簡單來說就是condition的wait和signal是一種簡單的信號機制,在wait時,就將當前線程阻塞,并且還要把負責將mutex打開,否則遵循mutex機制的其他乖孩子線程無法更改謂詞,和發(fā)出signal了.這樣wait的線程就會一直阻塞,無法進行下一步操作.而在wait退出的時候,會負責將mutex鎖上,從而wait的線程從wait剛退出就自動擁有這個mutex所保護的謂詞.帖子中說道:
Never change, nor check, the predicate condition unless the mutex is locked. Ever.
注意,我這里所說的一直阻塞其實只會發(fā)生在謂詞改變不了的時候.我們一般在signal的線程中采用下面的兩種規(guī)范寫法:
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);
}
這樣的話,即使錯誤信號發(fā)出,只要謂詞不滿足條件一樣是線程阻塞.
下面是一個例子:
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);
}