簡 述: 繼續上一篇中,講解了原子⚛操作(粗略看作 cpu 會執行完該幾行代碼,纔會切換到其他的線程)和重點講解讀寫鎖 的使用。本篇講解 條件變量 的使用步驟:
pthread_cond_t g_cond()
//條件變量–阻塞線程,等待條件滿足*pthread_cond_init()
//初始化pthread_cond_wait()
/pthread_cond_timedwait()
//阻塞線程(若是條件不滿足)- …其他代碼
pthread_cond_signal()
/pthread_cond_timedwait()
//通知阻塞中的線程解除阻塞pthread_cond_destroy()
//銷燬
編程環境:
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
條件變量是鎖🔐嗎?
- 條件變量不是鎖, 但是條件變量能夠阻塞線程
- 使用條件變量 + 互斥量
- 互斥量:保護一塊共享數據
- 條件變量:引起阻塞
- 生產者和消費者模型
條件變量的兩個動作?
- 當條件不滿足,阻塞線程
- 當條件滿足;通知阻塞的線程開始工作
使用條件變量流程:
條件變量通常是結合和互斥量一起使用。 其使用方式,和互斥量使用很是相似,包括下一篇準備寫的信號量(信號燈) ,的使用方式也是和互斥量很是相似。
-
初始化條件變量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
-
阻塞條件變量
//阻塞一個條件變量 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); //注意:另一個參數是互斥量參數 //限時阻塞一個條件變量 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
pthread_cond_wait() 函數,還有幹了其他事情:
- 阻塞線程
- 將已經上鎖的互斥鎖解鎖
- 接解除阻塞之後,對互斥鎖進行加鎖操作
-
喚醒阻塞在條件變量上的線程
//喚醒至少一個阻塞在條件變量上的線程 int pthread_cond_signal(pthread_cond_t *cond); //喚醒全部阻塞在條件變量上的線程 int pthread_cond_broadcast(pthread_cond_t *cond);
-
銷燬一個條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
生產者-消費者模型:
對於 “條件變量 + 互斥量” 的使用組合,有一個很是景點的例子 “生產者-消費者模型”。
理論模型:
代碼實現:
將上面的理論模型的僞代碼,用實際的完整代碼實現如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef struct node //節點結構
{
int data;
struct node* next;
} Node;
Node* g_head = nullptr; //永遠指向鏈表頭部的指針
pthread_mutex_t g_mutex; //互斥鎖--線程同步
pthread_cond_t g_cond; //條件變量--阻塞線程,等待條件滿足
void* funProducer(void* arg); //生產者--添加一個頭結點
void* funCustomer(void* arg); //消費者--刪除一個頭結點
int main(int argc, char *argv[])
{
pthread_t p1;
pthread_t p2;
pthread_mutex_init(&g_mutex, nullptr); //初始化互斥鎖
pthread_cond_init(&g_cond, nullptr); //初始化條件變量
pthread_create(&p1, nullptr, funProducer, nullptr); //創建生產者線程
pthread_create(&p2, nullptr, funCustomer, nullptr); //創建消費者線程
pthread_join(p1, nullptr); //阻塞回收子線程
pthread_join(p2, nullptr);
pthread_mutex_destroy(&g_mutex); //配套銷燬互斥鎖
pthread_cond_destroy(&g_cond); //配套銷燬條件變量
return 0;
}
void* funProducer(void* arg)
{
while (true) {
Node* pNew = new Node();
// Node* pNew = (Node *)malloc(sizeof(Node));
pNew->data = rand() % 1000;
pthread_mutex_lock(&g_mutex); //加鎖
pNew->next = g_head;
g_head = pNew;
printf("-----funProducer(生產者): %lu, %d\n", pthread_self(), pNew->data);
pthread_mutex_unlock(&g_mutex); //解鎖
pthread_cond_signal(&g_cond); //通知阻塞的消費者線程,解除阻塞
sleep(rand() % 3); //隨機休息 0~2 s
}
return nullptr;
}
void* funCustomer(void* arg)
{
while (true) {
pthread_mutex_lock(&g_mutex); //加鎖
if (g_head == nullptr) { //若是沒有,則等待生產者生產出來,在此阻塞,等待消費
pthread_cond_wait(&g_cond, &g_mutex); //阻塞線程,且該函數會對互斥鎖解鎖;且接解除阻塞之後,對互斥鎖進行加鎖操作
}
Node* pDel = g_head;
g_head = g_head->next;
printf("-----funCustomer(消費者): %lu, %d\n", pthread_self(), pDel->data);
delete pDel;
// free(pDel);
pthread_mutex_unlock(&g_mutex); //解鎖
}
return nullptr;
}
代碼分析:
多看一下理論模型的那個僞代碼的流程圖,生產者生產完一個之後,要通知(此時處於)阻塞的線程,給它一個信號,讓他解除阻塞。
運行結果:
下載地址:
歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。