一.POSIX信號量
- 實現進程/線程間同步與互斥
- 本質:計數器+等待隊列+等待+喚醒接口
- 與條件變量對比:多了一個資源計數器,對臨界資源的計數,來進行條件判斷,是否可以對資源進行訪問。若不能訪問當前執行流,則阻塞當前執行流。
- 依然接口爲庫函數。
1.使用
- 在程序初始化階段,會根據資源的一個數量來初始化POSIX信號量(計數器),當執行流需要獲取資源的時候,調用信號量接口,來判斷信號量當中的計數器是否大於0,如果大於0,計數器-1,直接返回,獲取數據。如果不大於0,則阻塞該執行流。
2.接口
頭文件
#include<semaphore.h>
1.定義
sem_t sem;
2.初始化
sem_init(sem_t * sem,int pshared,int value)
sem:傳入信號量的地址
pshared:0代表線程 1代表進程
value:實際資源個數,用於信號量初始化
3.等待
sem_wait(sem_t* sem)----沒有資源就阻塞等待,等待信號量,將信號量計數器的值-1
sem_trywait(sem_t* sem)----沒有資源就報錯返回
4.喚醒(發佈信號量,資源使用完畢,可以歸還資源,信號量計數器+1)
- 當執行流產生一個資源,是否需要喚醒
計數器小於0,對計數器+1,則喚醒一個執行流。
計數器小於0,對計數器+1,不喚醒執行流。
int sem_post(sem_t* sem)
5.銷燬
int sem_destroy(sem_t* sem)
3.信號量實現互斥功能
- 保證初始化資源數量爲1即可實現互斥。
4.使用POSIX信號量實現生產者與消費者模型
- 基於一個安全的環形隊列實現。可以採用數組,先進先出即可。
- 當寫滿時,藉助計數器進行阻塞。(初始化告訴計數器數組大小)
#include <semaphore.h>
#include <pthread.h>
#include <cstdio>
#include <iostream>
#include <vector>
#define SIZE 1
#define THREADCOUNT 4
class RingQueue
{
public:
RingQueue()
:Vec_(SIZE)
{
Capacity_ = SIZE;
PosWrite_ = 0;
PosRead_ = 0;
//初始化同步的信號量
//生產者生產的時候就看我們有多少空間可以供我們去生產
sem_init(&ProSem_, 0, SIZE);
//消費者初始化的時候是看當前有多少資源可以消費
sem_init(&ConSem_, 0, 0);
//初始化互斥
sem_init(&LockSem_, 0, 1);
}
~RingQueue()
{
sem_destroy(&ProSem_);
sem_destroy(&ConSem_);
sem_destroy(&LockSem_);
}
void Push(int& Data)
{
//對於加鎖而言,由於sem_wait並沒有通知LockSem進行釋放資源的操作,sem_wait(&ProSem_)內部並沒有 sem_post(&LockSem_)功能
//如果現在需要往vec_裏面插入數據的時候
//當時空間已經全部被插入了,沒有空間了
//卡在了sem_wait(&ProSem_);阻塞等待 消費者來通知進行生產
//但是這會兒由於生產者把鎖資源拿着,促使消費者想要消費數據的時候,拿不到
//所以必須把LockSem放裏面!
sem_wait(&ProSem_);
sem_wait(&LockSem_);
Vec_[PosWrite_] = Data;
PosWrite_ = (PosWrite_ + 1) % Capacity_;
sem_post(&LockSem_);
//資源進行+1操作,並且喚醒消費者
sem_post(&ConSem_);
}
void Pop(int* Data)
{
sem_wait(&ConSem_);
sem_wait(&LockSem_);
*Data = Vec_[PosRead_];
PosRead_ = (PosRead_ + 1) % Capacity_;
sem_post(&LockSem_);
sem_post(&ProSem_);
}
private:
std::vector<int> Vec_;
size_t Capacity_;
int PosWrite_;//讀位置
int PosRead_;//寫位置
//同步功能的信號量
//生產者的信號量
sem_t ProSem_;
//消費者的信號量
sem_t ConSem_;
//實現互斥
sem_t LockSem_;
};
void* ProStart(void* arg)
{
RingQueue* rq = (RingQueue*)arg;
int i = 0;
while(1)
{
rq->Push(i);
printf("ProStart make data [%p][%d]\n", pthread_self(), i);
i++;
}
return NULL;
}
void* ConStart(void* arg)
{
RingQueue* rq = (RingQueue*)arg;
int Data;
while(1)
{
rq->Pop(&Data);
printf("ConStart consume data [%p][%d]\n", pthread_self(), Data);
}
return NULL;
}
int main()
{
pthread_t Pro_tid[THREADCOUNT], Con_tid[THREADCOUNT];
int i = 0;
int ret = -1;
RingQueue* rq = new RingQueue();
for(; i < THREADCOUNT; i++)
{
ret = pthread_create(&Pro_tid[i], NULL, ProStart, (void*)rq);
if(ret != 0)
{
printf("create thread failed\n");
return 0;
}
ret = pthread_create(&Con_tid[i], NULL, ConStart, (void*)rq);
if(ret != 0)
{
printf("create thread failed\n");
return 0;
}
}
for(i = 0; i < THREADCOUNT; i++)
{
pthread_join(Pro_tid[i], NULL);
pthread_join(Con_tid[i], NULL);
}
delete rq;
return 0;
}
Makefile
g++$^ -o $@ -g -lpthread
二.讀寫鎖
- 使用場景:少量寫,大量讀。
- 與互斥鎖類似,允許更高的並行
- 讀寫鎖三種狀態
1.讀模式下的加鎖
2.寫模式下的加鎖狀態
3.不加鎖的狀態
1.加鎖規則
- 1.不能同時寫,可以同時讀
- 2.一個執行流寫時,其他執行流不能讀也不能寫
- 3.一個執行流讀時,其他執行流可以讀不能寫
細則
- 1.一次只有一個線程佔有寫模式的讀寫鎖
- 2.但是可以有多個線程同時佔有讀模式的讀寫鎖
問題1:已經有多個線程以讀模式獲取讀寫鎖,這時,有一個線程想以寫模式獲取讀寫鎖。
A:阻塞該進程,直到所有以讀模式打開的讀寫鎖全部釋放。
問題2:已經有多個線程以讀模式獲取讀寫鎖,這時,有一個線程想以寫模式獲取讀寫鎖,後面也有一些想讀的線程。會不會導致寫模式的線程一直阻塞?
A:通常不會長時間阻塞寫模式的線程。操作系統通常會阻塞後來想讀的線程,保證寫模式的線程不會等待太長時間。也避免鎖資源被長時間的佔用。
2.接口
1.定義
pthread_rwlock_t rwlock;
2.初始化
pthread_rwlock_init(pthread_rwlock_t*,pthread_rwlockattr_t*)
3.加鎖
pthread_rwlock_rdlock(pthread_rwlock_t* )
pthread_rwlock_wrlock(pthread_rwlock_t* )
4.解鎖
pthread_rwlock_unlock(pthread_rwlock_t* )
5.銷燬
pthread_rwlock_destroy(pthread_rwlock_t* )