国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Linux篇】第十七篇——信號(hào)量

這篇具有很好參考價(jià)值的文章主要介紹了【Linux篇】第十七篇——信號(hào)量。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

前言

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)量。

sem_wait,# Linux,Linux,信號(hào)量,二元信號(hào)量,環(huán)形隊(duì)列,生產(chǎn)消費(fèi)模型

信號(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ù)期的。

sem_wait,# Linux,Linux,信號(hào)量,二元信號(hào)量,環(huán)形隊(duì)列,生產(chǎn)消費(fèi)模型

?下面我們?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)模型

sem_wait,# Linux,Linux,信號(hào)量,二元信號(hào)量,環(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)題。

sem_wait,# Linux,Linux,信號(hào)量,二元信號(hào)量,環(huán)形隊(duì)列,生產(chǎn)消費(fèi)模型

?第二個(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)是一致的。

sem_wait,# Linux,Linux,信號(hào)量,二元信號(hào)量,環(huán)形隊(duì)列,生產(chǎn)消費(fèi)模型

?生產(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):

  • 當(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux】淺談信號(hào)量

    【Linux】淺談信號(hào)量

    tips:system V 是一套標(biāo)準(zhǔn),共享內(nèi)存,信號(hào)量,消息隊(duì)列屬于system V。 進(jìn)程A和進(jìn)程B進(jìn)行通信時(shí),假如進(jìn)程A向物理內(nèi)存的共享區(qū)寫入\\\"Hello World\\\",但是當(dāng)進(jìn)程A寫入了\\\"Hello\\\"時(shí),進(jìn)程B就向內(nèi)存讀取了,所以只讀取到了\\\"Hello\\\",這就導(dǎo)致進(jìn)程A想向進(jìn)程B發(fā)送的信息,進(jìn)程B讀取不完整,

    2024年02月05日
    瀏覽(18)
  • linux進(jìn)程間通信(信號(hào)量)

    linux進(jìn)程間通信(信號(hào)量)

    信號(hào)量是一個(gè)特殊的變量,程序?qū)ζ湓L問(wèn)都是原子操作,且只允許對(duì)它進(jìn)行等待(即 P(信號(hào)變量))和發(fā) 送(即 V(信號(hào)變量))信息操作。最簡(jiǎn)單的信號(hào)量是只能取 0 和 1 的變量,這也是信號(hào)量最常見(jiàn)的一種形式, 叫做二進(jìn)制信號(hào)量。而可以取多個(gè)正整數(shù)的信號(hào)量被稱為通用信號(hào)

    2024年02月07日
    瀏覽(33)
  • 【Linux】進(jìn)程間通信 -- 信號(hào)量

    【Linux】進(jìn)程間通信 -- 信號(hào)量

    信號(hào)量是什么? 本質(zhì)是一個(gè)計(jì)數(shù)器,通常用來(lái)表示公共資源中,資源數(shù)量多少的問(wèn)題 公共資源:能被多個(gè)進(jìn)程同時(shí)可以訪問(wèn)的資源 訪問(wèn)沒(méi)有保護(hù)的公共資源:數(shù)據(jù)不一致問(wèn)題(比如我想寫abc123,但是我123還沒(méi)有寫入,就讀取了abc,可能數(shù)據(jù)分開(kāi)會(huì)導(dǎo)致數(shù)據(jù)無(wú)意義) 為什么要

    2024年02月16日
    瀏覽(25)
  • linux中互斥鎖,自旋鎖,條件變量,信號(hào)量,與freeRTOS中的消息隊(duì)列,信號(hào)量,互斥量,事件的區(qū)別

    linux中互斥鎖,自旋鎖,條件變量,信號(hào)量,與freeRTOS中的消息隊(duì)列,信號(hào)量,互斥量,事件的區(qū)別

    對(duì)于目前主流的RTOS的任務(wù),大部分都屬于并發(fā)的線程。 因?yàn)镸CU上的資源每個(gè)任務(wù)都是共享的,可以認(rèn)為是單進(jìn)程多線程模型。 【freertos】003-任務(wù)基礎(chǔ)知識(shí) 在沒(méi)有操作系統(tǒng)的時(shí)候兩個(gè)應(yīng)用程序進(jìn)行消息傳遞一般使用全局變量的方式,但是如果在使用操作系統(tǒng)的應(yīng)用中用全局變

    2024年02月11日
    瀏覽(26)
  • 【Linux】多線程 之 POSIX信號(hào)量

    【Linux】多線程 之 POSIX信號(hào)量

    信號(hào)量又稱為 信號(hào)燈 本質(zhì)就是一個(gè)計(jì)數(shù)器,用于描述臨界資源數(shù)目的 sem: 0 - 1 - 0 若臨界資源只有1個(gè),則sem設(shè)為1,當(dāng)要使用臨界資源時(shí),sem由1變?yōu)?,其他人在想申請(qǐng),則申請(qǐng)不到掛起排隊(duì),等待釋放臨界資源時(shí) sem由0變?yōu)? ,才可以再申請(qǐng)臨界資源 這種信號(hào)量稱為 二元信號(hào)

    2024年02月16日
    瀏覽(47)
  • Linux進(jìn)程間通信【消息隊(duì)列、信號(hào)量】

    Linux進(jìn)程間通信【消息隊(duì)列、信號(hào)量】

    ?個(gè)人主頁(yè): 北 海 ??所屬專欄: Linux學(xué)習(xí)之旅 ??操作環(huán)境: CentOS 7.6 阿里云遠(yuǎn)程服務(wù)器 在 System V 通信標(biāo)準(zhǔn)中,還有一種通信方式: 消息隊(duì)列 ,以及一種實(shí)現(xiàn)互斥的工具: 信號(hào)量 ;隨著時(shí)代的發(fā)展,這些陳舊的標(biāo)準(zhǔn)都已經(jīng)較少使用了,但作為 IPC 中的經(jīng)典知識(shí),我們可

    2024年02月08日
    瀏覽(31)
  • linux(system V標(biāo)準(zhǔn))信號(hào)量

    linux(system V標(biāo)準(zhǔn))信號(hào)量

    目錄: ??????????? 1.什么是信號(hào)量 ??????????? 2.信號(hào)量的本質(zhì) 1.什么是信號(hào)量 ? 2.信號(hào)量的本質(zhì) ?什么是臨界資源呢?? 凡是倍多個(gè)執(zhí)行流同時(shí)訪問(wèn)的資源就是臨界資源!!! 我們看一個(gè)問(wèn)題,我們fork()之后創(chuàng)建一個(gè)子進(jìn)程,那么我們的全局變量,是不是我們父

    2024年02月07日
    瀏覽(28)
  • 『Linux』第九講:Linux多線程詳解(五)_ 信號(hào)量

    『Linux』第九講:Linux多線程詳解(五)_ 信號(hào)量

    「前言」文章是關(guān)于Linux多線程方面的知識(shí),上一篇是?Linux多線程詳解(四),今天這篇是 Linux多線程詳解(五),內(nèi)容大致是信號(hào)量,講解下面開(kāi)始! 「歸屬專欄」Linux系統(tǒng)編程 「主頁(yè)鏈接」個(gè)人主頁(yè) 「筆者」楓葉先生(fy) 「楓葉先生有點(diǎn)文青病」「每篇一句」 求其上,

    2024年02月07日
    瀏覽(18)
  • 【linux】進(jìn)行間通信——共享內(nèi)存+消息隊(duì)列+信號(hào)量

    【linux】進(jìn)行間通信——共享內(nèi)存+消息隊(duì)列+信號(hào)量

    進(jìn)程間通信方式目前我們已經(jīng)學(xué)了匿名管道,命名管道。讓兩個(gè)獨(dú)立的進(jìn)程通信,前提是看到同一份資源。匿名管道適用于血緣關(guān)系的進(jìn)程,一個(gè)打開(kāi)寫端一個(gè)打開(kāi)讀端實(shí)現(xiàn)的。命名管道適用于完全獨(dú)立的進(jìn)程,打開(kāi)同一份文件實(shí)現(xiàn)的。 接下來(lái)我們看看剩下的實(shí)現(xiàn)進(jìn)程間通信

    2024年02月05日
    瀏覽(21)
  • linux eventfd事件通知 比信號(hào)量更好用

    linux eventfd事件通知 比信號(hào)量更好用

    ? 專欄內(nèi)容:linux下并發(fā)編程 個(gè)人主頁(yè):我的主頁(yè) 座右銘:天行健,君子以自強(qiáng)不息;地勢(shì)坤,君子以厚德載物. 目錄 前言 概述 原理簡(jiǎn)介 使用場(chǎng)景 接口說(shuō)明 頭文件 參數(shù)說(shuō)明 代碼演示 默認(rèn)參數(shù) 信號(hào)量模式 結(jié)尾 ? 本專欄主要分享linux下并發(fā)編程相關(guān)知識(shí),包括多進(jìn)程,多

    2024年02月08日
    瀏覽(42)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包