有關線程建立:【Linux 編程】線程編程
當多個控制線程共用相同的資料時,需要確保每一個線程看到一致的資料。當一個線程修改資料變數時,其他線程在讀取這個變數值時就可能會看到不一致的資料。在變數修改時間多於一個儲存訪問的處理結構中,當儲存空間讀取或儲存空間寫這兩個周期交叉時,潛在的資料不一致性就會出現。為瞭解決這種資料不一致性的問題,線程利用鎖機制來保證在保證資料的一致性問題。若讀取資料或寫資料操作是原子操作,則不存在資料競爭或資料不一致性問題。
1. 互斥鎖(pthread_mutex_t)
互斥鎖保證同一時間只允許一個線程訪問資料。任何其他試圖擷取已加鎖的互斥鎖,就會被阻塞直到當前線程釋放該互斥鎖。若有多個線程被阻塞,在互斥鎖被釋放後,將喚醒所有阻塞線程,並採取先到先服務的策略來分配互斥鎖的使用權。
互斥鎖的初始化:
- 使用常量PTHREAD_MUTEX_INITIALIZER來初始化靜態分配的互斥量;
- 針對動態分配的互斥量,則調用函數pthread_mutex_init()進行初始化;調用函數pthread_mutex_destroy()來去初始化。需要注意,這兩個函數成對出現,使用方式類似於malloc/free。
互斥鎖的使用:
- 函數pthread_mutex_lock()用來對互斥鎖加鎖。若互斥鎖已上鎖,則當前線程將被阻塞;
- 若系統線程在不能擷取互斥鎖時被阻塞,則調用函數pthread_mutex_trylock()嘗試對互斥鎖進行加鎖。若成功擷取互斥鎖,則鎖住互斥鎖並返回0;若失敗,則返回非0,並設定錯誤碼為EBUSY。
- 函數pthread_mutex_unlock()用來對互斥鎖進行解鎖。
若線程試圖對同一個互斥鎖加鎖兩次,那麼它自身將會陷入死結狀態。在使用鎖機制時,需要預先設計加鎖的順序來避免死結的發生。
例子:
1 #include <unistd.h> 2 #include <pthread.h> 3 #include <stdio.h> 4 5 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 6 7 void *start_routine(void *arg) 8 { 9 int res;10 int times = 0;11 pthread_t thread_id = pthread_self();12 13 while (1) {14 res = pthread_mutex_trylock(&lock);15 if (res != 0)16 {17 ++times;18 printf("Now the mutex lock is using!");19 printf("And thread ID 0x%x is trying %d times!\n",20 (unsigned int) thread_id, times);21 sleep(2);22 continue;23 }24 25 printf("OK, 0x%x get mutex\n", (unsigned int) thread_id);26 times = 0;27 sleep(10);28 pthread_mutex_unlock(&lock);29 30 if (times == 0)31 break;32 }33 34 pthread_exit((void *)thread_id);35 }36 37 int main()38 {39 pthread_t id0, id1;40 int res;41 42 res = pthread_create(&id0, NULL, start_routine, NULL);43 if (res < 0)44 {45 perror("pthread_create error!");46 return (-1);47 }48 49 res = pthread_create(&id1, NULL, start_routine, NULL);50 if (res < 0)51 {52 perror("thread_create error!");53 return (-1);54 }55 56 sleep(20);57 printf("MutexDemo is over!\n");58 59 return 0;60 61 }
View Code
運行結果:
在多線程設計過程中不僅需要考慮加鎖的順序,還需要考慮鎖的粒度大小問題,即加鎖中處理資料的過程。若粒度太粗,就會發生出現很多線程阻塞等待相同的鎖,則導致並發性的效能改善微乎其微。如果鎖的粒度太小,那麼過多的鎖開銷會使系統效能受到影響,而且代碼會變得更加複雜。在設計加鎖時,需要在滿足鎖需求的情況下,在代碼複雜性和最佳化效能找到好的平衡點。
2. 讀寫鎖(pthread_rwlock_t)
不同於互斥鎖一次只能有一個線程可以對其加鎖。讀寫鎖具有三種狀態:讀模式下加鎖狀態、寫入模式下加鎖狀態、不加鎖狀態。一次只有一個線程可以佔有寫入模式的讀寫鎖,但多個線程可以同時佔有讀模式的讀寫鎖。因此,讀寫鎖也可以叫做共用-獨佔鎖。當讀寫鎖以讀模式鎖住時,它是以共用模式鎖住的;當它以寫入模式鎖住時,它是以獨佔模式鎖住的。
若讀寫鎖以讀模式鎖住狀態時,如果有另外的線程試圖從寫入模式加鎖,將阻塞隨後的讀模式鎖請求。若多個線程共同以寫入模式加鎖,處理方式與互斥鎖處理方式相同。
讀寫鎖的初始化使用pthread_rwlock_init()來完成,在釋放它們底層記憶體之前需要調用函數pthead_rwlock_destroy()來去初始化。
讀寫鎖的使用:
- 讀模式加鎖,pthread_rwlock_rdlock()和pthread_rwlock_tryrdlock();
- 寫入模式加鎖,pthread_rwlock_wrlock()和pthread_rwlock_trywrlock();
- 解鎖操作,pthread_rwlock_unlock()。
3.條件變數(pthread_cond_t)
條件變數與互斥鎖一起使用的,允許線程以無競爭的方式等待特定的條件發生。線程在改變條件狀態之前首先鎖住互斥鎖,其他線程在擷取互斥鎖之前不會察覺到這種改變,因此必須鎖定互斥鎖以後才能計算條件。
條件變數的初始化:
- 用常量PTHREAD_COND_INITIALIZER賦給靜態分配的條件變數;
- 若條件變數是動態分配的,則利用pthread_cond_init()函數進行初始化;在釋放底層記憶體空間之前,必須使用pthread_cond_destroy()函數對條件變數去除初始化。
條件變數使用函數pthread_cond_wait()等待條件為真。函數中的互斥鎖對條件進行保護,調用者把鎖住的互斥鎖傳給函數。函數把調用線程放在等待條件的線程列表中,然後對互斥鎖解鎖,這兩個操作是原子操作。函數在返回時,將再次獲得互斥鎖的控制權。函數pthread_cond_timedwait()在滿足pthread_cond_wait()函數的處理方式的基礎上,加上逾時處理。當在規定時間內條件不能滿足,則產生一個錯誤碼並返回。
有兩個函數用於通知線程條件已滿足。pthread_cond_signal()函數將喚醒等待該條件的某個線程,而pthread_cond_broadcast()函數將喚醒等待該條件的所有線程。
有關條件變數的編程例子:【Linux 編程】pthead_cond_t 的使用