一.概述 互斥量是線程同步的一種機制,用來保護多線程的共用資源。同一時刻,只允許一個線程對臨界區進行訪問。互斥量的工作流程:創建一個互斥量,把這個互斥量的加鎖調用放在臨界區的開始位置,解鎖調用放到臨界區的結束位置。當內核優先把某個線程調度到臨界區的開始...
一.概述
互斥量是線程同步的一種機制,用來保護多線程的共用資源。同一時刻,只允許一個線程對臨界區進行訪問。
互斥量的工作流程:創建一個互斥量,把這個互斥量的加鎖調用放在臨界區的開始位置,解鎖調用放到臨界區的結束位置。當內核優先把某個線程調度到臨界區的開始位置時,線程執行這個加鎖調用,併進入臨界區對資源進行操作。此時其他線程再被內核調度到這裡的時候,由於該互斥量已被加鎖狀態,得不到鎖會一直阻塞在這裡,導致其他線程不能進入臨界區,直到剛剛那個進入臨界區的線程離開臨界區並執行解鎖調用。
二.函數介面
1.初始化互斥量
互斥量是一個pthread_mutex_t類型的變數。
1.1:用巨集常量初始化:
1 pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
1.2:用函數初始化:
1 #include <pthread.h> 2 3 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
mutex:互斥量結構指針
attr:互斥量的屬性結構指針
2.設置互斥量屬性
1 #include <pthread.h> 2 3 int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
attr:互斥量的屬性結構指針
type:PTHREAD_MUTEX_NORMAL(預設屬性),PTHREAD_MUTEX_ERRORCHECK(會進行錯誤檢查,速度比較慢),PTHREAD_MUTEX_RECURSIVE(遞歸鎖)。對於遞歸鎖,同一個線程對一個遞歸鎖加鎖多次,會有一個鎖計數器,解鎖的時候也需要解鎖這個次數才能釋放該互斥量。
3.加鎖與解鎖
1 #include <pthread.h> 2 3 int pthread_mutex_lock(pthread_mutex_t *mutex); 4 int pthread_mutex_trylock(pthread_mutex_t *mutex); 5 int pthread_mutex_unlock(pthread_mutex_t *mutex);
參數都是互斥量指針。pthread_mutex_lock()得不到鎖會阻塞,int pthread_mutex_trylock()得不到鎖會立即返回,並返回EBUSY錯誤。
還有一個pthread_mutex_timedlock()會根據時間來等待加鎖,如果這段時間得不到鎖會返回ETIMEDOUT錯誤!
1 #include <pthread.h> 2 #include <time.h> 3 4 int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
4.銷毀互斥量
1 #include <pthread.h> 2 3 int pthread_mutex_destroy(pthread_mutex_t *mutex);
mutex:創建的互斥量指針
三.簡單例子
寫個簡單的例子,主線程消費,子線程生產,並模擬使用過程中可能遇到的缺點
1 /** 2 * @file pthread_mutex.c 3 */ 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 #include <pthread.h> 10 11 /* 定義互斥量 */ 12 pthread_mutex_t mtx; 13 /* 互斥量屬性 */ 14 pthread_mutexattr_t mtx_attr; 15 /* 全局資源 */ 16 int money; 17 18 void err_exit(const char *err_msg) 19 { 20 printf("error:%s\n", err_msg); 21 exit(1); 22 } 23 24 /* 線程函數 */ 25 void *thread_fun(void *arg) 26 { 27 while (1) 28 { 29 /* 加鎖 */ 30 pthread_mutex_lock(&mtx); 31 32 printf("子線程進入臨界區查看money\n"); 33 if (money == 0) 34 { 35 money += 200; 36 printf("子線程:money = %d\n", money); 37 } 38 39 /* 解鎖 */ 40 pthread_mutex_unlock(&mtx); 41 42 sleep(1); 43 } 44 45 return NULL; 46 } 47 48 int main(void) 49 { 50 pthread_t tid; 51 52 /* 初始化互斥量屬性 */ 53 if (pthread_mutexattr_init(&mtx_attr) == -1) 54 err_exit("pthread_mutexattr_init()"); 55 56 /* 設置互斥量屬性 */ 57 if (pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_NORMAL) == -1) 58 err_exit("pthread_mutexattr_settype()"); 59 60 /* 初始化互斥量 */ 61 if (pthread_mutex_init(&mtx, &mtx_attr) == -1) 62 err_exit("pthread_mutex_init()"); 63 64 /* 創建一個線程 */ 65 if (pthread_create(&tid, NULL, thread_fun, NULL)== -1) 66 err_exit("pthread_create()"); 67 68 money = 1000; 69 while (1) 70 { 71 /* 加鎖 */ 72 pthread_mutex_lock(&mtx); 73 74 if (money > 0) 75 { 76 money -= 100; 77 printf("主線程:money = %d\n", money); 78 } 79 80 /* 解鎖 */ 81 pthread_mutex_unlock(&mtx); 82 83 sleep(1); 84 } 85 86 return 0; 87 }
主線程和子線程都對money的操作進行了互斥量保護。68行,初始化money是1000,主線程每次消耗100,子線程只有到money是0是才會生產。sleep(1)防止獨占cpu,也方便列印信息。編譯運行:
可以看到這裡有個非常浪費資源的問題:主線程消耗money的時候,子線程它不知道什麼時候才消耗完,每次內核調度到它時,它都進入臨界區加鎖互斥量,然後查看money,再解鎖。這無意義的操作,簡直是極大的浪費!有什麼辦法可以解決這個問題呢?它有一個好伙伴,叫條件變數。
四.死鎖
假設:當線程1獲取鎖1,再獲取鎖2後才能進入臨界區1,線程2獲取鎖2,再獲取鎖1才能進入臨界區2。某個時刻,線程1獲取了鎖1,再去獲取鎖2的時候發現鎖2已經被線程2鎖住了,而線程2獲取鎖2後,發現鎖1被線程1鎖住了。這樣2個線程誰也不讓誰,都進不了自己的臨界區,就產生了死鎖現象!一般遇到這種情況常見的解決辦法是:規定統一的加鎖順序。線程1和線程2都按照先鎖1,再鎖2。還一種就是使用pthread_mutex_trylock(),如果該函數返回EBUSY錯誤,就釋放這個線程的所有鎖,不過效率有點低。