大话「线程同步」
文章目錄
- 初始線程同步
- 互斥量
- 互斥量相關操作函數
- 死鎖
- 讀寫鎖
- 讀寫鎖相關操作函數
- 生產者和消費者模型
- 信號量
- 條件變量
初始線程同步
線程的主要優勢在于,能夠通過全局變量來共享信息。不過,這種便捷的共享是有代價的:必須確保多個線程不會同時修改同一變量,或者某一線程不會讀取正在由其他線程修改的變量。
臨界區是指訪問某一共享資源的代碼片段,并且這段代碼的執行應為原子操作,也就是同時訪問同一共享資源的其他線程不應終端該片段的執行。
線程同步即當有一個線程在對內存進行操作時,其他線程都不可以對這個內存地址進行操作,直到該線程完成操作,其他線程才能對該內存地址進行操作,而其他線程則處于等待狀態。
互斥量
為避免線程更新共享變量時出現問題,可以使用互斥量(mutex 是 mutual exclusion的縮寫)來確保同時僅有一個線程可以訪問某項共享資源。可以使用互斥量來保證對任意共享資源的原子訪問。
互斥量有兩種狀態:已鎖定(locked)和未鎖定(unlocked)。任何時候,至多只有一個線程可以鎖定該互斥量。試圖對已經鎖定的某一互斥量再次加鎖,將可能阻塞線程或者報錯失敗,具體取決于加鎖時使用的方法。
一旦線程鎖定互斥量,隨即成為該互斥量的所有者,只有所有者才能給互斥量解鎖。一般情況下,對每一共享資源(可能由多個相關變量組成)會使用不同的互斥量,每一線程在訪問同一資源時將采用如下協議:
- 針對共享資源鎖定互斥量
- 訪問共享資源
- 對互斥量解鎖
如果多個線程試圖執行這一塊代碼(一個臨界區),事實上只有一個線程能夠持有該互斥量(其他線程將遭到阻塞),即同時只有一個線程能夠進入這段代碼區域,如下圖所示:
互斥量相關操作函數
互斥量的類型 pthread_mutex_t int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);- 初始化互斥量- 參數 :- mutex : 需要初始化的互斥量變量- attr : 互斥量相關的屬性,NULL- restrict : C語言的修飾符,被修飾的指針,不能由另外的一個指針進行操作。pthread_mutex_t *restrict mutex = xxx;pthread_mutex_t * mutex1 = mutex;-返回值:成功則返回0, 出錯則返回錯誤編號int pthread_mutex_destroy(pthread_mutex_t *mutex);- 釋放互斥量的資源int pthread_mutex_lock(pthread_mutex_t *mutex);- 加鎖,阻塞的,如果有一個線程加鎖了,那么其他的線程只能阻塞等待-返回值:成功則返回0, 出錯則返回錯誤編號int pthread_mutex_unlock(pthread_mutex_t *mutex);- 解鎖-返回值:成功則返回0, 出錯則返回錯誤編號int pthread_mutex_trylock(pthread_mutex_t *mutex);- 嘗試加鎖,如果加鎖失敗,不會阻塞,會直接返回。- 案例
死鎖
有時,一個線程需要同時訪問兩個或更多不同的共享資源,而每個資源又都由不同的互斥量管理。當超過一個線程加鎖同一組互斥量時,就有可能發生死鎖。
兩個或兩個以上的進程在執行過程中,因爭奪共享資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖。
死鎖的幾種場景:忘記釋放鎖、重復加鎖、多線程多鎖,搶占鎖資源。
讀寫鎖
當有一個線程已經持有互斥鎖時,互斥鎖將所有試圖進入臨界區的線程都阻塞住。但是考慮一種情形,當前持有互斥鎖的線程只是要讀訪問共享資源,而同時有其它幾個線程也想讀取這個共享資源,但是由于互斥鎖的排它性,所有其它線程都無法獲取鎖,也就無法讀訪問共享資源了,但是實際上多個線程同時讀訪問共享資源并不會導致問題。
在對數據的讀寫操作中,更多的是讀操作,寫操作較少,例如對數據庫數據的讀寫應用。
為了滿足當前能夠允許多個讀出,但只允許一個寫入的需求,線程提供了讀寫鎖來實現。
讀寫鎖的特點:
- 如果有其它線程讀數據,則允許其它線程執行讀操作,但不允許寫操作。
- 如果有其它線程寫數據,則其它線程都不允許讀、寫操作。
- 寫是獨占的,寫的優先級高。
讀寫鎖相關操作函數
讀寫鎖的類型 pthread_rwlock_t int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);- 案例
生產者和消費者模型
生產者消費者模型(粗略的版本) #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h>// 創建一個互斥量 pthread_mutex_t mutex;struct Node{int num;struct Node *next; };// 頭結點 struct Node * head = NULL;void * producer(void * arg) {// 不斷的創建新的節點,添加到鏈表中while(1) {pthread_mutex_lock(&mutex);struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));newNode->next = head;head = newNode;newNode->num = rand() % 1000;printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());pthread_mutex_unlock(&mutex);usleep(100);}return NULL; }void * customer(void * arg) {while(1) {pthread_mutex_lock(&mutex);// 保存頭結點的指針struct Node * tmp = head;// 判斷是否有數據if(head != NULL) {// 有數據head = head->next;printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());free(tmp);pthread_mutex_unlock(&mutex);usleep(100);} else {// 沒有數據pthread_mutex_unlock(&mutex);}}return NULL; }int main() {pthread_mutex_init(&mutex, NULL);// 創建5個生產者線程,和5個消費者線程pthread_t ptids[5], ctids[5];for(int i = 0; i < 5; i++) {pthread_create(&ptids[i], NULL, producer, NULL);pthread_create(&ctids[i], NULL, customer, NULL);}for(int i = 0; i < 5; i++) {pthread_detach(ptids[i]);pthread_detach(ctids[i]);}while(1) {sleep(10);}pthread_mutex_destroy(&mutex);pthread_exit(NULL);return 0; }信號量
信號量的類型 sem_t int sem_init(sem_t *sem, int pshared, unsigned int value);- 初始化信號量- 參數:- sem : 信號量變量的地址- pshared : 0 用在線程間 ,非0 用在進程間- value : 信號量中的值int sem_destroy(sem_t *sem);- 釋放資源int sem_wait(sem_t *sem);- 對信號量加鎖,調用一次對信號量的值-1,如果值為0,就阻塞int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); int sem_post(sem_t *sem);- 對信號量解鎖,調用一次對信號量的值+1int sem_getvalue(sem_t *sem, int *sval);sem_t psem; sem_t csem; init(psem, 0, 8); init(csem, 0, 0);producer() {sem_wait(&psem);sem_post(&csem) }customer() {sem_wait(&csem);sem_post(&psem) }- 案例
條件變量
互斥鎖有一個明顯到缺點: 只有兩種狀態,鎖定和非鎖定。
而條件變量則通過允許線程阻塞并等待另一個線程發送喚醒信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。
條件變量的類型 pthread_cond_t
等待,調用了該函數,線程會阻塞:
? int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
? int pthread_cond_destroy(pthread_cond_t *cond);
? 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);
喚醒一個或者多個等待的線程:
? int pthread_cond_signal(pthread_cond_t *cond);
喚醒所有的等待的線程:
? int pthread_cond_broadcast(pthread_cond_t *cond);
總結
- 上一篇: 基于osgEarth的空间态势三维场景视
- 下一篇: undefined运算