pthread_cond_wait學習筆記


近期學習了線程等待和激活的相關知識。

先介紹幾個api:

pthread_cond_t表示多線程的條件變量,用於控制線程等待和就緒的條件。

一:條件變量的初始化:

條件變量和互斥鎖一樣,都有靜態動態兩種創建方式,

靜態方式使用PTHREAD_COND_INITIALIZER常量初始化。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

動態方式初始化:

1 首先要new或者malloc一個pthread_cond_t類型變量,

用完后記得delete或者free掉。

2

動態方式調用 pthread_cond_init()函數,API定義如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
 
二:條件變量的銷毀
 
注銷一個條件變量需要調用pthread_cond_destroy(),只有在沒有線程在該條件變量上等待的時候才能注銷這個條件變量,否則返回EBUSY。
因為Linux實現的條件變量沒有分配什么資源,所以注銷動作只包括檢查是否有等待線程。API定義如下:
int pthread_cond_destroy(pthread_cond_t *cond)
 
new開辟的pthread_cond_t記得在調用pthread_cond_destroy()后調用delete或者free銷毀掉。
 
三:等待和觸發
 
1條件等待
int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);
 
2時間等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
 
 其中計時等待方式如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,
其中abstime以與time() 系統調用相同意義的絕對時間形式出現,0表示 格林尼治時間1970年1月1日0時0分0秒。
 
無論哪種等待方式,都必須和一個 互斥鎖配合,以防止多個線程同時請求pthread_cond_wait()(或pthread_cond_timedwait(),下同)
競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)
或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本 線程加鎖(pthread_mutex_lock()),
而在更新條件 等待隊列以前,mutex保持鎖定狀態,並在線程掛起進入等待前解鎖。
在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。
 

使用pthread_cond_wait方式如下:

    pthread _mutex_lock(&mutex)

    while或if(線程執行的條件是否成立)

          pthread_cond_wait(&cond, &mutex);

    線程執行

    pthread_mutex_unlock(&mutex);

 
3
激發條件有兩種形式,pthread_cond_signal()激活一個等待該條件的線程,存在多個等待線程時按入隊順序激活其中一個;
而pthread_cond_broadcast()則激活所有等待線程。
 
 上面就是多線程條件變量的基礎知識,下面着重闡述下為什么調用pthread_cond_wait之前要加鎖,以及pthread_cond_wait內部
調用了什么。
首先解釋為什么在等待前加鎖,因為線程隸屬於進程,線程共享進程的資源,如果不進行加鎖,就會造成多個線程同時(相對意義的同時,
可能一個線程在函數A中更改此共享資源,此時函數A沒結束,另一個線程也訪問了這個共享資源)
訪問這塊共享的資源,如果對臨界區的內容進行更改,那么兩個線程就會造成數據的不准確。所以在更改臨界資源的時候要枷鎖。而調用
pthread_cond_wait之前要加鎖也是為了避免多個線程競爭一個條件,造成共享的資源被多個線程更改。所以需要互斥的訪問共有資源,
那么在pthread_cond_wait之前需要加鎖,避免別的線程更改共有資源。
接下來思考pthread_cond_wait內部做了哪些操作。
在pthread_cond_wait調用之前,線程調用pthread_mutex_lock,設置鎖,如果條件不滿足,那么該線程處於阻塞等待的狀態。別的線程
發現條件滿足后會調用pthread_cond_signal或pthread_cond_broadcast通知他。那么問題出來了,如果該線程不解鎖,別的線程是沒辦法
更改共享資源的,也就沒辦法設置條件變量使其滿足該線程的等待條件,出現死鎖。所以,pthread_cond_wait會在內部進行解鎖操作。別的
線程可以訪問共享資源,更改條件觸發該線程,是該線程從阻塞狀態變為就緒。慢一點,還有一個重要的步驟,pthread_cond_wait會將該線程
放到線程等待隊列里,那么是在放到等待隊列之前解鎖還是放到等待隊列之后才解鎖呢?

對於這點apue給出的解釋:The mutex passed to pthread_cond_wait protects the condition.The caller passes it locked to 

the function, which then atomically places the calling thread on the list of threads waiting for the condition and unlocks 

the mutex. This closes the window between the time that the condition is checked and the time that the

 thread goes to sleep waiting for the condition to change, so that the thread doesn't miss a change in the condition. 

When pthread_cond_wait returns, the mutex is again locked.

 這段話的意思是mutex傳遞給pthread_cond_wait 用於保護條件,調用者將mutex傳遞給pthread_cond_wait,
pthread_cond_wait 會自動將調用該函數的線程放到線程等待隊列上,等待條件並且解鎖。這種做法關閉了一段間隙,
這段間隙就是在我們檢測條件的時刻和將線程放到等待隊列休眠的時刻之間,這么做該線程不會錯過條件的改變。而當
pthread_cond_wait 返回時,mutex又被上鎖了。
所以,pthread_cond_wait內部的操作順序是將線程放到等待隊列,然后解鎖,等條件滿足時進行加鎖,然后返回
整理下pthread_cond_wait內部操作

1,線程放在等待隊列上,解鎖

2,等待 pthread_cond_signal或者pthread_cond_broadcast信號之后去競爭鎖

3,若競爭到互斥索則加鎖。

 

使用流程

等待線程:

pthread_mutex_lock(&mutex);

if(條件不滿足)

  pthread_cond_wait(&cond, &mutex);

//處理共享資源

pthread_mutex_unlock(&mutex);

 

激活線程:

pthread_mutex_lock(&mutex);

pthread_cond_signal(&cond);

pthread_mutex_unlock(&mutex);

 

下面寫了一個例子

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <iostream>
using namespace std;

int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 
//該函數增加count數值
void * creator(void * arg)
{
    cout << "creator add lock" << endl;
    pthread_mutex_lock(&mutex);

    count ++;

    cout << "in creator count is : " << count << endl;
    //條件滿足時發送信號
    if(count > 0)
    {

        pthread_cond_signal(&cond);
    }

    
    cout << "creator release lock" << endl;
    
    pthread_mutex_unlock(&mutex);

    return NULL;

}

//該函數減少count數值
void * consumer(void * arg)
{
    cout << "consumer add lock" << endl;

    pthread_mutex_lock(&mutex);
    //當條件不滿足時等待
    while(count <= 0) //防止虛假喚醒
    {
        cout << "begin wait" << endl;
        pthread_cond_wait(&cond,&mutex);
        cout << "end wait" << endl;
    }

    count --;

    cout << "in consumer count is " << count << endl;

    pthread_mutex_unlock(&mutex);

    cout << "consumer release lock" << endl;
    
    return NULL;
    
}


int main()
{
    //兩個線程,一個生產者線程一個消費者線程
    pthread_t createthread,consumethread;

     pthread_create(&consumethread, NULL, consumer, NULL);
   sleep(2); pthread_create(
&createthread, NULL, creator, NULL); //主進程等待兩個線程結束 pthread_join(createthread, NULL); pthread_join(consumethread, NULL); return 0; }
因為消費者線程先跑起來,會等待生產者增加count數量,所以打印輸出結果如下
 
下面將消費者和生產者線程增加幾個,creater和consumer內部用循環處理,
這樣就能看出效果了。
 
void * creator(void * arg)
{
    int i = 0;
    while(i<300)
    {

        i++;
        cout << "creator add lock" << endl;
        pthread_mutex_lock(&mutex);

        count ++;

        cout << "in creator count is : " << count << endl;

        if(count > 0)
        {

            pthread_cond_signal(&cond);
        }

    
        cout << "creator release lock" << endl;
    
        pthread_mutex_unlock(&mutex);

    }

    return NULL;

}





void * consumer(void * arg)
{
    int i = 0;
    while(i < 100)
    {
        
        i++;
        cout << "consumer add lock" << endl;

        pthread_mutex_lock(&mutex);

        while(count <= 0) //防止虛假喚醒
        {
            cout << "begin wait" << endl;
            pthread_cond_wait(&cond,&mutex);
            cout << "end wait" << endl;
        }

        count --;

        cout << "in consumer count is " << count << endl;

        pthread_mutex_unlock(&mutex);

        cout << "consumer release lock" << endl;
    }
    
    return NULL;
    
}


int main()
{
     pthread_t createthread[2],consumethread[3];

     for(int i = 0; i < 3; i++)
     {
        pthread_create(&consumethread[i], NULL, consumer, NULL);
     }
     
     for(int i = 0; i < 2; i++)
     {
        pthread_create(&createthread[i], NULL, creator, NULL);
         }
     
     for(int i = 0; i < 2; i++)
      {
        pthread_join(createthread[i], NULL);
         }

     for(int i = 0; i < 3; i++)
     {
         pthread_join(consumethread[i], NULL);
     }

    
    return 0;

}

 

截取一部分結果截圖,可以看出數字是連續變動的,而且

加鎖解鎖內數字才變動,說明我們對鎖和條件變量使用合理。

 
就總結到這里,下次根據這個學習libevent的文檔。
我的公眾號,關注下吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM