Linux 學習筆記—執行緒同步之互斥量與條件變數
執行緒同步(同步的意思是協同步調) 執行緒同步機制包括互斥,讀寫鎖以及條件變數等 3.2.1 互斥量(互斥鎖) **互斥量本質是一把鎖,在訪問公共資源前對互斥量設定(加鎖),確保同一時間只有一個執行緒訪問資料,在訪問完成後再釋放(解鎖)互斥量。**在互斥量加鎖之後,其他執行緒試圖對該互斥量再次加鎖時都會被阻塞,知道當前執行緒釋放互斥鎖。如果釋放互斥量時有一個以上的互斥量,那麼所有在該互斥量上阻塞的執行緒都會變成可執行狀態,第一個變成執行的執行緒可以對互斥量加鎖(哪個執行緒先執行哪個執行緒就可以對互斥量加鎖),其他執行緒看到互斥量依然是鎖著的,只能再次阻塞等待該互斥量。
互斥量用pthread_mutex_t資料型別表示,在使用互斥量之前,必須使用pthread_mutex_init函式對它進行初始化,注意,使用完畢後需呼叫pthread_mutex_destroy。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int pthread_mutex_destroy(pthread_mutex_t *mutex); // 兩個函式返回值,成功返回0,否則返回錯誤碼 pthread_mutex_init用於初始化互斥鎖,mutexattr用於指定互斥鎖的屬性,若為NULL,則表示預設屬性。除了用這個函式初始化互斥所外,還可以用如下方式初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER。 pthread_mutex_destroy用於銷燬互斥鎖
/**
* 使用3個執行緒分別列印 A B C
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
pthread_mutex_t g_mutex;
int g_cnt = 0;
void *func(void *arg)
{
int loop = 3;
int result = (int)arg;
printf("result=%d\n",result);
//while (loop > 0) {
//if (g_cnt % 3 == result) {
switch (result)
{
case 0: {
printf("--- a\n");
break;
}
case 1: {
printf("--- b\n");
break;
}
case 2: {
printf("--- c\n");
break;
}
default: {
return NULL;
}
}
pthread_mutex_lock(&g_mutex);
g_cnt++;
//loop--;
printf("g_cnt=%d\n",g_cnt);
pthread_mutex_unlock(&g_mutex);
// }
//}
return NULL;
}
int main(int argc, char **argv)
{
pthread_t t1, t2, t3;
pthread_mutex_init(&g_mutex, NULL);
pthread_create(&t1, NULL, func, (void *)0);
pthread_create(&t2, NULL, func, (void *)1);
pthread_create(&t3, NULL, func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_mutex_destroy(&g_mutex);
return 0;
}
3.2.2 條件變數 條件變數是執行緒可用的一種同步機制,條件變數給多個執行緒提供了一個回合的場所,條件變數和互斥量一起使用,允許執行緒以無競爭的方式等待特定的條件發生。條件變數本身是由互斥體保護的,執行緒在改變條件狀態之前必須首先鎖住互斥量,其他執行緒在獲取互斥量之前就不會覺察到這種變化,因為互斥量必須鎖定之後才改變條件。 #include<pthread.h> pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_destroy(pthread_cont_t *cond); // 成功返回0,否則返回錯誤碼 使用條件變數前呼叫pthread_cond_init初始化,使用完畢後呼叫pthread_cond_destroy做清理工作。除非需要建立一個具有非預設屬性的條件變數,否則pthread_cond_init函式的attr引數可以設定為NULL。 #include<pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); // 成功返回0,否則返回錯誤碼 傳遞給pthread_cond_wait的互斥量對條件進行保護,呼叫者把鎖住互斥量傳給函式,函式然後自動把呼叫執行緒放到等待條件的執行緒列表上,對互斥量解鎖。這就關閉了條件檢查和執行緒進入休眠狀態等待條件改變這兩個操作之間的時間通道,這樣執行緒就不會錯過條件的任何變化。pthread_cond_wait函式返回時,互斥量再次被鎖住。 pthread_cond_broadcast用廣播的形式喚醒所有等待條件變數的執行緒。pthread_cond_signal用於喚醒一個等待條件變數的執行緒,至於哪個執行緒被喚醒,取決於執行緒的優先順序和排程機制。有時候需要喚醒一個指定的執行緒,但pthread沒有對該需要提供解決方法。可以間接實現該需求:定義一個能夠唯一表示目標執行緒的全域性變數,在喚醒等待條件變數的執行緒前先設定該變數為目標執行緒,然後以廣播形式喚醒所有等待條件變數的執行緒,這些執行緒被喚醒後都檢查改變數是否是自己,如果是就開始執行後續程式碼,否則繼續等待。
條件變數例項
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { fprintf(stderr, msg); exit(-1); } while(0)
pthread_cond_t cond;
void *r1(void *arg)
{
pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
static int cnt = 10;
while(cnt--)
{
printf("r1: I am wait.\n");
pthread_mutex_lock(mutex);
pthread_cond_wait(&cond, mutex); /* mutex引數用來保護條件變數的互斥鎖,呼叫pthread_cond_wait前mutex必須加鎖 */
pthread_mutex_unlock(mutex);
}
return "r1 over";
}
void *r2(void *arg)
{
pthread_mutex_t* mutex = (pthread_mutex_t *)arg;
static int cnt = 10;
while(cnt--)
{
//pthread_mutex_lock(mutex); //這個地方不用加鎖操作就行
printf("r2: I am send the cond signal.\n");
pthread_cond_signal(&cond);
//pthread_mutex_unlock(mutex);
sleep(1);
}
return "r2 over";
}
int main(void)
{
pthread_mutex_t mutex;
pthread_t t1, t2;
char* p1 = NULL;
char* p2 = NULL;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&t1, NULL, r1, &mutex);
pthread_create(&t2, NULL, r2, &mutex);
pthread_join(t1, (void **)&p1);
pthread_join(t2, (void **)&p2);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
printf("s1: %s\n", p1);
printf("s2: %s\n", p2);
return 0;
}