文章目录
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_); ------生产者计数器++,通知消费者队列