多線程編程介紹-條件變量

多線程編程介紹-條件變量

條件變量定義

條件變量是多線程對共享資源數據的變化的通知機制。條件變量與互斥量明顯不一樣爲互斥量是對臨界資源的保護機制,但條件變量能夠理解爲一種通訊機制。html

條件變量的應用場景

設想以下編程場景,咱們要實現一個消息接收轉發並處理的流程,爲了提升程序執行效率。咱們啓動兩個線程一個是接收消息線程,專門負責接收消息,將消息加入到一個共享鏈表中;而一個線程是工做線程,專門負責等待讀取鏈表中的消息,若是鏈表爲空,則工做線程則進入等待隊列,若是有節點插入則工做線程須要被喚醒繼續工做。編程

  • 問題1:共享資源消息鏈表須要保護,怎麼作?多線程

    互斥量是解決共享資源的典型方法,相信你們都知道,使用mutex鎖保護一下對鏈表的操做就行了。對於鏈表操做的代碼段咱們成爲臨界資源。函數

  • 問題2:如何通知工做線程有消息到達?操作系統

    方法1:你們應該都能想到,利用現有知識互斥量,工做線程一直輪詢去檢查鏈表中是否有消息能夠處理,不就能夠了,但這樣顯然效率過低,浪費cpu資源。線程

    方法2:當有資源時接收消息線程通知工做線程一下不就能夠了,其餘時間工做線程就休眠就好啦。是的,條件變量就是爲了達到這個目的的。當工做線程檢查沒有消息處理時,就主動將本身掛起,等待接收消息線程喚醒。rest

條件變量功能函數介紹

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • pthread_cond_init初始化一個條件變量,須要在其餘條件變量執行函數以前執行。code

  • 只能被初始化一次,若是初始化兩次則結果不可預期。htm

  • restrict cond 是一個條件變量地址,使用此函數前須要定義一個條件變量結構體pthread_cond_tblog

  • 若是須要copy條件變量,只能copy條件變量地址,不能複製這個結構體,不然結果將不可預知。(想一想也知道這個條件變量是一個結構體,若是複製一個結構體則信息被複制兩份在多線程中必定會出現問題,而複製地址則原內容不變)

  • 第二個字段屬性字段,通常默認填NULL(其餘高級用法待研究,若是有知道的小夥伴還望分享一下啊~)

    int pthread_cond_destroy(pthread_cond_t *cond);
  • 釋放一個條件變量,通常值只在線程結束時調用,用於回收條件變量資源。

    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
    const struct timespec *restrict abstime);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 這兩個函數用於主動將線程阻塞,等待其餘線程喚醒。對應場景中工做線程判斷沒有能夠獲取的消息時將線程主動放入阻塞隊列中(這裏的阻塞隊列指操做系統中的),即線程將本身掛起。

  • 函數中前兩個參數一個是條件變量自己,一個是mutex互斥量。條件變量沒什麼好說的,由於這組函數就是和他相關的,那mutex呢?mutex是用來保護臨界資源使用的mutex,如場景中所說的對於消息隊列的訪問是須要加鎖保護的。之因此要出入mutex是由於線程在阻塞掛起本身以前要先釋放鎖,否則其餘線程也不能獲取鎖了。

  • 根據上一點分析mutex不但要做爲入參傳入,並且須要在傳入以前先得到鎖。

  • 固然pthread_cond_timedwait函數的最後一個參數能夠指定阻塞的時間,即即便在指定的時間沒沒有線程喚醒這個阻塞線程,阻塞線程也會本身被喚醒執行。

    int pthread_cond_broadcast(pthread_cond_t *cond);
    int pthread_cond_signal(pthread_cond_t *cond);
  • 以上這兩個函數是用來向等待此條件變量的線程發送信號,表示能夠繼續運行了。對應咱們的場景就是接收線程有消息到達,以後將消息插入隊列,通知工做線程。

  • 兩個函數不一樣之處,pthread_cond_broadcast喚醒等待中的全部線程。pthread_cond_signal至少喚醒一個等待線程。

  • 若是執行這兩個函數的時候沒有任何等待此條件變量的線程,則無任何影響,也無任何變化。這一點在後續問題介紹時會再次提到。

條件變量編程實例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

#define MSG_LEN_MAX        31

typedef struct tagNode
{
    char szMsg[MSG_LEN_MAX+1];
    struct tagNode *pstNextNode;
}NODE_S;

typedef struct tagList
{
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    NODE_S stHead;
}LIST_S;

LIST_S g_stMsgList = {};

void list_Init(LIST_S *pstList)
{
    pstList->stHead.pstNextNode = NULL;
    pthread_mutex_init(&pstList->mutex, NULL);
    pthread_cond_init(&pstList->cond, NULL);
}
/*工做線程用於處理並打印消息*/
void * dealMsgThread(void * pVoid)
{
    NODE_S *pstNode = NULL;
    int count = 0;
    
    while (1)
    {
        pthread_mutex_lock(&g_stMsgList.mutex);        
        
        /* 查找鏈表中的節點的頭節點返回 */
        if ( NULL == g_stMsgList.stHead.pstNextNode )
        {
            printf("can not find msg,waiting...\n");
            pthread_cond_wait(&g_stMsgList.cond, &g_stMsgList.mutex);
            printf("notify msg, wake up\n");
        }

        if ( NULL != g_stMsgList.stHead.pstNextNode )
        {
            pstNode = g_stMsgList.stHead.pstNextNode;
            g_stMsgList.stHead.pstNextNode = pstNode->pstNextNode;
            printf("echo: %s\n", pstNode->szMsg);
            free(pstNode);
            count++;
        }

        pthread_mutex_unlock(&g_stMsgList.mutex);
        if ( count >= 10 )
        {
            break;
        }
    }

    return NULL;
}
/*向消息隊列中放入節點,並通知等待的工線程*/
void receiveMsg(char *pcMsg, int iLen)
{
    NODE_S *pstNode = NULL;
    NODE_S* pstTemp = NULL;
    
    if ( iLen > MSG_LEN_MAX )
    {
        iLen = MSG_LEN_MAX;
    }

    pstNode = (NODE_S*)malloc(sizeof(NODE_S));
    memset(pstNode, 0, sizeof(NODE_S));
    snprintf(pstNode->szMsg, MSG_LEN_MAX+1, "%s", pcMsg);

    /* 獲取鎖 */
    pthread_mutex_lock(&g_stMsgList.mutex);

    pstTemp = &g_stMsgList.stHead;
    while ( pstTemp )
    {
        if ( NULL == pstTemp->pstNextNode )
        {
            break;
        }

        pstTemp = pstTemp->pstNextNode;
    }

    pstTemp->pstNextNode = pstNode;
    
    printf("recieve msg %s add list, send signal\n", pcMsg);
    /* 發送通知到工做線程 */
    pthread_cond_signal(&g_stMsgList.cond);

    pthread_mutex_unlock(&g_stMsgList.mutex);

    return;
}
/*main函數*/
int main(int argc, char **argv)
{
    pthread_t iThreadId;
    void *ret = NULL;
    char szMsg[MSG_LEN_MAX+1];
    
    list_Init(&g_stMsgList);
    
    pthread_create(&iThreadId, NULL, dealMsgThread, NULL);
    //sleep(1);
    for(int i =0 ; i < 10; i++)
    {
        sprintf(szMsg, "%d : hello", i);
        receiveMsg(szMsg, strlen(szMsg));
    }
    
    pthread_join(iThreadId, &ret);

    return 0;
}
  • 輸出結果:

    • recieve msg 0 : hello add list, send signal
      recieve msg 1 : hello add list, send signal
      recieve msg 2 : hello add list, send signal
      recieve msg 3 : hello add list, send signal
      recieve msg 4 : hello add list, send signal
      recieve msg 5 : hello add list, send signal
      recieve msg 6 : hello add list, send signal
      recieve msg 7 : hello add list, send signal
      echo: 0 : hello
      echo: 1 : hello
      echo: 2 : hello
      echo: 3 : hello
      echo: 4 : hello
      echo: 5 : hello
      echo: 6 : hello
      echo: 7 : hello
      can not find msg,waiting...
      recieve msg 8 : hello add list, send signal
      recieve msg 9 : hello add list, send signal
      notify msg, wake up
      echo: 8 : hello
      echo: 9 : hello

條件變量函數內部實現猜測

1.條件變量內部猜測一:條件變量pthread_cond_timedwait函數內部對於條件變量自己還存在一個鎖。

  • 這個鎖的用途就是保證條件變量掛起和釋放鎖是一個原子操做。

  • 設想場景,若是進入pthread_cond_timedwait函數以後,先釋放mutex(必須執行釋放操做,前文提到),以後再進入等待掛起以前,cpu切換到執行pthread_cond_signal線程,此時因爲沒有等待線程,從而不會有任何影響,這就致使後續進入wait態的工做線程永遠等不到被接收線程喚醒。因此爲保證釋放和進入wait態不能被打斷,因此須要加鎖保護。

  • 固然不僅是猜測,經過一篇內核態對於pthread_cond_timedwait函數的分析也證明了這一點。連接:https://www.cnblogs.com/c-slm...

條件變量使用注意事項

  • 條件變量使用前初始化,且初始化一次。

  • 條件變量使用pthread_cond_timedwait類函數時須要在獲取mutex鎖和釋放mutex鎖之間。

  • 使用pthread_cond_timedwait函數被喚醒後仍然須要判斷隊列中的狀態,由於可能被其餘線程首先搶佔了mutex並處理了消息隊列消息。即等待的條件變量失效,須要從新等待。

  • 條件變量使用pthread_cond_signal類函數時能夠在獲取mutex鎖和釋放mutex鎖之間,也能夠在獲取mutex鎖和釋放mutex鎖以後。但建議在獲取mutex鎖和釋放mutex鎖之間。

    • pthread_cond_signal函數在mutex lock與unlock之間執行。

      • 缺點:可能致使pthread_cond_wait線程執行後從新進入休眠,由於wait線程須要獲取mutex鎖,但此時signal線程可能並無釋放,致使頻繁的cpu切換。

    • pthread_cond_signal函數在mutex lock,unlock以後執行。

      • 缺點:先unlock操做以後此時低優先級任務可能會佔用cpu資源致使wait的高優先級任務得不到調度。由於wait的函數尚未收到signal信號喚醒。

相關文章
相關標籤/搜索