文章目錄
- 1.什么是posix信號量?
- 2.posix信號量操作介面
- 3.posix是如何保證同步和互斥的?
- 3.1 互測驗性
- 3.2 同步屬性
- 4.實作生產消費模型
- 4.1 前提:使用POSIX信號量
- 4.2 使用信號量實作生產者與消費者模型
- 5.實作互斥和同步的模板寫法
1.什么是posix信號量?
- posix信號量可以完成行程間和執行緒間的同步與互斥
- 本質:信號量 = 資源計數器 (判斷資源是否可用) + PCB等待佇列 (生產者、消費者和lock) + 等待和喚醒介面
- 對比條件變數cond,多一個資源計數器,這個資源計數器用來對臨界資源進行計數,信號量通過判斷自身的資源計數器,來進行條件判斷,判斷當前資源是否可用,因此也就省去了while()這樣回圈判定資源的情況,
計數器大于零:則進行資源訪問
計數器小于零:則進行阻塞等待,直到被計數器++,大于零
2.posix信號量操作介面
前提: 在bash中輸入ipcs -s 看到的就是信號量
行程間本質: 在共享記憶體中創建的一塊,因此可以實作行程間同步和互斥
執行緒間本質: 定義成全域變數或者類的成員變數,因為各個執行緒同一塊虛擬地址空間
- 定義:
sem_t sem;定義了一個posix信號量 - 初始化:
sem_init(sem_t* sem,int pshared,int value)
用例:sem_init(con_,0,0);
sem:傳入信號量地址
pshared:表示當前信號量是用在行程間還是執行緒間
0:執行緒間使用
!0:行程間使用
value:用于初始化信號量當中資源計數器,表示實際的資源的數量 - 等待: wait介面用于請求“加鎖”,加鎖則計數器 - -
sem_wait(sem_t* sem)–>阻塞等待
sem_trywait(sem_t* sem)–>非阻塞等待
sem_timedwait(sem_t* sem,const struct timespec* timeval)–>帶有超時時間的等待 - 喚醒: post介面用于“解鎖”,并喚醒佇列,解鎖則計數器++
int sem_post(sem_t* sem);發布信號量,表示資源使用完成了,即:歸還資源或者生產者重新生產了一個資源,對信號量中的資源計數器+1,并且喚醒PCB等待佇列當中的PCB,
簡而言之:歸還或者生產了一個,每發布一個信號:資源計數器進行+1,wait介面里面-1 - 銷毀:
sem_destroy(sem_t* sem);釋放信號量開辟的記憶體
值得注意的是:
1.行程間: 當使用sem_init初始化信號量為行程間時,會在內核中創建一塊共享記憶體,來保存信號量的資料結構,其中資源計數器,PCB等待佇列都是在共享記憶體當中維護的,所以我們呼叫喚醒或者等待介面的時候,就通過操作共享記憶體實作了不同行程之間的通信,進而實作不同行程之間的同步與互斥,
2.執行緒間: 定義一個全域變數或者類的成員變數則都能訪問到,因為各個執行緒都同一塊虛擬地址空間
3.呼叫 wait 介面之后,如果資源計數器的值 > 0 則成功獲取信號量,如果資源計數器 < = 0 則阻塞該執行緒,進入PCB等待佇列,直到被喚醒
4.呼叫 wait 介面進行獲取信號量,不論是否獲取成功,都會對資源計數器-1
3.posix是如何保證同步和互斥的?
3.1 互測驗性

互斥:lock_
初始化時,必須初始化信號量中資源計數器的值為1,表示同一時刻只能有一個訪問lock信號量
sem_init(&lock_,0,1);
3.2 同步屬性

同步:pro_,con_
初始化的時候,根據資源的容量來進行初始化posix信號量當中的資源計數器
例如:資源容量為5,則生產者posix信號量資源計數器初始化為5,消費者posix信號量資源計數器初始化為0,表示當前無資源可用,等生產者訪問完臨界資源之后,會通知消費者sem,計數器進行++
sem_init(&pro_,0,capacity_);
sem_init(&con_,0,0);
4.實作生產消費模型
4.1 前提:使用POSIX信號量
- POSIX信號量初始化階段,會初始化一個資源的數量,意味著給資源計數器附一個初始值
- 每次獲取臨界資源之前,都需要先去獲取信號量,獲取信號量時,需要呼叫等待介面
如果等待介面沒有回傳:阻塞,則表示不能獲取信號量(資源計數器的值<=0)
如果等待介面回傳:則表示已經獲取信號量,可以正常訪問臨界資源 - 在訪問完成臨界資源之后,呼叫喚醒介面,會對信號量當中的計數器進行+1操作
- 釋放信號量的記憶體
4.2 使用信號量實作生產者與消費者模型
-
執行緒安全的佇列
陣列+先進先出的特性
posWrite、posRead
用PosWrite=(POSWrite+1)%capacity 計算位置,回圈讀寫 -
互斥
sem_t lock;互斥–>初始化后資源數為1 -
同步,消費者和生產者
sem_t Consume;消費者PCB等待佇列
讀:sem_init(&Consume,0,0);最后一個引數要初始化為0,表示當前沒有資源可用
sem_post(&Product):生產者的計數器+1,通知生產者PCB等待佇列sem_t Product;生產者PCB等待佇列
寫:sem_init(&Product,0,capacity);
sem_post(&Consume):可讀的空間+1,通知消費者PCB等待佇列
代碼實作:
#include<stdio.h>
#include<iostream>
#include<vector>
#include<unistd.h>
#include<semaphore.h>
#include<pthread.h>
#define THREADCOUNT 4
class RingQueue
{
private:
std::vector<int> vec_;
size_t Capacity_;
sem_t lock_;
sem_t pro_;
sem_t con_;
int WritePos;
int ReadPos;
public:
RingQueue(size_t SIZE)
:vec_(SIZE)
,Capacity_(SIZE)
{
sem_init(&lock_,0,1); //第二個引數屬性一般為0,第三個引數初始化數量
sem_init(&pro_,0,SIZE);
sem_init(&con_,0,0);
WritePos=0;
WritePos=0;
}
~RingQueue()
{
sem_destroy(&lock_);
sem_destroy(&pro_);
sem_destroy(&con_);
}
void Push(int& data)
{
sem_wait(&pro_);
sem_wait(&lock_);
vec_[WritePos]=data;
WritePos=(WritePos+1)%Capacity_;
sem_post(&lock_);
sem_post(&con_);
}
void Pop(int* data)
{
sem_wait(&con_);
sem_wait(&lock_);
*data=vec_[ReadPos];
ReadPos=(ReadPos+1)%Capacity_;
sem_post(&lock_);
sem_post(&pro_);
}
};
void* ProStart(void*arg)
{
RingQueue* rq=(RingQueue*)arg;
int data=1;
while(1)
{
rq->Push(data);
printf("pro:[%d]~~~\n",data);
data++;
sleep(1);
}
return NULL;
}
void* ConStart(void*arg)
{
RingQueue* rq=(RingQueue*)arg;
int data;
while(1)
{
rq->Pop(&data);
printf("---con:[%d]---\n",data);
sleep(1);
}
return NULL;
}
int main()
{
RingQueue* rq=new RingQueue(4);
pthread_t pro_tid[THREADCOUNT];
pthread_t con_tid[THREADCOUNT];
for(int i=0;i<THREADCOUNT;i++)
{
pthread_create(&pro_tid[i],NULL,ProStart,(void*)rq);
pthread_create(&con_tid[i],NULL,ConStart,(void*)rq);
}
for(int i=0;i<THREADCOUNT;i++)
{
pthread_join(pro_tid[i],NULL);
pthread_join(con_tid[i],NULL);
}
delete rq;
rq=NULL;
return 0;
}
5.實作互斥和同步的模板寫法
- Push:往佇列中存盤資料
前提: sem_init(&ProSem_,0, 陣列元素個數);------初始化資源計數器和陣列容量相同,代表可以多次生產
1) sem_wait(&ProSem_);------生產者計數器–,如果計數器>0,則繼續往下,否則入隊等待
2)sem_wait(&lock_); ------lock計數器–,鎖一般只能有一個訪問,若<0則入隊等待
3)arr[PosWrite] = 10;------訪問臨界資源
4)sem_post(&lock_); ------“解鎖”,計數器++,通知lock等待佇列
5)sem_post(&ConSem_);------消費者計數器++,通知消費者佇列 - Pop:從佇列中獲取資料
前提: sem_init(&ConSem_,0,0);------初始化消費者計數器為0,代表當前沒有資源可以使用,等生產者呼叫post會使這里的計數器++,
1)sem_wait(&ConSem_); ------.消費者計數器–,如果計數器>0,則繼續往下,否則入隊等待
2)sem_wait(&lock_); ------lock
3)arr[PosRead_]; ------訪問
4)sem_post(&lock_); ------“解鎖”,計數器++,通知lock等待佇列
5)sem_post(&ProSem_); ------生產者計數器++,通知消費者佇列
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/230398.html
標籤:其他
下一篇:競賽生的十條“軍規”
