1. 程式人生 > >執行緒同步(條件變數)

執行緒同步(條件變數)

參考:https://blog.csdn.net/qq_37653144/article/details/81988615

https://blog.csdn.net/qq_34328833/article/details/56012780 

https://blog.csdn.net/lycommand/article/details/79652403

互斥鎖只有兩種狀態,他的用途相對來說比較有限。除了互斥鎖之外,還可以用條件變數解決執行緒同步的問題,條件變數是對互斥鎖的補充,它允許執行緒阻塞並且等待另一個執行緒傳送的訊號,當收到訊號之後,阻塞的執行緒就被喚醒並鎖定與之相關的互斥鎖。

條件變數是用來等待執行緒而不是上鎖的,條件變數通常和互斥鎖一起使用。條件變數之所以要和互斥鎖一起使用,主要是因為互斥鎖的一個明顯的特點就是它只有兩種狀態:鎖定和非鎖定,而條件變數可以通過允許執行緒阻塞和等待另一個執行緒傳送訊號來彌補互斥鎖的不足,所以互斥鎖和條件變數通常一起使用。

    當條件滿足的時候,執行緒通常解鎖並等待該條件發生變化,一旦另一個執行緒修改了環境變數,就會通知相應的環境變數喚醒一個或者多個被這個條件變數阻塞的執行緒。這些被喚醒的執行緒將重新上鎖,並測試條件是否滿足。一般來說條件變數被用於執行緒間的同步;當條件不滿足的時候,允許其中的一個執行流掛起和等待。

利用條件變數解決生產者消費者問題

//
//  main.cpp
//  條件變數解決執行緒同步
//
//  Created by 藍貓 on 2018/11/26.
//  Copyright © 2018年 藍貓. All rights reserved.
//生產者消費者問題

#include <iostream>
#include <stdio.h>
#include <queue>
#include <pthread.h>
#include <memory>
#include <stdlib.h>
#include <unistd.h>
#define Buffer_Size 4
#define Over -1
template<typename T>
class Producer//定義生產者條件變數結構
{
public:
    T buffer[Buffer_Size];//緩衝區
    pthread_mutex_t lock;//定義緩衝區互斥鎖
    int readpos;//讀取的位置 隊頭
    int writepos;//寫入位置 隊尾
    std::queue<T> q;
    pthread_cond_t notempty;//緩衝區又資料的標記
    pthread_cond_t notfull;//緩衝區沒有資料的時候標記
    Producer();
};
template<typename T>
Producer<T>::Producer()
{
    pthread_mutex_init(&lock, NULL);
    pthread_cond_init(&notfull, NULL);
    pthread_cond_init(&notempty, NULL);
    readpos=0;
    writepos=0;
}
Producer<int> b;
//std::shared_ptr<Producer> buffer=std::make_shared<Producer>();
//在緩衝區中存放資料
void put(Producer<int> &b,int data)
{
    pthread_mutex_lock(&b.lock);// 加鎖
    //如果佇列滿了  資料為空的訊號要等待 隊滿的條件是 隊尾加1就是隊頭
    //printf("隊尾%d\n",b.writepos);
    while ((b.writepos+1)%Buffer_Size==b.readpos)
    {
        
        pthread_cond_wait(&b.notfull, &b.lock);
    }
    b.buffer[b.writepos]=data;
    b.writepos++;
    printf("生產者生產了%d\n",data);
    if(b.writepos>=Buffer_Size)
    {
        b.writepos=0;
    }
    pthread_cond_signal(&b.notempty);//傳送現在資料不滿的訊號
    pthread_mutex_unlock(&b.lock);//解鎖
    
}
int get(Producer<int> &b)
{
    int data;
    pthread_mutex_lock(&b.lock);// 加鎖
    //如果佇列為空  資料不空的訊號要等待 隊滿的條件是
    //printf("隊頭%d\n",b.readpos);
    while (b.writepos==b.readpos)
    {
        pthread_cond_wait(&b.notempty, &b.lock);
    }
    data=b.buffer[b.readpos];
    printf("消費者消費了了%d\n",data);
    b.readpos++;
    if(b.readpos>=Buffer_Size)
    {
        //!!!!錯在這裡
        //b.writepos=0;
        b.readpos=0;
    }
    pthread_cond_signal(&b.notfull);//傳送現在資料不滿的訊號
    pthread_mutex_unlock(&b.lock);//解鎖
    return data;
}
void *producer(void *arg)
{
    for(int i=0;i<9;i++)
    {
        //std::cout<<"生產者:"<<i<<std::endl;
        //printf("生產者:%d\n",i);
        put(b, i);
    }
    put(b, Over);
    pthread_exit(NULL);
}
void *consumer(void *arg)
{
    sleep(1);
    int cons;
    while (cons!=Over)
    {
        cons=get(b);
       // std::cout<<"消費者:"<<cons<<std::endl;
        //printf("消費者:%d\n",cons);
    }
    pthread_exit(NULL);
}

int main(int argc, const char * argv[])
{
    pthread_t pro,cons;
    pthread_create(&pro, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(pro, NULL);
    pthread_join(cons, NULL);
    return 0;
}

兩個執行緒,生產者和消費者執行緒分別從緩衝區生產和消費資料,緩衝區儲存設定為4,因為是迴圈佇列,緩衝區要浪費一個儲存節點來判斷佇列滿,如果佇列滿,阻塞生產者執行緒,如果空,阻塞消費者執行緒。下圖,生產者生產0,1,2之後阻塞,但執行緒隨機執行,沒有體現佇列空的情況,但並不礙事。