前言
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ì)列的原理
前言
- 將可能被多個(gè)執(zhí)行流同時(shí)訪問(wèn)的資源叫做臨界資源,臨界資源需要進(jìn)行保護(hù)否則會(huì)出現(xiàn)數(shù)據(jù)不一致等問(wèn)題。
- 當(dāng)我們僅用一個(gè)互斥鎖對(duì)臨界資源進(jìn)行保護(hù)時(shí),相當(dāng)于我們將這塊臨界資源看作一個(gè)整體,同一時(shí)刻只允許一個(gè)執(zhí)行流對(duì)這塊臨界資源進(jìn)行訪問(wèn)。
- 但實(shí)際我們可以將這塊臨界資源再分割為多個(gè)區(qū)域,當(dāng)多個(gè)執(zhí)行流需要訪問(wèn)臨界資源時(shí),如果這些執(zhí)行流訪問(wèn)的是臨界資源的不同區(qū)域,那么我們可以讓這些執(zhí)行流同時(shí)訪問(wèn)臨界資源的不同區(qū)域,此時(shí)不會(huì)出現(xiàn)數(shù)據(jù)不一致等問(wèn)題。
POSIX信號(hào)量
信號(hào)量的概念
信號(hào)量本質(zhì)是一個(gè)計(jì)數(shù)器(不設(shè)置全局變量是因?yàn)檫M(jìn)程間是相互獨(dú)立的,而這不一定能看到,看到也不能保證++引用計(jì)數(shù)為原子操作),用于多進(jìn)程對(duì)共享數(shù)據(jù)對(duì)象的讀取,它和管道有所不同,它不以傳送數(shù)據(jù)為主要目的,它主要是用來(lái)保護(hù)共享資源(信號(hào)量也屬于臨界資源),使得資源在一個(gè)時(shí)刻只有一個(gè)進(jìn)程獨(dú)享。?
每個(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)量的工作原理
由于信號(hào)量只能進(jìn)行兩種操作等待和發(fā)送信號(hào),即P(sv)和V(sv),他們的行為是這樣的:
(1)P(sv):我們將申請(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ù)器減一,如果sv的值大于零,就給它減1;如果它的值為零,就掛起該進(jìn)程的執(zhí)行
(2)V(sv):我們將釋放信號(hào)量稱為V操作,釋放信號(hào)量的本質(zhì)就是歸還臨界資源中某塊資源的使用權(quán)限,當(dāng)釋放成功時(shí)臨界資源中資源的數(shù)目就應(yīng)該加一,因此V操作本質(zhì)就是讓計(jì)數(shù)器加一,如果有其他進(jìn)程因等待sv而被掛起,就讓它恢復(fù)運(yùn)行,如果沒(méi)有進(jìn)程因等待sv而掛起,就給他加1
PV操作必須是原子操作
多個(gè)執(zhí)行流為了訪問(wèn)臨界資源會(huì)競(jìng)爭(zhēng)式的申請(qǐng)信號(hào)量,?因此信號(hào)量是會(huì)被多個(gè)執(zhí)行流同時(shí)訪問(wèn)的,也就是說(shuō)信號(hào)量本質(zhì)也是臨界資源。
信號(hào)量本質(zhì)就是用于保護(hù)臨界資源的,我們不可能再用信號(hào)量去保護(hù)信號(hào)量,所以信號(hào)量的PV操作必須是原子操作。
注意:?內(nèi)存當(dāng)中變量的++
、--
操作并不是原子操作,因此信號(hào)量不可能只是簡(jiǎn)單的對(duì)一個(gè)全局變量進(jìn)行++
、--
操作。
申請(qǐng)信號(hào)量失敗被掛起等待
當(dāng)執(zhí)行流在申請(qǐng)信號(hào)量時(shí),可能此時(shí)信號(hào)量的值為0,也就是說(shuō)信號(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ù)說(shuō)明:
- sem:需要初始化的信號(hào)量。
- pshared:傳入0值表示線程間共享,傳入非零值表示進(jìn)程間共享。
- value:信號(hào)量的初始值(計(jì)數(shù)器的初始值)。
返回值說(shuō)明:
- 初始化信號(hào)量成功返回0,失敗返回-1。
注意:?POSIX信號(hào)量和System V信號(hào)量作用相同,都是用于同步操作,達(dá)到無(wú)沖突的訪問(wèn)共享資源目的,但POSIX信號(hào)量可以用于線程間同步。
銷毀信號(hào)量
?銷毀信號(hào)量的函數(shù)叫做sem_destroy,該函數(shù)的函數(shù)原型如下:
int sem_destroy(sem_t *sem);
參數(shù)說(shuō)明:
- sem:需要銷毀的信號(hào)量。
返回值說(shuō)明:
- 銷毀信號(hào)量成功返回0,失敗返回-1。
等待信號(hào)量(申請(qǐng)信號(hào)量)
?等待信號(hào)量的函數(shù)叫做sem_wait,該函數(shù)的函數(shù)原型如下:
int sem_wait(sem_t *sem);
?參數(shù)說(shuō)明:
- sem:需要等待的信號(hào)量。
返回值說(shuō)明:
- 等待信號(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ù)說(shuō)明
- sem:需要發(fā)布的信號(hào)量。
返回值說(shuō)明:
- 發(fā)布信號(hào)量成功返回0,信號(hào)量的值加一。
- 發(fā)布信號(hào)量失敗返回-1,信號(hào)量的值保持不變。
二元信號(hào)量模擬實(shí)現(xiàn)互斥功能
二元信號(hào)量是最簡(jiǎn)單的一種鎖(互斥鎖),它只用兩種狀態(tài):占用與非占用,所以它的引用計(jì)數(shù)為1,說(shuō)明信號(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í)訪問(wèn)的臨界資源,在下面的代碼中我們并沒(méi)有對(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è)線程在訪問(wèn)全局變量tickets之前先申請(qǐng)信號(hào)量,訪問(wèn)完畢后再釋放信號(hào)量,此時(shí)二元信號(hào)量就達(dá)到了互斥的效果。
#include <iostream>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
class Sem{
public:
Sem(int num)
{
sem_init(&_sem, 0, num);
}
~Sem()
{
sem_destroy(&_sem);
}
void P()
{
sem_wait(&_sem);
}
void V()
{
sem_post(&_sem);
}
private:
sem_t _sem;
};
Sem sem(1); //二元信號(hào)量
int tickets = 2000;
void* TicketGrabbing(void* arg)
{
std::string name = (char*)arg;
while (true){
sem.P();
if (tickets > 0){
usleep(1000);
std::cout << name << " get a ticket, tickets left: " << --tickets << std::endl;
sem.V();
}
else{
sem.V();
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)行代碼后就不會(huì)出現(xiàn)剩余票數(shù)為負(fù)的情況了,因?yàn)榇藭r(shí)同一時(shí)刻只會(huì)有一個(gè)執(zhí)行流對(duì)全局變量tickets進(jìn)行訪問(wèn),不會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。
基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
空間資源和數(shù)據(jù)資源
生產(chǎn)者關(guān)注的是空間資源,消費(fèi)者關(guān)注的是數(shù)據(jù)資源
?對(duì)于生產(chǎn)者和消費(fèi)者來(lái)說(shuō),它們關(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)量來(lái)描述環(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)閯傞_(kāi)始時(shí)環(huán)形隊(duì)列當(dāng)中全是空間
- data_sem的初始值我們應(yīng)該設(shè)置為0,因?yàn)閯傞_(kāi)始時(shí)環(huán)形隊(duì)列當(dāng)中沒(méi)有數(shù)據(jù)。
生產(chǎn)者和消費(fèi)者申請(qǐng)和釋放資源
生產(chǎn)者申請(qǐng)空間資源,釋放數(shù)據(jù)資源
對(duì)于生產(chǎn)者來(lái)說(shuō),生產(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)者來(lái)說(shuō),消費(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)過(guò)了,再次被消費(fèi)就沒(mé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)行訪問(wèn)。
?生產(chǎn)者和消費(fèi)者在訪問(wèn)環(huán)形隊(duì)列時(shí):
- 如果生產(chǎn)者和消費(fèi)者訪問(wèn)的是環(huán)形隊(duì)列當(dāng)中的同一個(gè)位置,那么此時(shí)生產(chǎn)者和消費(fèi)者就相當(dāng)于同時(shí)對(duì)這一塊臨界資源進(jìn)行了訪問(wèn),這當(dāng)然是不允許的。
- 而如果生產(chǎn)者和消費(fèi)者訪問(wèn)的是環(huán)形隊(duì)列當(dāng)中的不同位置,那么此時(shí)生產(chǎn)者和消費(fèi)者是可以同時(shí)進(jìn)行生產(chǎn)和消費(fèi)的,此時(shí)不會(huì)出現(xiàn)數(shù)據(jù)不一致等問(wèn)題。
?第二個(gè)規(guī)則:無(wú)論是生產(chǎn)者還是消費(fèi)者,都不應(yīng)該將對(duì)方套一個(gè)圈以上。
- 生產(chǎn)者從消費(fèi)者的位置開(kāi)始一直按順時(shí)針?lè)较蜻M(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)者的位置開(kāi)始一直按順時(shí)針?lè)较蜻M(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)中的交易場(chǎng)所,我們可以用C++STL庫(kù)當(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 8
template<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)說(shuō)明:
- 當(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ù)來(lái)源于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ù)的來(lái)源位置,++后的下標(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è)變量訪問(wèn)時(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)說(shuō)明:
- 環(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)。
此時(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)。
雖然消費(fèi)者消費(fèi)的很快,但一開(kā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ù)不一致的問(wèn)題。
?因?yàn)橹挥挟?dāng)生產(chǎn)者和消費(fèi)者指向同一個(gè)位置并訪問(wèn)時(shí),才會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題,而此時(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)行訪問(wèn):文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-736748.html
- 當(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。
也就是說(shuō),當(dāng)環(huán)形隊(duì)列為空和滿時(shí),我們已經(jīng)通過(guò)信號(hào)量保證了生產(chǎn)者和消費(fèi)者的串行化過(guò)程。而除了這兩種情況之外,生產(chǎn)者和消費(fèi)者指向的都不是同一個(gè)位置,因此該環(huán)形隊(duì)列當(dāng)中不可能會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題。并且大部分情況下生產(chǎn)者和消費(fèi)者指向并不是同一個(gè)位置,因此大部分情況下該環(huán)形隊(duì)列可以讓生產(chǎn)者和消費(fèi)者并發(fā)的執(zhí)行。
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-736748.html
到了這里,關(guān)于【Linux篇】第十七篇——信號(hào)量的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!