1. 概念
信號(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)量 ,等同于互斥鎖
每一個(gè)線程,在訪問對(duì)應(yīng)的資源時(shí),先申請(qǐng)信號(hào)量,
申請(qǐng)成功,表示該線程允許使用該資源
申請(qǐng)不成功,表示目前無法使用該資源
2. 信號(hào)量的工作機(jī)制
信號(hào)量機(jī)制類似于看電影買票,一種資源的預(yù)訂機(jī)制
申請(qǐng)信號(hào)量成功,相當(dāng)于預(yù)定了一部分資源

判斷條件是否滿足,決定了后續(xù)行為
信號(hào)量已經(jīng)是資源的計(jì)數(shù)器,申請(qǐng)信號(hào)量成功,本身就表明資源可用
申請(qǐng)信號(hào)量失敗,本身表明資源不可用
本質(zhì)就是把判斷轉(zhuǎn)換成信號(hào)量的申請(qǐng)行為
3. 認(rèn)識(shí)接口
POSIX信號(hào)量 和system V 信號(hào)量 作用相同,都是用于同步操作,達(dá)到無沖突的訪問共享資源目的,但POSIX可以用于線程間同步
sem_init ——初始化信號(hào)量
輸入 man sem_init

sem :表示信號(hào)量
pshared : 0表示線程間共享 非零表示進(jìn)程間共享
value : 信號(hào)量初始值 (計(jì)數(shù)器值初始化為多少)
sem_destroy——銷毀信號(hào)量
輸入 man sem_destroy

對(duì)已經(jīng)初始化的信號(hào)量進(jìn)行銷毀
sem_wait ——申請(qǐng)信號(hào)量
輸入 man sem_wait

進(jìn)行申請(qǐng)信號(hào)量的操作,使信號(hào)量的值減1
sem_post ——釋放信號(hào)量
輸入 man sem_post

進(jìn)行釋放信號(hào)量的操作,使信號(hào)量的值加1
4. 基于環(huán)形隊(duì)列的生產(chǎn)消費(fèi)模型
原理解析
環(huán)形隊(duì)列實(shí)際上使用數(shù)組模擬的

數(shù)組多開一個(gè)空間是為了解決判滿的問題

若為空,則 thread和tail 在同一個(gè)位置

若為滿,則tail的下一個(gè)位置為head

生產(chǎn)者向tail中push數(shù)據(jù) 即生產(chǎn)
消費(fèi)者向head中pop數(shù)據(jù) 即消費(fèi)
生產(chǎn)者 和消費(fèi)者 關(guān)心的資源 是一樣的嗎?
不一樣, 生產(chǎn)者關(guān)心整個(gè)環(huán)形隊(duì)列的空間(商店是否裝滿貨物)
消費(fèi)者關(guān)心的是 數(shù)據(jù),(商店是否還有貨物,有貨物就買)

head 和tail什么時(shí)候訪問 同一個(gè)區(qū)域?
有一個(gè)很大的桌子,存在像鐘表的0-12點(diǎn)刻度的區(qū)域,在每個(gè)刻度中放入一個(gè)盤子
有兩個(gè)人A和B同時(shí)進(jìn)入房間看到桌子,A往盤子中放蘋果,B在后面拿蘋果
A和B約定:B不能超過A,一個(gè)盤子只能放一個(gè)蘋果
當(dāng)A和B開始,桌子上沒有蘋果時(shí) ,或者 桌子上全都是蘋果時(shí),都會(huì)訪問同一個(gè)盤子
即環(huán)形隊(duì)列 為空 ,或者環(huán)形隊(duì)列為滿 會(huì)訪問 同一個(gè)區(qū)域
當(dāng)隊(duì)列為空,指向同一個(gè)位置,存在競(jìng)爭(zhēng)關(guān)系,
讓生產(chǎn)者先運(yùn)行 (只有當(dāng)生產(chǎn)者產(chǎn)生數(shù)據(jù)后,消費(fèi)者才能拿到數(shù)據(jù))
當(dāng)隊(duì)列為滿時(shí),指向同一個(gè)位置,存在競(jìng)爭(zhēng)關(guān)系,
讓消費(fèi)者先運(yùn)行 (只有當(dāng)消費(fèi)者拿數(shù)據(jù)后,生產(chǎn)者才能生產(chǎn))

生產(chǎn)者關(guān)心空間,空間本身也是資源,所以要給生產(chǎn)者定義一個(gè)信號(hào)量sem_room ,其初始值為N
P(sem_room) —— 申請(qǐng)空間信號(hào)量
生產(chǎn)者生產(chǎn)數(shù)據(jù)在當(dāng)前空間,則對(duì)應(yīng)的數(shù)據(jù)+1,所以消費(fèi)者可以拿數(shù)據(jù)
V(sem_data) ——數(shù)據(jù)信號(hào)量的值+1
消費(fèi)者關(guān)心數(shù)據(jù),信號(hào)量為sem_data,其初始值為0
P(sem_data) —— 申請(qǐng)數(shù)據(jù)信號(hào)量
消費(fèi)者把數(shù)據(jù)拿走,當(dāng)前空間就被閑置出來了,所以生產(chǎn)者可以放數(shù)據(jù)
V(sem_room) ——空間信號(hào)量的值+1
代碼
代碼解析
首先在ringqueue.hpp中創(chuàng)建一個(gè)ringqueue類

在main函數(shù)中使用new創(chuàng)建出rq隊(duì)列
為了保證生產(chǎn)者和消費(fèi)者看到同一份資源,所以兩者回調(diào)函數(shù)的參數(shù)args都為rq

productorRoutine的回調(diào)函數(shù)中 使用 隊(duì)列rq的push,將數(shù)據(jù)插入到隊(duì)列中 即生產(chǎn)
consumerRoutine的回調(diào)函數(shù)中 使用 隊(duì)列rq的pop,把隊(duì)列中的數(shù)據(jù)取出 即消費(fèi)
ringqueue類
ringqueue類中

在上述講解原理時(shí),數(shù)據(jù)信號(hào)量只有消費(fèi)者關(guān)心,空間信號(hào)量只有生產(chǎn)者關(guān)心
構(gòu)造

將環(huán)形隊(duì)列ring大小和_cap(容量)初始化為N
0表示線程間共享,將數(shù)據(jù)信號(hào)量 初始化為0,將空間信號(hào)量初始化為整個(gè)環(huán)形隊(duì)列的容量
(對(duì)于兩者的初始化值大小,在原理處都有詳細(xì)解釋)
析構(gòu)

由于在構(gòu)造時(shí),對(duì)信號(hào)量進(jìn)行初始化,所以需要銷毀信號(hào)量
push ——生產(chǎn)
要生產(chǎn)之前要保證符合條件,才能夠進(jìn)行生產(chǎn),所以要進(jìn)行P操作——申請(qǐng)信號(hào)量

在使用信號(hào)量時(shí),是不需要判斷的
因?yàn)樾盘?hào)量是一把計(jì)數(shù)器,本質(zhì)為把對(duì)資源就緒的情況,由在臨界區(qū)內(nèi)轉(zhuǎn)到臨界區(qū)外
它本身就是描述臨界資源數(shù)量的,所以就不用進(jìn)入臨界區(qū)后判斷臨界資源是否滿足條件

生產(chǎn)者和消費(fèi)者可能訪問同一個(gè)位置,大概率訪問不同的位置
所以生產(chǎn)者和消費(fèi)者要有自己的下標(biāo) 用于 表示兩者的位置

不斷進(jìn)行P操作,在空間上插入數(shù)據(jù)
沒有空間就需要消費(fèi)者進(jìn)行消費(fèi)(V操作),將數(shù)據(jù)拿走下·

當(dāng)tail達(dá)到多開一個(gè)空間位置,實(shí)際上相當(dāng)于再次回到head開頭的位置
所以使用%=,模擬環(huán)形隊(duì)列

將 sem_wait 和sem_post借助 函數(shù) P和V完成封裝
再次使用時(shí),只需調(diào)用P V即可實(shí)現(xiàn)
文章來源:http://www.zghlxwxcb.cn/news/detail-572275.html
pop ——消費(fèi)

不斷進(jìn)行P操作,將數(shù)據(jù)從空間上拿走,空間都閑置出來了
就需要生產(chǎn)者進(jìn)行生產(chǎn)(V操作),在空間上放置數(shù)據(jù)文章來源地址http://www.zghlxwxcb.cn/news/detail-572275.html
代碼實(shí)現(xiàn)
Ringqueue.hpp
#include<iostream>
#include<vector>
#include<semaphore.h>//信號(hào)量頭文件
static const int N=5;//設(shè)置環(huán)形隊(duì)列的大小
template<class T>
class ringqueue
{
private:
void P(sem_t &s)
{
sem_wait(&s);
}
void V(sem_t&s)
{
sem_post(&s);
}
public:
ringqueue(int num=N)
: _ring(num),_cap(num)
{
//信號(hào)量初始化
sem_init(&_data_sem,0,0);
sem_init(&_space_sem,0,num);
_c_step=_p_step=0;//生產(chǎn)和消費(fèi)下標(biāo)都為0
}
~ringqueue()
{
//銷毀信號(hào)量
sem_destroy(&_data_sem);
sem_destroy(&_space_sem);
}
void push(const T&in)//生產(chǎn)
{
P(_space_sem);//P操作 申請(qǐng)信號(hào)量
_ring[_p_step++]=in;//將數(shù)據(jù)放入生產(chǎn)位置
_p_step%=_cap;
V(_data_sem);//V操作 釋放信號(hào)量
}
void pop(T*out)//消費(fèi)
{
P(_data_sem);//P操作
*out=_ring[_c_step++];//將該位置的數(shù)據(jù)給與out
_c_step%=_cap;
V(_space_sem);//V操作
}
private:
int _c_step;//消費(fèi)者位置下標(biāo)
int _p_step;//生產(chǎn)者位置下標(biāo)
std::vector<int> _ring;//充當(dāng)環(huán)形隊(duì)列
int _cap;//環(huán)形隊(duì)列的容器大小
sem_t _data_sem;//數(shù)據(jù)信號(hào)量
sem_t _space_sem;//空間信號(hào)量
};
makefile
ringqueue:main.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f ringqueue
main.cc
#include"Ringqueue.hpp"
#include<pthread.h>
#include<unistd.h>
using namespace std;
void*consumerRoutine(void*args)
{
ringqueue<int>*rq=(ringqueue<int>*)args;
while(true)
{
int data=0;
rq->pop(&data);//從隊(duì)列取出數(shù)據(jù) 消費(fèi)
cout<<"consumer done:"<<data<<endl;
sleep(1);
}
}
void*productorRoutine(void*args)
{
ringqueue<int>*rq=(ringqueue<int>*)args;
while(true)
{
int data=1;
rq->push(data);//將數(shù)據(jù)插入隊(duì)列中 生產(chǎn)
cout<<"productor done:"<<data<<endl;
}
}
int main()
{
ringqueue<int>*rq=new ringqueue<int>();
pthread_t c;//消費(fèi)者
pthread_t p;//生產(chǎn)者
//創(chuàng)建線程
pthread_create(&c,nullptr,consumerRoutine,rq);
pthread_create(&p,nullptr,productorRoutine,rq);
pthread_join(c,nullptr);
pthread_join(p,nullptr);
return 0;
}
到了這里,關(guān)于【Linux】多線程 之 POSIX信號(hào)量的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!