目錄
信號量
信號量相關接口
創建信號量
初始化信號量?
等待信號量,P操作
發布信號量,V操作?
銷毀信號量
基于信號量的環形隊列下的生產者和消費者模型?
環形隊列
代碼實現
上期我們學習了線程同步的概念,掌握了基于阻塞隊列的生產者和消費者模型。基于阻塞隊列的生產者和消費者模型,三種關系,兩個角色,一個場所,現在我們要關注的就是這一個場所,在此模型中,我們把阻塞隊列稱為臨界資源,但是基于此模型,有且僅有這一個臨界資源,也就是說臨界資源的數目只有這一個。但是很多場景下臨界資源的數目并不只有一個,本期我們主要研究的就是臨界資源不只有一個的場景下的生產者和消費者模型。
信號量
我們之前學習了臨界資源的概念,臨界資源其實就是可以被多個執行流訪問的資源。上節課的生產者和消費者模型,只有超市這一個臨界資源。還有一個場景,就是電影院這個場景,在電影中,電影院的座位可以被多個顧客使用,所以電影院的座位就是臨界資源,且電影院的座位不單單只有一個,所以臨界資源是有多個的。那么信號量是什么呢?
信號量用于描述臨界資源的數目的大小,比如在電影院的模型下,信號量就可以表示座位的多少。?任何線程要訪問臨界資源,必須先申請信號量,訪問完臨界資源,必須釋放信號量。申請信號量我們稱為P操作,釋放信號量我們稱為V操作。
通過偽代碼為大家講述p操作和v操作。?
當然并不是所有臨界資源都可以用信號量來表示其數目的多少。當一個臨界資源可以被細分時,我們才可以用臨界資源來表示臨界資源的數目的多少,當一個臨界資源被分成了多個小的臨界資源時,此時就可以實現多個執行流共同訪問臨界資源,從而實現,多個執行流的并發,從而提高代碼的執行效率。
信號量相關接口
信號量的接口與互斥鎖接口和條件變量接口類似。
創建信號量
sem_t? sem;? ? ? ?//創建信號量?
初始化信號量?
int sem_init(sem_t *sem, int pshared, unsigned int value);? ? ? ? ? //初始化信號量
sem為創建的信號量的地址;pshared,0表示線程間共享,非0表示進程間共享;value表示信號量的初始值。
等待信號量,P操作
int sem_wait(sem_t *sem);? ? ? ? ? ?//P()
等待信號量,表示申請信號量,要使用資源。將信號量減1。
發布信號量,V操作?
sem_post(sem_t *sem);? ? ? ? ? //V()
發布信號量,表示資源使用完畢,可以歸還資源了。將信號量值加1。
銷毀信號量
int sem_destroy(sem_t *sem);? ? ? ? ? ?//銷毀信號量??
基于信號量的環形隊列下的生產者和消費者模型?
環形隊列
上圖為一個環形隊列,有生產者和消費者兩個角色,環形隊列為一個大的臨界資源,這個大的臨界資源有被分成了多個小部分,為多份小的臨界資源。?
代碼實現
生產者往環形隊列中生產數據,消費者從環形隊列中消費數據。生產者每生產一個數據前進一格,當生產者和消費者相遇時,要么是環形隊列為空,要么是環形隊列中的數據已滿。基于此,我們對基于環形隊列的生產者和消費者模型設立兩個規則。
規則一:生產者不能把消費者逃一圈,因為一旦套了一圈,勢必會造成相遇時,生產者和消費者所處的環形隊列的那一塊空間中數據的覆蓋,造成數據丟失。
規則二:因為剛開始環形隊列是為空的,所以剛開始一定是生產者先生產,然后再是消費者再進行消費,正是基于此,往后消費者的位置一定是不能超過生產者的。
生產者看重的是環形隊列中的空白位置的數目,而消費者看重的是環形隊列中數據的數目。所以創建兩個信號量,一個信號量描述環形隊列中空位置的數目,一個信號量描述環形隊列中數據的數目。
代碼實現如下。
?RingQueue.hpp
#include <iostream>
#include <vector>
#include <semaphore.h>namespace yjd
{const int DefaultCapacity = 10;template <class T>class RingQueue{public:RingQueue(const int &capacity = DefaultCapacity): _v(capacity){_capacity = capacity;sem_init(&_blank_sem, 0, 10);sem_init(&_data_sem, 0, 0);_c_step = _p_step = 0;}~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}void push(T &data){sem_wait(&_blank_sem);_v[_p_step] = data;sem_post(&_data_sem);_p_step++;_p_step % _capacity;}void pop(T *data){sem_wait(&_data_sem);*data = _v[_c_step];sem_post(&_blank_sem);_c_step++;_c_step%_capacity;}private:std::vector<T> _v;int _capacity;// 描述空位置的數目sem_t _blank_sem;// 描述數據的數目sem_t _data_sem;int _p_step;int _c_step;};
}
整個代碼的邏輯為,生產者先生產數據,然后消費者消費數據。生產者要生產數據,先申請一個空位置信號量,空位置信號量減1,然后生產數據,生產完數據之后,數據信號量加1;消費者消費數據時,先申請數據信號量,數據信號量減1,消費完數據之后,多了一個空位置,空位置信號量加1。
?RingCP.cc
#include "RingQueue.hpp"
#include <pthread.h>
#include <unistd.h>
#include <time.h>using namespace yjd;void *producter(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (true){int data = rand() % 20 + 1;rq->push(data);std::cout << "生產者生產數據 " << data << std::endl;}
}void *consumer(void *args)
{RingQueue<int> *rq = (RingQueue<int> *)args;while (true){int data = 0;rq->pop(&data);std::cout << "消費者消費數據 " << data << std::endl;sleep(1);}
}int main()
{pthread_t c;pthread_t p;RingQueue<int> *rq = new RingQueue<int>();srand((long long)time(nullptr));pthread_create(&c, nullptr, consumer, (void *)rq);pthread_create(&p, nullptr, producter, (void *)rq);pthread_join(c, nullptr);pthread_join(p, nullptr);return 0;
}
運行結果如下。
通過運行結果可知,我們通過使用信號量和喚醒隊列,實現了生產和消費者的同步。??
以上便是本期信號量的相關知識點。
本期內容到此結束 ^_^