在多線程編程中僅使用互斥鎖來完成互斥是不夠用的, 如以下情形:
假設有兩個線程 t1 和 t2, 需要這個兩個線程循環對一個共享變量 sum 進行自增操作,那么 t1 和 t2 只需要使用互斥量即可保證操作正確完成,線程執行代碼如所示:
pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) { pthread_mutex_lock(&sumlock); sum++; pthread_mutex_unlock(&sumlock); }
如果這時需要增加另一個線程 t3,需要 t3 在 count 大於 100 時將 count 值重新置 0 值,那么可以 t3 可以實現如下:
void * t3 (void) { pthread_mutex_lock(&sumlock); if (sum >= 100) { sum = 0; pthread_mutex_unlock(&sumlock); } else { pthread_mutex_unlock(&sumlock); usleep(100); } }
以上代碼存在以下問題:
1) sum 在大多數情況下小於 100, 那么對 t3 的代碼來說,大多數情況下, 走的是 else 分支, 只是 lock 和 unlock,然后 sleep()。 這浪費了 CPU 處理時間。
2) 為了節省 CPU 處理時間, t3 會在探測到 sum 沒到達 100 的時候 usleep()一段時間。這樣卻又帶來另外一個問題, 亦即 t3 響應速度下降。 可能在 sum 到達 200 的時候, t3 才會醒過來。
這樣時間與效率出現了矛盾,而條件變量就是解決這個問題的好方法。
創建與銷毀
1. 創建條件變量
Pthreads 用 pthread_cond_t 類型的變量來表示條件變量。程序必須在使用 pthread_cond_t變量之前對其進行初始化。
(1) 靜態初始化
對於靜態分配的變量可以簡單地將 PTHREAD_COND_INITIALIZER 賦值給變量來初始化默認行為的條件變量。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
(2)動態初始化
對動態分配或者不使用默認屬性的條件變量來說可以使用 pthread _cond_init()來初始化。函數原型如下:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
參數 cond 是一個指向需要初始化 pthread_cond_t 變量的指針,參數 attr 傳遞 NULL 值時, pthread_cond_init()將 cond 初始化為默認屬性的條件變量。
函數成功將返回 0;否則返回一個非 0 的錯誤碼。
靜態初始化程序通常比調用 pthread_cond_init()更有效,而且在任何線程開始執行之前,確保變量被執行一次。
以下代碼示例了條件變量的初始化。
pthread_cond_t cond; int error; if (error = pthread_cond_init(&cond, NULL)); fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));
2. 銷毀條件變量
函數 pthread_cond_destroy()用來銷毀它參數所指出的條件變量,函數原型如下:
int pthread_cond_destroy(pthread_cond_t *cond);
函數成功調用返回 0,否則返回一個非 0 的錯誤碼。以下代碼演示了如何銷毀一個條件變量。
pthread_cond_t cond; int error; if (error = pthread_cond_destroy(&cond)) fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
等待與通知
等待
條件變量是與條件測試一起使用的,通常線程會對一個條件進行測試,如果條件不滿足就會調用條件等待函數來等待條件滿足。
條件等待函數有 pthread_cond_wait()pthread_cond_timedwait()和兩個,函數原型如下:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
pthread_cond_wait()函數在條件不滿足時將一直等待, 而 pthread_cond_timedwait()將只等待一段時間。
參數 cond 是一個指向條件變量的指針,參數 mutex 是一個指向互斥量的指針,線程在調用前應該擁有這個互斥量,當線程要加入條件變量的等待隊列時,等待操作會使線程釋放這個互斥量。 pthread_timedwait()的第三個參數 abstime 是一個指向返回時間的指針,如果條件變量通知信號沒有在此等待時間
之前出現,等待將超時退出, abstime 是個絕對時間,而不是時間間隔。
以上函數成功調用返回 0,否則返回非 0 的錯誤碼,其中 pthread_cond_timedwait() 函數如果 abstime 指定的時間到期,錯誤碼為 ETIMEOUT。
以下代碼使得線程進入等待,直到收到通知並且滿足 a 大於等於 b 的條件。
pthread_mutex_lock(&mutex) while(a < b) pthread_cond_wait(&cond, &mutex) pthread_mutex_unlock(&mutex)
通知
當另一個線程修改了某參數可能使得條件變量所關聯的條件變成真時,它應該通知一個或者多個等待在條件變量等待隊列中的線程。
條件通知函數有 pthread_cond_signal()和 pthread_cond_broadcast()函數,其中 pthread_cond_signal 函數可以喚醒一個在條件變量等待隊列等待的線程,而 pthread_cond_broadcast函數可以所有在條件變量等待隊列等待的線程。函數原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
參數 cond 是一個指向條件變量的指針。函數成功返回 0,否則返回一個非 0 的錯誤碼。
線程范例
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[3]; int sum = 0; pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 靜態初始化互斥量 */ pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 靜態初始化條件變量 */
void * t1t2(void *arg) { int i; long id = (long)arg; for (i = 0; i < 60; i++) { pthread_mutex_lock(&sumlock); /* 使用互斥量保護臨界變量 */ sum++; printf("t%ld: read sum value = %d\n", id + 1 , sum); pthread_mutex_unlock(&sumlock); if (sum >= 100) pthread_cond_signal(&cond_sum_ready); /* 發送條件通知,喚醒等待線程 */ } return NULL; }
void * t3(void *arg) { pthread_mutex_lock(&sumlock); while(sum < 100) /* 不滿足條件將一直等待 */ pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待條件滿足 */ sum = 0; printf("t3: clear sum value\n"); pthread_mutex_unlock(&sumlock); return NULL; }
int main(void) { int err; long i; for (i = 0; i < 2; i++) { err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i); /* 創建線程 1 線程 2 */ if (err != 0) { printf("Can't create thread :[%s]", strerror(err)); } } err = pthread_create(&(tid[2]), NULL, &t3, NULL); /* 創建線程 3 */ if (err != 0) printf("Can't create thread :[%s]", strerror(err)); for (i = 0; i < 3; i++) pthread_join(tid[i], NULL); return 0; }
運行結果如下所示, sum 累加到 100 時發送條件通知,但程序結果中 sum 計算到 103 時, t3 才被調用,這是因為 signal 與 wait 調用之間有間隙存在。
t1: read sum value = 1 t1: read sum value = 2 ... t2: read sum value = 100 t1: read sum value = 101 t1: read sum value = 102 t1: read sum value = 103 t3: clear sum value t2: read sum value = 1 ....... t2: read sum value = 17