Linux中多線程使用條件變量阻塞線程,和pthread_cond_wait()函數使用


簡 述: 繼續上一篇中,講解了原子⚛操作(粗略看作 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


條件變量是鎖🔐嗎?

  • 件變量不是鎖, 但是條件變量能夠阻塞線程
  • 使用條件變量 + 互斥量
    • 互斥量:保護一塊共享數據
    • 條件變量:引起阻塞
      • 生產者和消費者模型

條件變量的兩個動作?

  • 當條件不滿足,阻塞線程
  • 當條件滿足;通知阻塞的線程開始工作

使用條件變量流程:

條件變量通常是結合和互斥量一起使用。 其使用方式,和互斥量使用很是相似,包括下一篇準備寫的信號量(信號燈) ,的使用方式也是和互斥量很是相似。

  1. 初始化條件變量

    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
    
  2. 阻塞條件變量

    //阻塞一個條件變量
    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() 函數,還有幹了其他事情:

    • 阻塞線程
    • 將已經上鎖的互斥鎖解鎖
    • 接解除阻塞之後,對互斥鎖進行加鎖操作
  3. 喚醒阻塞在條件變量上的線程

    //喚醒至少一個阻塞在條件變量上的線程
    int pthread_cond_signal(pthread_cond_t *cond);
    
    //喚醒全部阻塞在條件變量上的線程
    int pthread_cond_broadcast(pthread_cond_t *cond);
    
  4. 銷燬一個條件變量

    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;
}

代碼分析:

多看一下理論模型的那個僞代碼的流程圖,生產者生產完一個之後,要通知(此時處於)阻塞的線程,給它一個信號,讓他解除阻塞。


運行結果:


下載地址:

20_conditton

歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章