文章目錄
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_); ------生產者計數器++,通知消費者隊列