pthread_cond_wait()的實現原理

/************pthread_cond_wait()的使用方法**********/
    pthread_mutex_lock(&qlock);    
    pthread_cond_wait(&qready, &qlock);
    pthread_mutex_unlock(&qlock);
/*****************************************************/
 
The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to the function, which then atomically places them calling thread on the list of threads waiting for the condition and unlocks the mutex. This closes the window between the time that the condition is checked and the time that the thread goes to sleep waiting for the condition to change, so that the thread doesn't miss a change in the condition. When pthread_cond_wait returns, the mutex is again locked.
上面是APUE的原話,就是說pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)函數傳入的參數mutex用於保護條件,因爲我們在調用pthread_cond_wait時,如果條件不成立我們就進入阻塞,但是進入阻塞這個期間,如果條件變量改變了的話,那我們就漏掉了這個條件。因爲這個線程還沒有放到等待隊列上,所以調用pthread_cond_wait前要先鎖互斥量,即調用pthread_mutex_lock(),pthread_cond_wait在把線程放進阻塞隊列後,自動對mutex進行解鎖,使得其它線程可以獲得加鎖的權利。這樣其它線程才能對臨界資源進行訪問並在適當的時候喚醒這個阻塞的進程。當pthread_cond_wait返回的時候又自動給mutex加鎖。
實際上邊代碼的加解鎖過程如下:
/************pthread_cond_wait()的使用方法**********/
pthread_mutex_lock(&qlock);    /*lock*/
pthread_cond_wait(&qready, &qlock); /*block-->unlock-->wait() return-->lock*/
pthread_mutex_unlock(&qlock); /*unlock*/
/*****************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>

void* testThreadPool(int *t);
pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;
int a = 0;

int main() {

int sock_fd, conn_fd;
int optval;

socklen_t cli_len;
struct sockaddr_in cli_addr, serv_addr;


sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
   printf("socket\n");
}

optval = 1;
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *) &optval,
    sizeof(int)) < 0) {
   printf("setsockopt\n");
}

memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(4507);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(sock_fd, (struct sockaddr *) &serv_addr,
    sizeof(struct sockaddr_in)) < 0) {
   printf("bind\n");
}

if (listen(sock_fd, 100) < 0) {
   printf("listen\n");
}

cli_len = sizeof(struct sockaddr_in);
int t;
pthread_t * mythread;
mythread = (pthread_t*) malloc(100 * sizeof(pthread_t));
for (t = 0; t < 5; t++) {
   int *i=(int*)malloc(sizeof(int));
   *i=t;
   if (pthread_create(&mythread[t], NULL, (void*)testThreadPool, (void*)i) != 0) {
    printf("pthread_create");
   }
}

while (1) {
   conn_fd = accept(sock_fd, (struct sockaddr *) &cli_addr, &cli_len);
   if (conn_fd < 0) {
    printf("accept\n");
   }
   printf("accept a new client, ip:%s\n", inet_ntoa(cli_addr.sin_addr));

   pthread_mutex_lock(&clifd_mutex);
   a=conn_fd;
   pthread_cond_signal(&clifd_cond);
   pthread_mutex_unlock(&clifd_mutex);
}
return 0;
}

void* testThreadPool(int *t) {

printf("t is %d\n", *t);
for (;;) {
   pthread_mutex_lock(&clifd_mutex);
   pthread_cond_wait(&clifd_cond, &clifd_mutex);
   printf("a is %d\n", a);
   printf("t is %d\n", *t);
   pthread_mutex_unlock(&clifd_mutex);
   sleep(100);
}
return (void*) 0;
}

瞭解 pthread_cond_wait() 的作用非常重要 -- 它是 POSIX 線程信號發送系統的核心,也是最難以理解的部分。 

首先,讓我們考慮以下情況:線程爲查看已鏈接列表而鎖定了互斥對象,然而該列表恰巧是空的。這一特定線程什麼也幹不了 -- 其設計意圖是從列表中除去節點,但是現在卻沒有節點。因此,它只能: 

鎖定互斥對象時,線程將調用 pthread_cond_wait(&mycond,&mymutex)。pthread_cond_wait() 調用相當複雜,因此我們每次只執行它的一個操作。 

pthread_cond_wait() 所做的第一件事就是同時對互斥對象解鎖(於是其它線程可以修改已鏈接列表),並等待條件 mycond 發生(這樣當 pthread_cond_wait() 接收到另一個線程的“信號”時,它將甦醒)。現在互斥對象已被解鎖,其它線程可以訪問和修改已鏈接列表,可能還會添加項。 【要求解鎖並阻塞是一個原子操作

此時,pthread_cond_wait() 調用還未返回。對互斥對象解鎖會立即發生,但等待條件 mycond 通常是一個阻塞操作,這意味着線程將睡眠,在它甦醒之前不會消耗 CPU 週期。這正是我們期待發生的情況。線程將一直睡眠,直到特定條件發生,在這期間不會發生任何浪費 CPU 時間的繁忙查詢。從線程的角度來看,它只是在等待 pthread_cond_wait() 調用返回。 

現在繼續說明,假設另一個線程(稱作“2 號線程”)鎖定了 mymutex 並對已鏈接列表添加了一項。在對互斥對象解鎖之後,2 號線程會立即調用函數 pthread_cond_broadcast(&mycond)。此操作之後,2 號線程將使所有等待 mycond 條件變量的線程立即甦醒。這意味着第一個線程(仍處於 pthread_cond_wait() 調用中)現在將甦醒。 

現在,看一下第一個線程發生了什麼。您可能會認爲在 2 號線程調用 pthread_cond_broadcast(&mymutex) 之後,1 號線程的 pthread_cond_wait() 會立即返回。不是那樣!實際上,pthread_cond_wait() 將執行最後一個操作:重新鎖定 mymutex。一旦 pthread_cond_wait() 鎖定了互斥對象,那麼它將返回並允許 1 號線程繼續執行。那時,它可以馬上檢查列表,查看它所感興趣的更改。 

停止並回顧! 
那個過程非常複雜,因此讓我們先來回顧一下。第一個線程首先調用: 
pthread_mutex_lock(&mymutex); 
然後,它檢查了列表。沒有找到感興趣的東西,於是它調用:
pthread_cond_wait(&mycond, &mymutex); 
 
然後,pthread_cond_wait() 調用在返回前執行許多操作: 
 
pthread_mutex_unlock(&mymutex); 
 
它對 mymutex 解鎖,然後進入睡眠狀態,等待 mycond 以接收 POSIX 線程“信號”。一旦接收到“信號”(加引號是因爲我們並不是在討論傳統的 UNIX 信號,而是來自 pthread_cond_signal() 或 pthread_cond_broadcast() 調用的信號),它就會甦醒。但 pthread_cond_wait() 沒有立即返回 -- 它還要做一件事:重新鎖定 mutex:
pthread_mutex_lock(&mymutex); 
 
pthread_cond_wait() 知道我們在查找 mymutex “背後”的變化,因此它繼續操作,爲我們鎖定互斥對象,然後才返回。

發佈了12 篇原創文章 · 獲贊 16 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章