一.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* )