條件變量是利用線程間共享的全局變量進行同步的一種機制,
主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;
另一個線程使"條件成立"(給出條件成立信號)。
為了防止競爭,條件變量的使用總是和一個互斥鎖結合在一起。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
等待條件有兩種方式:條件等待pthread_cond_wait()和計時等待pthread_cond_timedwait(),
其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,
其中abstime以與time()系統調用相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。
無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()
(或pthread_cond_timedwait(),下同)的競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)
或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),
而在更新條件等待隊列以前,mutex保持鎖定狀態,並在線程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,
mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。
激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;
而pthread_cond_broadcast()則激活所有等待線程。
現在來看一段典型的應用:看注釋即可
1 #include<pthread.h> 2 #include<unistd.h> 3 #include<stdio.h> 4 #include<string.h> 5 #include<stdlib.h> 6 7 static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; 8 static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 9 10 struct node 11 { 12 int n_number; 13 struct node*n_next; 14 }*head = NULL;/*[thread_func]*/ 15 16 /*釋放節點內存*/ 17 static void cleanup_handler(void*arg) 18 { 19 printf("Cleanup handler of second thread.\n"); 20 21 struct node *p = *((struct node**)arg); 22 while(p) 23 { 24 struct node* tmp = p->n_next; 25 free(p); 26 printf("free %p\n", p); 27 p = tmp; 28 } 29 30 *((struct node **)arg) = NULL; 31 32 (void)pthread_mutex_unlock(&mtx); 33 } 34 35 static void* thread_func(void*arg) 36 { 37 struct node* p = NULL; 38 pthread_cleanup_push(cleanup_handler, &head); 39 40 while(1) 41 { 42 pthread_mutex_lock(&mtx); 43 //這個mutex_lock主要是用來保護wait等待臨界時期的情況, 44 //當在wait為放入隊列時,這時,已經存在Head條件等待激活 45 //的條件,此時可能會漏掉這種處理 46 //這個while要特別說明一下,單個pthread_cond_wait功能很完善, 47 //為何這里要有一個while(head==NULL)呢?因為pthread_cond_wait 48 //里的線程可能會被意外喚醒,如果這個時候head!=NULL, 49 //則不是我們想要的情況。這個時候, 50 //應該讓線程繼續進入pthread_cond_wait 51 52 while(1) 53 { 54 while(head==NULL) 55 { 56 pthread_cond_wait(&cond,&mtx); 57 } 58 //pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx, 59 //然后阻塞在等待隊列里休眠,直到再次被喚醒 60 //(大多數情況下是等待的條件成立而被喚醒,喚醒后, 61 //該進程會先鎖定先pthread_mutex_lock(&mtx);, 62 //再讀取資源用這個流程是比較清楚的 63 /*block-->unlock-->wait()return-->lock*/ 64 65 p = head; 66 head = head->n_next; 67 printf("Got %d from front of queue\n",p->n_number); 68 free(p); 69 } 70 pthread_mutex_unlock(&mtx);//臨界區數據操作完畢,釋放互斥鎖 71 72 } 73 74 pthread_cleanup_pop(0); 75 76 return 0; 77 } 78 79 int main(void) 80 { 81 pthread_t tid; 82 int i; 83 struct node* p; 84 pthread_create(&tid,NULL,thread_func,NULL); 85 //子線程會一直等待資源,類似生產者和消費者, 86 //但是這里的消費者可以是多個消費者, 87 //而不僅僅支持普通的單個消費者,這個模型雖然簡單, 88 //但是很強大 89 for(i=0;i<10;i++) 90 { 91 p=(struct node*)malloc(sizeof(struct node)); 92 p->n_number=i; 93 pthread_mutex_lock(&mtx);//需要操作head這個臨界資源,先加鎖, 94 p->n_next=head; 95 head=p; 96 pthread_cond_signal(&cond); 97 pthread_mutex_unlock(&mtx);//解鎖 98 sleep(1); 99 } 100 101 p=(struct node*)malloc(sizeof(struct node)); 102 p->n_number=i; 103 pthread_mutex_lock(&mtx);//需要操作head這個臨界資源,先加鎖, 104 p->n_next=head; 105 head=p; 106 pthread_mutex_unlock(&mtx);//解鎖 107 108 printf("thread1 wanna end the cancel thread2.\n"); 109 pthread_cancel(tid); 110 //關於pthread_cancel,有一點額外的說明,它是從外部終止子線程, 111 //子線程會在最近的取消點,退出線程,而在我們的代碼里,最近的 112 //取消點肯定就是pthread_cond_wait()了。 113 pthread_join(tid,NULL); 114 115 printf("All done--exiting\n"); 116 117 return 0; 118 }
