Linux信号量
文章目錄
- POSIX信號(hào)量
- 信號(hào)量的原理
- 信號(hào)量的概念
- 信號(hào)量函數(shù)
- 二元信號(hào)量模擬實(shí)現(xiàn)互斥功能
- 基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
- 空間資源和數(shù)據(jù)資源
- 生產(chǎn)者和消費(fèi)者申請(qǐng)和釋放資源
- 必須遵守的兩個(gè)規(guī)則
- 代碼實(shí)現(xiàn)
- 信號(hào)量保護(hù)環(huán)形隊(duì)列的原理
POSIX信號(hào)量
信號(hào)量的原理
- 我們將可能會(huì)被多個(gè)執(zhí)行流同時(shí)訪問的資源叫做臨界資源,臨界資源需要進(jìn)行保護(hù)否則會(huì)出現(xiàn)數(shù)據(jù)不一致等問題。
- 當(dāng)我們僅用一個(gè)互斥鎖對(duì)臨界資源進(jìn)行保護(hù)時(shí),相當(dāng)于我們將這塊臨界資源看作一個(gè)整體,同一時(shí)刻只允許一個(gè)執(zhí)行流對(duì)這塊臨界資源進(jìn)行訪問。
- 但實(shí)際我們可以將這塊臨界資源再分割為多個(gè)區(qū)域,當(dāng)多個(gè)執(zhí)行流需要訪問臨界資源時(shí),如果這些執(zhí)行流訪問的是臨界資源的不同區(qū)域,那么我們可以讓這些執(zhí)行流同時(shí)訪問臨界資源的不同區(qū)域,此時(shí)不會(huì)出現(xiàn)數(shù)據(jù)不一致等問題。
信號(hào)量的概念
信號(hào)量(信號(hào)燈)本質(zhì)是一個(gè)計(jì)數(shù)器,是描述臨界資源中資源數(shù)目的計(jì)數(shù)器,信號(hào)量能夠更細(xì)粒度的對(duì)臨界資源進(jìn)行管理。
每個(gè)執(zhí)行流在進(jìn)入臨界區(qū)之前都應(yīng)該先申請(qǐng)信號(hào)量,申請(qǐng)成功就有了操作特點(diǎn)的臨界資源的權(quán)限,當(dāng)操作完畢后就應(yīng)該釋放信號(hào)量。
信號(hào)量的PV操作:
- P操作:我們將申請(qǐng)信號(hào)量稱為P操作,申請(qǐng)信號(hào)量的本質(zhì)就是申請(qǐng)獲得臨界資源中某塊資源的使用權(quán)限,當(dāng)申請(qǐng)成功時(shí)臨界資源中資源的數(shù)目應(yīng)該減一,因此P操作的本質(zhì)就是讓計(jì)數(shù)器減一。
- V操作:我們將釋放信號(hào)量稱為V操作,釋放信號(hào)量的本質(zhì)就是歸還臨界資源中某塊資源的使用權(quán)限,當(dāng)釋放成功時(shí)臨界資源中資源的數(shù)目就應(yīng)該加一,因此V操作的本質(zhì)就是讓計(jì)數(shù)器加一。
PV操作必須是原子操作
多個(gè)執(zhí)行流為了訪問臨界資源會(huì)競爭式的申請(qǐng)信號(hào)量,因此信號(hào)量是會(huì)被多個(gè)執(zhí)行流同時(shí)訪問的,也就是說信號(hào)量本質(zhì)也是臨界資源。
但信號(hào)量本質(zhì)就是用于保護(hù)臨界資源的,我們不可能再用信號(hào)量去保護(hù)信號(hào)量,所以信號(hào)量的PV操作必須是原子操作。
注意: 內(nèi)存當(dāng)中變量的++、--操作并不是原子操作,因此信號(hào)量不可能只是簡單的對(duì)一個(gè)全局變量進(jìn)行++、--操作。
申請(qǐng)信號(hào)量失敗被掛起等待
當(dāng)執(zhí)行流在申請(qǐng)信號(hào)量時(shí),可能此時(shí)信號(hào)量的值為0,也就是說信號(hào)量描述的臨界資源已經(jīng)全部被申請(qǐng)了,此時(shí)該執(zhí)行流就應(yīng)該在該信號(hào)量的等待隊(duì)列當(dāng)中進(jìn)行等待,直到有信號(hào)量被釋放時(shí)再被喚醒。
注意: 信號(hào)量的本質(zhì)是計(jì)數(shù)器,但不意味著只有計(jì)數(shù)器,信號(hào)量還包括一個(gè)等待隊(duì)列。
信號(hào)量函數(shù)
初始化信號(hào)量
初始化信號(hào)量的函數(shù)叫做sem_init,該函數(shù)的函數(shù)原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);參數(shù)說明:
- sem:需要初始化的信號(hào)量。
- pshared:傳入0值表示線程間共享,傳入非零值表示進(jìn)程間共享。
- value:信號(hào)量的初始值(計(jì)數(shù)器的初始值)。
返回值說明:
- 初始化信號(hào)量成功返回0,失敗返回-1。
注意: POSIX信號(hào)量和System V信號(hào)量作用相同,都是用于同步操作,達(dá)到無沖突的訪問共享資源目的,但POSIX信號(hào)量可以用于線程間同步。
銷毀信號(hào)量
銷毀信號(hào)量的函數(shù)叫做sem_destroy,該函數(shù)的函數(shù)原型如下:
int sem_destroy(sem_t *sem);參數(shù)說明:
- sem:需要銷毀的信號(hào)量。
返回值說明:
- 銷毀信號(hào)量成功返回0,失敗返回-1。
等待信號(hào)量(申請(qǐng)信號(hào)量)
等待信號(hào)量的函數(shù)叫做sem_wait,該函數(shù)的函數(shù)原型如下:
int sem_wait(sem_t *sem);參數(shù)說明:
- sem:需要等待的信號(hào)量。
返回值說明:
- 等待信號(hào)量成功返回0,信號(hào)量的值減一。
- 等待信號(hào)量失敗返回-1,信號(hào)量的值保持不變。
發(fā)布信號(hào)量(釋放信號(hào)量)
發(fā)布信號(hào)量的函數(shù)叫做sem_post,該函數(shù)的函數(shù)原型如下:
int sem_post(sem_t *sem);參數(shù)說明:
- sem:需要發(fā)布的信號(hào)量。
返回值說明:
- 發(fā)布信號(hào)量成功返回0,信號(hào)量的值加一。
- 發(fā)布信號(hào)量失敗返回-1,信號(hào)量的值保持不變。
二元信號(hào)量模擬實(shí)現(xiàn)互斥功能
信號(hào)量本質(zhì)是一個(gè)計(jì)數(shù)器,如果將信號(hào)量的初始值設(shè)置為1,那么此時(shí)該信號(hào)量叫做二元信號(hào)量。
信號(hào)量的初始值為1,說明信號(hào)量所描述的臨界資源只有一份,此時(shí)信號(hào)量的作用基本等價(jià)于互斥鎖。
例如,下面我們實(shí)現(xiàn)一個(gè)多線程搶票系統(tǒng),其中我們用二元信號(hào)量模擬實(shí)現(xiàn)多線程互斥。
我們?cè)谥骶€程當(dāng)中創(chuàng)建四個(gè)新線程,讓這四個(gè)新線程執(zhí)行搶票邏輯,并且每次搶完票后打印輸出此時(shí)剩余的票數(shù),其中我們用全局變量tickets記錄當(dāng)前剩余的票數(shù),此時(shí)tickets是會(huì)被多個(gè)執(zhí)行流同時(shí)訪問的臨界資源,在下面的代碼中我們并沒有對(duì)tickets進(jìn)行任何保護(hù)操作。
#include <iostream> #include <string> #include <unistd.h> #include <pthread.h>int tickets = 2000; void* TicketGrabbing(void* arg) {std::string name = (char*)arg;while (true){if (tickets > 0){usleep(1000);std::cout << name << " get a ticket, tickets left: " << --tickets << std::endl;}else{break;}}std::cout << name << " quit..." << std::endl;pthread_exit((void*)0); }int main() {pthread_t tid1, tid2, tid3, tid4;pthread_create(&tid1, nullptr, TicketGrabbing, (void*)"thread 1");pthread_create(&tid2, nullptr, TicketGrabbing, (void*)"thread 2");pthread_create(&tid3, nullptr, TicketGrabbing, (void*)"thread 3");pthread_create(&tid4, nullptr, TicketGrabbing, (void*)"thread 4");pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);pthread_join(tid3, nullptr);pthread_join(tid4, nullptr);return 0; }運(yùn)行代碼后可以看到,線程打印輸出剩余票數(shù)時(shí)出現(xiàn)了票數(shù)剩余為負(fù)數(shù)的情況,這是不符合我們預(yù)期的。
下面我們?cè)趽屍边壿嫯?dāng)中加入二元信號(hào)量,讓每個(gè)線程在訪問全局變量tickets之前先申請(qǐng)信號(hào)量,訪問完畢后再釋放信號(hào)量,此時(shí)二元信號(hào)量就達(dá)到了互斥的效果。
運(yùn)行代碼后就不會(huì)出現(xiàn)剩余票數(shù)為負(fù)的情況了,因?yàn)榇藭r(shí)同一時(shí)刻只會(huì)有一個(gè)執(zhí)行流對(duì)全局變量tickets進(jìn)行訪問,不會(huì)出現(xiàn)數(shù)據(jù)不一致的問題。
基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
空間資源和數(shù)據(jù)資源
生產(chǎn)者關(guān)注的是空間資源,消費(fèi)者關(guān)注的是數(shù)據(jù)資源
對(duì)于生產(chǎn)者和消費(fèi)者來說,它們關(guān)注的資源是不同的:
- 生產(chǎn)者關(guān)注的是環(huán)形隊(duì)列當(dāng)中是否有空間(blank),只要有空間生產(chǎn)者就可以進(jìn)行生產(chǎn)。
- 消費(fèi)者關(guān)注的是環(huán)形隊(duì)列當(dāng)中是否有數(shù)據(jù)(data),只要有數(shù)據(jù)消費(fèi)者就可以進(jìn)行消費(fèi)。
blank_sem和data_sem的初始值設(shè)置
現(xiàn)在我們用信號(hào)量來描述環(huán)形隊(duì)列當(dāng)中的空間資源(blank_sem)和數(shù)據(jù)資源(data_sem),在我們初始信號(hào)量時(shí)給它們?cè)O(shè)置的初始值是不同的:
- blank_sem的初始值我們應(yīng)該設(shè)置為環(huán)形隊(duì)列的容量,因?yàn)閯傞_始時(shí)環(huán)形隊(duì)列當(dāng)中全是空間。
- data_sem的初始值我們應(yīng)該設(shè)置為0,因?yàn)閯傞_始時(shí)環(huán)形隊(duì)列當(dāng)中沒有數(shù)據(jù)。
生產(chǎn)者和消費(fèi)者申請(qǐng)和釋放資源
生產(chǎn)者申請(qǐng)空間資源,釋放數(shù)據(jù)資源
對(duì)于生產(chǎn)者來說,生產(chǎn)者每次生產(chǎn)數(shù)據(jù)前都需要先申請(qǐng)blank_sem:
- 如果blank_sem的值不為0,則信號(hào)量申請(qǐng)成功,此時(shí)生產(chǎn)者可以進(jìn)行生產(chǎn)操作。
- 如果blank_sem的值為0,則信號(hào)量申請(qǐng)失敗,此時(shí)生產(chǎn)者需要在blank_sem的等待隊(duì)列下進(jìn)行阻塞等待,直到環(huán)形隊(duì)列當(dāng)中有新的空間后再被喚醒。
當(dāng)生產(chǎn)者生產(chǎn)完數(shù)據(jù)后,應(yīng)該釋放data_sem:
- 雖然生產(chǎn)者在進(jìn)行生產(chǎn)前是對(duì)blank_sem進(jìn)行的P操作,但是當(dāng)生產(chǎn)者生產(chǎn)完數(shù)據(jù),應(yīng)該對(duì)data_sem進(jìn)行V操作而不是blank_sem。
- 生產(chǎn)者在生產(chǎn)數(shù)據(jù)前申請(qǐng)到的是blank位置,當(dāng)生產(chǎn)者生產(chǎn)完數(shù)據(jù)后,該位置當(dāng)中存儲(chǔ)的是生產(chǎn)者生產(chǎn)的數(shù)據(jù),在該數(shù)據(jù)被消費(fèi)者消費(fèi)之前,該位置不再是blank位置,而應(yīng)該是data位置。
- 當(dāng)生產(chǎn)者生產(chǎn)完數(shù)據(jù)后,意味著環(huán)形隊(duì)列當(dāng)中多了一個(gè)data位置,因此我們應(yīng)該對(duì)data_sem進(jìn)行V操作。
消費(fèi)者申請(qǐng)數(shù)據(jù)資源,釋放空間資源
對(duì)于消費(fèi)者來說,消費(fèi)者每次消費(fèi)數(shù)據(jù)前都需要先申請(qǐng)data_sem:
- 如果data_sem的值不為0,則信號(hào)量申請(qǐng)成功,此時(shí)消費(fèi)者可以進(jìn)行消費(fèi)操作。
- 如果data_sem的值為0,則信號(hào)量申請(qǐng)失敗,此時(shí)消費(fèi)者需要在data_sem的等待隊(duì)列下進(jìn)行阻塞等待,直到環(huán)形隊(duì)列當(dāng)中有新的數(shù)據(jù)后再被喚醒。
當(dāng)消費(fèi)者消費(fèi)完數(shù)據(jù)后,應(yīng)該釋放blank_sem:
- 雖然消費(fèi)者在進(jìn)行消費(fèi)前是對(duì)data_sem進(jìn)行的P操作,但是當(dāng)消費(fèi)者消費(fèi)完數(shù)據(jù),應(yīng)該對(duì)blank_sem進(jìn)行V操作而不是data_sem。
- 消費(fèi)者在消費(fèi)數(shù)據(jù)前申請(qǐng)到的是data位置,當(dāng)消費(fèi)者消費(fèi)完數(shù)據(jù)后,該位置當(dāng)中的數(shù)據(jù)已經(jīng)被消費(fèi)過了,再次被消費(fèi)就沒有意義了,為了讓生產(chǎn)者后續(xù)可以在該位置生產(chǎn)新的數(shù)據(jù),我們應(yīng)該將該位置算作blank位置,而不是data位置。
- 當(dāng)消費(fèi)者消費(fèi)完數(shù)據(jù)后,意味著環(huán)形隊(duì)列當(dāng)中多了一個(gè)blank位置,因此我們應(yīng)該對(duì)blank_sem進(jìn)行V操作。
必須遵守的兩個(gè)規(guī)則
在基于環(huán)形隊(duì)列的生產(chǎn)者和消費(fèi)者模型當(dāng)中,生產(chǎn)者和消費(fèi)者必須遵守如下兩個(gè)規(guī)則。
第一個(gè)規(guī)則:生產(chǎn)者和消費(fèi)者不能對(duì)同一個(gè)位置進(jìn)行訪問。
生產(chǎn)者和消費(fèi)者在訪問環(huán)形隊(duì)列時(shí):
- 如果生產(chǎn)者和消費(fèi)者訪問的是環(huán)形隊(duì)列當(dāng)中的同一個(gè)位置,那么此時(shí)生產(chǎn)者和消費(fèi)者就相當(dāng)于同時(shí)對(duì)這一塊臨界資源進(jìn)行了訪問,這當(dāng)然是不允許的。
- 而如果生產(chǎn)者和消費(fèi)者訪問的是環(huán)形隊(duì)列當(dāng)中的不同位置,那么此時(shí)生產(chǎn)者和消費(fèi)者是可以同時(shí)進(jìn)行生產(chǎn)和消費(fèi)的,此時(shí)不會(huì)出現(xiàn)數(shù)據(jù)不一致等問題。
如下圖:
第二個(gè)規(guī)則:無論是生產(chǎn)者還是消費(fèi)者,都不應(yīng)該將對(duì)方套一個(gè)圈以上。
- 生產(chǎn)者從消費(fèi)者的位置開始一直按順時(shí)針方向進(jìn)行生產(chǎn),如果生產(chǎn)者生產(chǎn)的速度比消費(fèi)者消費(fèi)的速度快,那么當(dāng)生產(chǎn)者繞著消費(fèi)者生產(chǎn)了一圈數(shù)據(jù)后再次遇到消費(fèi)者,此時(shí)生產(chǎn)者就不應(yīng)該再繼續(xù)生產(chǎn)了,因?yàn)樵偕a(chǎn)就會(huì)覆蓋還未被消費(fèi)者消費(fèi)的數(shù)據(jù)。
- 同理,消費(fèi)者從生產(chǎn)者的位置開始一直按順時(shí)針方向進(jìn)行消費(fèi),如果消費(fèi)者消費(fèi)的速度比生產(chǎn)者生產(chǎn)的速度快,那么當(dāng)消費(fèi)者繞著生產(chǎn)者消費(fèi)了一圈數(shù)據(jù)后再次遇到生產(chǎn)者,此時(shí)消費(fèi)者就不應(yīng)該再繼續(xù)消費(fèi)了,因?yàn)樵傧M(fèi)就會(huì)消費(fèi)到緩沖區(qū)中保存的廢棄數(shù)據(jù)。
如下圖:
代碼實(shí)現(xiàn)
其中的RingQueue就是生產(chǎn)者消費(fèi)者模型當(dāng)中的交易場所,我們可以用C++STL庫當(dāng)中的vector進(jìn)行實(shí)現(xiàn)。
#pragma once#include <iostream> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <vector>#define NUM 8template<class T> class RingQueue { private://P操作void P(sem_t& s){sem_wait(&s);}//V操作void V(sem_t& s){sem_post(&s);} public:RingQueue(int cap = NUM): _cap(cap), _p_pos(0), _c_pos(0){_q.resize(_cap);sem_init(&_blank_sem, 0, _cap); //blank_sem初始值設(shè)置為環(huán)形隊(duì)列的容量sem_init(&_data_sem, 0, 0); //data_sem初始值設(shè)置為0}~RingQueue(){sem_destroy(&_blank_sem);sem_destroy(&_data_sem);}//向環(huán)形隊(duì)列插入數(shù)據(jù)(生產(chǎn)者調(diào)用)void Push(const T& data){P(_blank_sem); //生產(chǎn)者關(guān)注空間資源_q[_p_pos] = data;V(_data_sem); //生產(chǎn)//更新下一次生產(chǎn)的位置_p_pos++;_p_pos %= _cap;}//從環(huán)形隊(duì)列獲取數(shù)據(jù)(消費(fèi)者調(diào)用)void Pop(T& data){P(_data_sem); //消費(fèi)者關(guān)注數(shù)據(jù)資源data = _q[_c_pos];V(_blank_sem);//更新下一次消費(fèi)的位置_c_pos++;_c_pos %= _cap;} private:std::vector<T> _q; //環(huán)形隊(duì)列int _cap; //環(huán)形隊(duì)列的容量上限int _p_pos; //生產(chǎn)位置int _c_pos; //消費(fèi)位置sem_t _blank_sem; //描述空間資源sem_t _data_sem; //描述數(shù)據(jù)資源 };相關(guān)說明:
- 當(dāng)不設(shè)置環(huán)形隊(duì)列的大小時(shí),我們默認(rèn)將環(huán)形隊(duì)列的容量上限設(shè)置為8。
- 代碼中的RingQueue是用vector實(shí)現(xiàn)的,生產(chǎn)者每次生產(chǎn)的數(shù)據(jù)放到vector下標(biāo)為p_pos的位置,消費(fèi)者每次消費(fèi)的數(shù)據(jù)來源于vector下標(biāo)為c_pos的位置。
- 生產(chǎn)者每次生產(chǎn)數(shù)據(jù)后p_pos都會(huì)進(jìn)行++,標(biāo)記下一次生產(chǎn)數(shù)據(jù)的存放位置,++后的下標(biāo)會(huì)與環(huán)形隊(duì)列的容量進(jìn)行取模運(yùn)算,實(shí)現(xiàn)“環(huán)形”的效果。
- 消費(fèi)者每次消費(fèi)數(shù)據(jù)后c_pos都會(huì)進(jìn)行++,標(biāo)記下一次消費(fèi)數(shù)據(jù)的來源位置,++后的下標(biāo)會(huì)與環(huán)形隊(duì)列的容量進(jìn)行取模運(yùn)算,實(shí)現(xiàn)“環(huán)形”的效果。
- p_pos只會(huì)由生產(chǎn)者線程進(jìn)行更新,c_pos只會(huì)由消費(fèi)者線程進(jìn)行更新,對(duì)這兩個(gè)變量訪問時(shí)不需要進(jìn)行保護(hù),因此代碼中將p_pos和c_pos的更新放到了V操作之后,就是為了盡量減少臨界區(qū)的代碼。
為了方便理解,我們這里實(shí)現(xiàn)單生產(chǎn)者、單消費(fèi)者的生產(chǎn)者消費(fèi)者模型。于是在主函數(shù)我們就只需要?jiǎng)?chuàng)建一個(gè)生產(chǎn)者線程和一個(gè)消費(fèi)者線程,生產(chǎn)者線程不斷生產(chǎn)數(shù)據(jù)放入環(huán)形隊(duì)列,消費(fèi)者線程不斷從環(huán)形隊(duì)列里取出數(shù)據(jù)進(jìn)行消費(fèi)。
#include "RingQueue.hpp"void* Producer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = rand() % 100 + 1;rq->Push(data);std::cout << "Producer: " << data << std::endl;} } void* Consumer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = 0;rq->Pop(data);std::cout << "Consumer: " << data << std::endl;} } int main() {srand((unsigned int)time(nullptr));pthread_t producer, consumer;RingQueue<int>* rq = new RingQueue<int>;pthread_create(&producer, nullptr, Producer, rq);pthread_create(&consumer, nullptr, Consumer, rq);pthread_join(producer, nullptr);pthread_join(consumer, nullptr);delete rq;return 0; }相關(guān)說明:
- 環(huán)形隊(duì)列要讓生產(chǎn)者線程向隊(duì)列中Push數(shù)據(jù),讓消費(fèi)者線程從隊(duì)列中Pop數(shù)據(jù),因此這個(gè)環(huán)形隊(duì)列必須要讓這兩個(gè)線程同時(shí)看到,所以我們?cè)趧?chuàng)建生產(chǎn)者線程和消費(fèi)者線程時(shí),需要將環(huán)形隊(duì)列作為線程執(zhí)行例程的參數(shù)進(jìn)行傳入。
- 代碼中生產(chǎn)者生產(chǎn)數(shù)據(jù)就是將獲取到的隨機(jī)數(shù)Push到環(huán)形隊(duì)列,而消費(fèi)者就是從環(huán)形隊(duì)列Pop數(shù)據(jù),為了便于觀察,我們可以將生產(chǎn)者生產(chǎn)的數(shù)據(jù)和消費(fèi)者消費(fèi)的數(shù)據(jù)進(jìn)行打印輸出。
生產(chǎn)者消費(fèi)者步調(diào)一致
由于代碼中生產(chǎn)者是每隔一秒生產(chǎn)一個(gè)數(shù)據(jù),而消費(fèi)者是每隔一秒消費(fèi)一個(gè)數(shù)據(jù),因此運(yùn)行代碼后我們可以看到生產(chǎn)者和消費(fèi)者的執(zhí)行步調(diào)是一致的。
生產(chǎn)者生產(chǎn)的快,消費(fèi)者消費(fèi)的慢
我們可以讓生產(chǎn)者不停的進(jìn)行生產(chǎn),而消費(fèi)者每隔一秒進(jìn)行消費(fèi)。
void* Producer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){int data = rand() % 100 + 1;rq->Push(data);std::cout << "Producer: " << data << std::endl;} } void* Consumer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = 0;rq->Pop(data);std::cout << "Consumer: " << data << std::endl;} }此時(shí)由于生產(chǎn)者生產(chǎn)的很快,運(yùn)行代碼后一瞬間生產(chǎn)者就將環(huán)形隊(duì)列打滿了,此時(shí)生產(chǎn)者想要再進(jìn)行生產(chǎn),但空間資源已經(jīng)為0了,于是生產(chǎn)者只能在blank_sem的等待隊(duì)列下進(jìn)行阻塞等待,直到由消費(fèi)者消費(fèi)完一個(gè)數(shù)據(jù)后對(duì)blank_sem進(jìn)行了V操作,生產(chǎn)者才會(huì)被喚醒進(jìn)而繼續(xù)進(jìn)行生產(chǎn)。
但由于生產(chǎn)者的生產(chǎn)速度很快,生產(chǎn)者生產(chǎn)完一個(gè)數(shù)據(jù)后又會(huì)進(jìn)行等待,因此后續(xù)生產(chǎn)者和消費(fèi)者的步調(diào)又變成一致的了。
生產(chǎn)者生產(chǎn)的慢,消費(fèi)者消費(fèi)的快
當(dāng)然我們也可以讓生產(chǎn)者每隔一秒進(jìn)行生產(chǎn),而消費(fèi)者不停的進(jìn)行消費(fèi)。
void* Producer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){sleep(1);int data = rand() % 100 + 1;rq->Push(data);std::cout << "Producer: " << data << std::endl;} } void* Consumer(void* arg) {RingQueue<int>* rq = (RingQueue<int>*)arg;while (true){int data = 0;rq->Pop(data);std::cout << "Consumer: " << data << std::endl;} }雖然消費(fèi)者消費(fèi)的很快,但一開始環(huán)形隊(duì)列當(dāng)中的數(shù)據(jù)資源為0,因此消費(fèi)者只能在data_sem的等待隊(duì)列下進(jìn)行阻塞等待,直到生產(chǎn)者生產(chǎn)完一個(gè)數(shù)據(jù)后對(duì)data_sem進(jìn)行了V操作,消費(fèi)者才會(huì)被喚醒進(jìn)而進(jìn)行消費(fèi)。
但由于消費(fèi)者的消費(fèi)速度很快,消費(fèi)者消費(fèi)完一個(gè)數(shù)據(jù)后又會(huì)進(jìn)行等待,因此后續(xù)生產(chǎn)者和消費(fèi)者的步調(diào)又變成一致的了。
信號(hào)量保護(hù)環(huán)形隊(duì)列的原理
在blank_sem和data_sem兩個(gè)信號(hào)量的保護(hù)后,該環(huán)形隊(duì)列中不可能會(huì)出現(xiàn)數(shù)據(jù)不一致的問題。
因?yàn)橹挥挟?dāng)生產(chǎn)者和消費(fèi)者指向同一個(gè)位置并訪問時(shí),才會(huì)導(dǎo)致數(shù)據(jù)不一致的問題,而此時(shí)生產(chǎn)者和消費(fèi)者在對(duì)環(huán)形隊(duì)列進(jìn)行寫入或讀取數(shù)據(jù)時(shí),只有兩種情況會(huì)指向同一個(gè)位置:
- 環(huán)形隊(duì)列為空時(shí)。
- 環(huán)形隊(duì)列為滿時(shí)。
但是在這兩種情況下,生產(chǎn)者和消費(fèi)者不會(huì)同時(shí)對(duì)環(huán)形隊(duì)列進(jìn)行訪問:
- 當(dāng)環(huán)形隊(duì)列為空的時(shí),消費(fèi)者一定不能進(jìn)行消費(fèi),因?yàn)榇藭r(shí)數(shù)據(jù)資源為0。
- 當(dāng)環(huán)形隊(duì)列為滿的時(shí),生產(chǎn)者一定不能進(jìn)行生產(chǎn),因?yàn)榇藭r(shí)空間資源為0。
也就是說,當(dāng)環(huán)形隊(duì)列為空和滿時(shí),我們已經(jīng)通過信號(hào)量保證了生產(chǎn)者和消費(fèi)者的串行化過程。而除了這兩種情況之外,生產(chǎn)者和消費(fèi)者指向的都不是同一個(gè)位置,因此該環(huán)形隊(duì)列當(dāng)中不可能會(huì)出現(xiàn)數(shù)據(jù)不一致的問題。并且大部分情況下生產(chǎn)者和消費(fèi)者指向并不是同一個(gè)位置,因此大部分情況下該環(huán)形隊(duì)列可以讓生產(chǎn)者和消費(fèi)者并發(fā)的執(zhí)行
總結(jié)
- 上一篇: KITTI数据集解析和可视化
- 下一篇: linux C语言基础学习总结