[多線程]環形緩沖區以及多線程條件同步


1、環形緩沖區(下面生產者消費者的例子使用)

  使用一段內存空間作為緩沖區,維護兩個指針,一是讀指針,指向緩沖空間的第一個可讀位置;二是寫指針,指向空間的第一個空位置。讀取一個數據后,讀指針+1,當指針位置超出緩沖區域則指向緩沖區域的頭位置(置0);寫入一個數據后,寫指針+1,當指針位置超出緩沖區域則指向緩沖區域的頭位置(置0);由於空間循環利用,故稱為環形緩沖區。

  空間滿和空都是讀指針位置等於寫指針位置。如何判斷空間已滿?空?方法1:廢棄一個緩沖空間不用,當寫指針+1等於讀指針的時候(意思是寫指針多跑一圈快要趕上讀指針),此時表明空間已滿。方法2:維護一個變量記錄緩沖區使用大小,當大小等於緩沖區大小,則為滿;當大小為0時為空。

  環形緩沖區多用於多線程緩沖。相對於隊列,它的優點是不必新增與釋放空間,所有空間重復利用,只需維護兩個指針。

2、多線程同步

初始化條件變量:pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

第一個參數為條件變量的地址,第二個參數設置為NULL表明默認參數設置,也可設為已初始化的條件變量指針,復制參數設置。返回0為初始化成功。

條件變量阻塞:pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

第一個參數設置為需要阻塞的條件變量;第二個參數為用於阻塞的互斥變量

建議使用方式:

pthread_mutex_lock();
    while(condition_is_false)
        pthread_cond_wait();
pthread_mutex_unlock();

一旦則該條件進入阻塞狀態,等待pthread_cond_broadcast (pthread_cond_t *cond)(喚醒全部,慎用) ;與pthread_cond_signal (pthread_cond_t *cond)(喚醒其中一個) ;喚醒條件,解除阻塞。

解除條件阻塞:pthread_cond_signal應在互斥保護下使用,否則可能會在檢測condition_is_false后與pthread_cond_wait前調用,解除沒阻塞的條件。成功返回0。

解除條件變量與當前狀態的關聯:pthread_cond_destroy(pthread_cond_t *cond);

生產者與消費者的例子:

#define BUFFER_SIZE 16 // 緩沖區數量

struct prodcons
{
    // 緩沖區相關數據結構
    int buffer[BUFFER_SIZE]; /* 實際數據存放的數組*/
    pthread_mutex_t lock; /* 互斥體lock 用於對緩沖區的互斥操作 */
    int readpos, writepos; /* 讀寫指針*/
    pthread_cond_t notempty; /* 緩沖區非空的條件變量 */
    pthread_cond_t notfull; /* 緩沖區未滿的條件變量 */
    int size;/*緩沖區當前大小*/
};
/* 初始化緩沖區結構 */
void init(struct prodcons *b)
{
    pthread_mutex_init(&b->lock, NULL);
    pthread_cond_init(&b->notempty, NULL);
    pthread_cond_init(&b->notfull, NULL);
    b->readpos = 0;
    b->writepos = 0;
    b->size=0;
}
/* 將產品放入緩沖區,這里是存入一個整數*/
void put(struct prodcons *b, int data)
{
    pthread_mutex_lock(&b->lock);
    /* 等待緩沖區未滿*/
    while(b->size==BUFFER_SIZE)//if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
    {
        pthread_cond_wait(&b->notfull, &b->lock);//進入等待后會釋放互斥,使其他線程進入互斥改變狀態
    }
    /* 寫數據,並移動指針 */
    b->buffer[b->writepos] = data;
    b->writepos++;
    if (b->writepos >= BUFFER_SIZE)
        b->writepos = 0;
    b->size++;
    /* 設置緩沖區非空的條件變量*/
    pthread_cond_signal(&b->notempty);
    pthread_mutex_unlock(&b->lock);
} 
/* 從緩沖區中取出整數*/
int get(struct prodcons *b)
{
    int data;
    pthread_mutex_lock(&b->lock);
    /* 等待緩沖區非空*/
    while(b->size==0)//if (b->writepos == b->readpos)//此時緩沖區為空
    {
        pthread_cond_wait(&b->notempty, &b->lock);
    }
    /* 讀數據,移動讀指針*/
    data = b->buffer[b->readpos];
    b->readpos++;
    if (b->readpos >= BUFFER_SIZE)
        b->readpos = 0;
    b->size--;
    /* 設置緩沖區未滿的條件變量*/
    pthread_cond_signal(&b->notfull);
    pthread_mutex_unlock(&b->lock);
    return data;
}

/* 測試:生產者線程將1 到10000 的整數送入緩沖區,消費者線
   程從緩沖區中獲取整數,兩者都打印信息*/
#define OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
    int n;
    for (n = 0; n < 10000; n++)
    {
        printf("%d --->\n", n);
        put(&buffer, n);
    } put(&buffer, OVER);
    return NULL;
}

void *consumer(void *data)
{
    int d;
    while (1)
    {
        d = get(&buffer);
        if (d == OVER)
            break;
        printf("--->%d \n", d);
    }
    return NULL;
}

int main(void)
{
    pthread_t th_a, th_b;
    void *retval;
    init(&buffer);
    /* 創建生產者和消費者線程*/
    pthread_create(&th_a, NULL, producer, 0);
    pthread_create(&th_b, NULL, consumer, 0);
    /* 等待兩個線程結束*/
    pthread_join(th_a, &retval);
    pthread_join(th_b, &retval);
    return 0;
}

 

 解釋一下pthread_cond_wait的工作流程

1、pthread_cond_wait必須與pthread_mutex_t協同使用。首先,pthread_mutex_lock(&b->lock);

2、程序查看了緩沖區中,發現緩沖區狀態不適合操作,即調用pthread_cond_wait。

3、調用pthread_cond_wait后,mutex的鎖馬上解除,以便其他線程進行操作,改變緩沖區。

4、mutex解鎖后,線程2得以進入改變緩沖區,改變后調用pthread_cond_signal(&b->notfull);喚醒阻塞的線程1.

5、被喚醒后,pthread_cond_wait()先對mutex上鎖,再返回。上鎖是為了與接下來對緩沖區操作后的解鎖相匹配。

 

最近做多線程數據傳輸程序時出現了“線程餓死”的現象,在這里說明一下:

  當一個線程釋放互斥后,CPU時間片還沒到時,他又重新進入互斥,使得另外一個生產者線程完全沒有機會得到CPU運行權,導致線程無法執行。解決的辦法是在釋放互斥鎖后添加usleep(1),強迫線程進入阻塞,交出CPU運行權,使其他的線程有機會運行

pthread_cond_wait總是與一個布爾判斷式相聯在一起,如果判斷式為真,則線程繼續執行,然而pthread_cond_wait返回時無法保證判斷式是真是假,因此需要重新判斷。

 

pthread_cond_wait與while循環結合使用來檢查判斷式:

之前在判斷size大小的時候使用了if,導致出現了死鎖。上網查了文獻,發現pthread_cond_wait應該使用while循環,因為解鎖的時候不能保證if的條件成立。就像有兩個生產者線程的條件下,一個生產者判斷BUFFER沒空位后被阻塞了,消費者取走BUFFER的貨物后喚醒了這個生產者,但此時,有可能此生產者的時間片用完了,CPU切換到第二個生產者去,第二個生產者又把BUFFER填滿了。如果第一個生產者使用的是if,被喚醒后沒有再判斷size,就直接到已滿的BUFFER上操作造成錯誤了。
   When using condition variables there is always a Boolean predicate involving shared variables associated with 
   each condtion wait that is ture if the thread should proceed...
   Since the return does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return.

強烈建議pthread_cond_wait與while循環結合使用來檢查判斷式

   It is thus recommened that a condition wait be enclosed in the equivalent of a "while loop" that checks the predicate.


免責聲明!

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



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