【Linux】posix信号量,线程的二义性一次解决!!

1.什么是posix信号量?

  1. posix信号量可以完成进程间和线程间的同步与互斥
  2. 本质:信号量 = 资源计数器 (判断资源是否可用) + PCB等待队列 (生产者、消费者和lock) + 等待和唤醒接口
  3. 对比条件变量cond,多一个资源计数器,这个资源计数器用来对临界资源进行计数。信号量通过判断自身的资源计数器,来进行条件判断,判断当前资源是否可用。因此也就省去了while()这样循环判定资源的情况。
    计数器大于零:则进行资源访问
    计数器小于零:则进行阻塞等待,直到被计数器++,大于零

2.posix信号量操作接口

前提: 在bash中输入ipcs -s 看到的就是信号量
进程间本质: 在共享内存中创建的一块,因此可以实现进程间同步和互斥
线程间本质: 定义成全局变量或者类的成员变量,因为各个线程同一块虚拟地址空间

  1. 定义:
    sem_t sem;定义了一个posix信号量
  2. 初始化:
    sem_init(sem_t* sem,int pshared,int value)
    用例:sem_init(con_,0,0);
    sem:传入信号量地址
    pshared:表示当前信号量是用在进程间还是线程间
    0:线程间使用
    !0:进程间使用
    value:用于初始化信号量当中资源计数器,表示实际的资源的数量
  3. 等待: wait接口用于请求“加锁”,加锁则计数器 - -
    sem_wait(sem_t* sem)–>阻塞等待
    sem_trywait(sem_t* sem)–>非阻塞等待
    sem_timedwait(sem_t* sem,const struct timespec* timeval)–>带有超时时间的等待
  4. 唤醒: post接口用于“解锁”,并唤醒队列,解锁则计数器++
    int sem_post(sem_t* sem);发布信号量,表示资源使用完成了,即:归还资源或者生产者重新生产了一个资源,对信号量中的资源计数器+1,并且唤醒PCB等待队列当中的PCB。
    简而言之:归还或者生产了一个,每发布一个信号:资源计数器进行+1,wait接口里面-1
  5. 销毁:
    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信号量

  1. POSIX信号量初始化阶段,会初始化一个资源的数量,意味着给资源计数器附一个初始值
  2. 每次获取临界资源之前,都需要先去获取信号量,获取信号量时,需要调用等待接口
    如果等待接口没有返回:阻塞,则表示不能获取信号量(资源计数器的值<=0)
    如果等待接口返回:则表示已经获取信号量,可以正常访问临界资源
  3. 在访问完成临界资源之后,调用唤醒接口,会对信号量当中的计数器进行+1操作
  4. 释放信号量的内存

4.2 使用信号量实现生产者与消费者模型

  1. 线程安全的队列
    数组+先进先出的特性
    posWrite、posRead
    用PosWrite=(POSWrite+1)%capacity 计算位置,循环读写

  2. 互斥
    sem_t lock;互斥–>初始化后资源数为1

  3. 同步,消费者和生产者
    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.实现互斥和同步的模板写法

  1. 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_);------消费者计数器++,通知消费者队列
  2. 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_); ------生产者计数器++,通知消费者队列
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值