c++ linux 线程等待与唤醒_Linux线程同步(互斥量、信号量、条件变量、生产消费者模型)...
為什么要線程同步?
- 線程間有很多共享資源,都對一個共享數據讀寫操作,線程操作共享資源的先后順序不確定,可能會造成數據的沖突
看一個例子
兩個線程屏行對全局變量count++ (采用一個val值作為中間變量,模擬寄存器工作方式,后面會詳解)
#include <stdlib.h> #include <pthread.h> #include <unistd.h>#define NLOOP 5000 //循環次數int count = 0;//全局資源void* func(void* p) {int i,val;for (i = 0; i < NLOOP; i++){val = count;printf("count = %dn",val+1);count = val + 1;usleep(100);//減緩線程執行速度,增加資源沖突概率}return NULL; }int main() {pthread_t tidA, tidB;pthread_create(&tidA, NULL, &func, NULL);//線程A 對count++pthread_create(&tidB, NULL, &func, NULL);//線程B 對count++sleep(1);pthread_join(tidA, NULL);pthread_join(tidB, NULL);return 0; }第一次執行結果
第二次執行結果
第三次執行結果
一段代碼執行三次出現不同的結果,這是為什么?就是因為兩個線程同時對共享資源進行操作,導致CPU處理共享資源出現錯誤
寄存器處理數據+1操作一般分為三步
- 從內存讀變量值到寄存器
- 寄存器的值加1
- 將寄存器的值寫回內存
假設變量值為5那么CPU執行線程A,將變量讀到寄存器中,寄存器的值+1(正在加,此時值還為5),同時線程B將變量從內存中讀走(值也為5),線程A將寄存器值寫回變量(此時值為6),之后線程B處理完寫回變量(此時值還是為6)
那么怎么解決這種情況發生?
一、互斥量
- 互斥量是pthread_mutex_t類型的變量。
- 互斥量有兩種狀態:lock(上鎖)、unlock(解鎖)
- 當對一個互斥量加鎖后,其他任何試圖訪問互斥量的線程都會被堵塞,直到當前線程釋放互斥鎖上的鎖。如果釋放互斥量上的鎖后,有多個堵塞線程,這些線程只能按一定的順序得到互斥量的訪問權限,完成對共享資源的訪問后,要對互斥量進行解鎖,否則其他線程將一直處于阻塞狀態。
簡單理解為:超市中的儲存柜,當有人使用儲存柜后,其他人使用不了,只能等待使用者使用完,再使用
互斥量操作原理
- 創建鎖可以通過直接定義鎖,或者調用初始化鎖函數,二選一
對之前的例子加上互斥量
#include <stdlib.h> #include <pthread.h> #include <unistd.h>#define NLOOP 5000 //循環次數int count = 0;//全局資源 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;//定義鎖void* func(void* p) {int i, val;for (i = 0; i < NLOOP; i++){pthread_mutex_lock(&counter_mutex);//上鎖val = count;printf("count = %dn", val + 1);count = val + 1;pthread_mutex_unlock(&counter_mutex);//解鎖usleep(100);//減緩線程執行速度,增加資源沖突概率}return NULL; }int main() {pthread_t tidA, tidB;pthread_create(&tidA, NULL, &func, NULL);//線程A 對count++pthread_create(&tidB, NULL, &func, NULL);//線程B 對count++sleep(1);pthread_join(tidA, NULL);pthread_join(tidB, NULL);return 0; }不管怎么執行,結果都是10000次
死鎖的情況
- 同一個線程已擁有A鎖的情況下,再次請求獲取A鎖,導致線程阻塞
解決方法:使用完資源后立刻解鎖 - 線程一擁有A鎖,再次請求獲取B鎖,同時線程二擁有B鎖,請求獲取A鎖,導致線程阻塞
解決方法:當擁有鎖的情況下,請求獲取另外一把鎖失敗時,釋放已擁有的鎖
c/c++Linux服務器開發高階知識點視頻學習資料的朋友加qun720209036獲取
二、條件變量
- 條件變量就是一個變量,用來自動阻塞一個線程,直到某特殊情況發生為止。
- 條件變量是用來等待事件
- 通常條件下變量和互斥鎖同時使用。
條件變量操作原語
#include <pthread.h>//全局定義條件變量 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;//初始化條件變量 //cond參數為條件變量指針,通過該函數實現條件變量賦初值;cond_attr參數通常為NULL int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); //銷毀條件變量 int pthread_cond_destroy(pthread_cond_t *cond); //自動釋放mutex鎖,等待條件滿足 //這個函數的過程我們必須了解,首先對互斥鎖進行解鎖;然后自身堵塞等待;當等待條件達成,注意這時候函數并未返回,而是重新獲得鎖并返回。 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//自動釋放mutex鎖,等待條件滿足,如果在abstime時間內還沒有滿足,則返回錯誤 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);//讓等待條件滿足的線程中某一個被喚醒 int pthread_cond_signal(pthread_cond_t *cond);//讓等待條件滿足的線程中全部被喚醒 (廣播) int pthread_cond_broadcast(pthread_cond_t *cond);生產者消費模型
流程首先消費者需要訪問共享資源首先要去拿到鎖訪問條件變量,條件變量說現在還沒有資源,所以消費者釋放鎖,阻塞等待,直到生產者生產出資源后,將資源先放到公共區后,再告訴條件變量,現在有資源了,然后條件變量再去喚醒阻塞線程,這些阻塞的線程被喚醒后需要去爭搶鎖,先拿到鎖的線程優先訪問共享資源
實例
#include <stdlib.h> #include <pthread.h> #include <stdio.h>typedef struct msg {struct msg *next;int num; }MSG_T;MSG_T *head;//消息頭結點pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//互斥鎖 //pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; //條件變量 本文通過init定義條件變量 pthread_cond_t mycond;//生產者 void *producer(void *p) {MSG_T* mp;for (;;) {mp = malloc( sizeof(MSG_T) );mp->num = rand() % 1000 + 1;//生成隨機數1到1000printf("Produce %dn", mp->num);//將資源放入公共區pthread_mutex_lock(&lock);mp->next = head;head = mp;pthread_mutex_unlock(&lock);//通知條件變量喚醒線程pthread_cond_signal(&mycond);sleep(rand() % 5);} }//消費者 void *consumer(void *p) {MSG_T* mp;for (;;) {pthread_mutex_lock(&lock);//上鎖while (head == NULL)//當沒有數據時,wait阻塞等待pthread_cond_wait(&mycond, &lock);//當沒有拿到鎖的時候 釋放鎖并等待mp = head;head = mp->next;pthread_mutex_unlock(&lock);//解鎖printf("Consume %dn", mp->num);free(mp);sleep(rand() % 5);} }int main(int argc, char *argv[]) {pthread_t pid, cid;if (pthread_cond_init(&mycond, NULL) != 0){printf("cond error'n");exit(1);}srand(time(NULL));//加入隨機因子pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);pthread_join(cid, NULL);return 0; }運行結果
三、信號量
- 信號量分為有名信號量和無名信號量,這里主要介紹無名信號量,用于線程同步,有名信號量一般是用于進程之間管理。
- 信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問,也被稱為PV原子操作
- P操作,即信號量sem減一的過程,如果sem小于等于0,P操作被堵塞,直到sem變量大于0為止。P操作及加鎖過程。
- V操作,即信號量sem加一的過程。V操作及解鎖過程。
簡單理解:假設現在有五把鑰匙,當有人拿走一把后(P操作),就剩4把鑰匙,直到鑰匙被拿完,后面的人需要等待,直到使用者將鑰匙歸還(V操作)
信號量操作原理
#include <semaphore.h>//初始化 //sem: 要進行初始化的信號量對象 //pshared:控制著信號量的類型,如果值為0,表示它是當前進程的局部信號量;否則,其他進程就能夠共享這個信號量 //value:賦給信號量對象的一個整數類型的初始值調用成功時 返回 0; int sem_init(sem_t *sem,int pshared,unsigned value);//p操作 -1 int sem_wait(sem_t *sem);//v操作 +1 int sem_post(sem_t *sem);//銷毀信號量 int sem_destory(sem_t *sem);信號量的生產消費者模型
#include <stdlib.h> #include <pthread.h> #include <stdio.h> #include <semaphore.h>#define NUM 5int queue[NUM];//定義一個環形隊列 sem_t blank_number, product_number;//定義兩個信號量//生產者 void *producer(void *arg) {int p = 0;while (1) {sem_wait(&blank_number);//信號量blank_number--,可以比喻成盤子(最多5個盤子)盤子數量-1,queue[p] = rand() % 1000 + 1;//產生隨機數printf("Produce %dn", queue[p]);sem_post(&product_number);//信號量product_number++ ,比喻成菜,現在菜的數量+1 消費者可以使用p = (p + 1) % NUM;//隊列下標偏移sleep(rand() % 5);} }//消費者 void *consumer(void *arg) {int c = 0;while (1) {sem_wait(&product_number);//信號量product_number--,現在菜的數量-1printf("Consume %dn", queue[c]);queue[c] = 0;//清空當前下標隊列sem_post(&blank_number);//信號量 blank_number++,資源使用完了 盤子數量+1c = (c + 1) % NUM;//隊列下標偏移sleep(rand() % 5);} }int main(int argc, char *argv[]) {pthread_t pid, cid;sem_init(&blank_number, 0, NUM);//值為5sem_init(&product_number, 0, 0);//初始值為0pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);pthread_join(cid, NULL);sem_destroy(&blank_number);sem_destroy(&product_number);return 0; }運行結果
原文鏈接:Linux線程同步(互斥量、信號量、條件變量、生產消費者模型)_趙小廚的博客-CSDN博客_互斥量條件變量信號量
總結
以上是生活随笔為你收集整理的c++ linux 线程等待与唤醒_Linux线程同步(互斥量、信号量、条件变量、生产消费者模型)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: narwal无法连接机器人_懒无止境 能
- 下一篇: 语言 提取列名_学习健明老师发布的R语言