线程间通信:信号量

线程间通信

进程间通信:
进程间由于进程是各自独立的,各自有各自的虚拟地址空间,所以想要进程之间进行通信,就必须在两个进程之前创建一个通道,作为通讯的媒介。pipe、fifo、msg、shm都是这样的。
线程间通信:
线程间的通信,由于他们是同一个进程的执行流,共享同一个虚拟地址空间,所以大部分资源都是一样的,想要线程之间进行通信,只需要一个全局变量就可以,或者在创建线程之前的一个变量。
存在问题:
无论是进程间通信还是线程之前的通信,都涉及到对资源的访问,由于cpu是随时间片的切换轮询调度的,所以就很容易存在线程安全问题(这种问题在线程是是最明显的),要想达到安全、合理的通信,就必须达到临界资源访问的互斥和同步。
实现同步的方式:
在说线程安全时提到,互斥锁实现互斥,条件变量和互斥锁搭配就可以实现同步,其实posix标准下的信号量,也为进程间和线程间的通信实现了互斥。

信号量

posix标准的信号量,
	实现进程间的同步与互斥,进程间的通信就这几种方式,它是使用共享内存实现的,多个进程都可以访问
	实现线程间的同步与互斥,线程通信灵活,使用全局变量就可以实现通信。只要是临界资源就可以通信
systemV标准的信号量
	本质是内核中的一个计数器

信号量的本质就是一个计数器+pcb等待队列
通过信号量可以实现同步和互斥,主要是同步
同步的实现:
通过自身的计数器对资源进行计数,并且通过计数器的资源计数,判断进程/线程是否符合访问资源的条件。
互斥的实现:保证计数器的计数不大于1,就保障了资源只有一个,同一时间只有一个pcb可以访问临界资源,实现互斥。

代码操作:

1.定义信号量sem_t;
2.初始化信号量:int sem_init(sem_t* sem,int pshared,unsigned int value);
	sem:定义一个信号量变量		
	pshared:0 - 用于线程之间   /   非0 - 用于进程之间
	value:初始化信号量的初值,初始资源有多少计数就是多少;
	返回值:成功返回0,失败返回-1;
3.P操作
	在访问临界资源之前,先访问信号量,判断是否可以访问,计数-1;
	int sem_wait(sem_t* sem);  -- 通过自身计数判断是否满足访问条件,不满足则直接一直阻塞线程/进程
	int sem_trywait(sem_t* sem);  -- 通过自身计数判断是否满足条件,不满足则直接报错返回,ETIMEDOUT
	int sem_timedwait(sem_t* sem,struct timespace* abs_timeout);
4.V操作
	促使访问条件满足之后,计数+1,唤醒阻塞的线程/进程
	int sem_post(sem_t* sem);    -- 通过信号量唤醒自己阻塞队列上的pcb;
5.销毁信号量
	void sem_destroy(sem_t* sem);

通过信号量实现一个生产者消费者模型/线程安全的环形队列

1.实现一个线程安全的环形队列  -- 使用数组实现
2.读写指针控制下标,实现出队入队
实现同步:
	使用计数器对资源进行计数
	对生产者来说,空闲节点就是资源   --_sem_pro 控制写指针
	对消费者来说,数据节点就是资源   --_sem_cum 控制读指针
实现互斥:
	这个过程中,队列的资源是都需要访问的 
	-- 计数器初始化为1,访问之前进行p操作,访问完成之后进行v操作,实现锁的功能

在这里插入图片描述
在这里插入图片描述

#include<iostream>                                                                                              
#include<cstdio>                                             
#include<semaphore.h>    
#include<vector>    
using namespace std;                                    
                    
#define MAX_NODE 5    
class RingQueue                                           
{                   
  private:    
    vector<int> q;    
    int capacity;    
    int _write_ptr;  //写指针(下标)    
    int _read_ptr;    
  private:                         
    sem_t _sem_data;        sem_t _sem_emp; //两个信号量代表两种资源,分别控制空数据和有数据    
    sem_t _sem_lock;    
  public:    
    //RingQueue(){}    
    RingQueue(int maxq = MAX_NODE):q(maxq),capacity(maxq),_write_ptr(0),_read_ptr(0)    
  {    
    //sem_init(信号量,标志位,资源数)  标志位:0-线程  1-进程  
        sem_init(&_sem_data,0,0);
    sem_init(&_sem_emp,0,maxq);
    sem_init(&_sem_lock,0,1);       //实现互斥的资源1表示可以访问,p操作之后小于0表示不能访问,即加锁的过程
  }
    ~RingQueue()                                                                                                
    {
      sem_destroy(&_sem_data);
      sem_destroy(&_sem_emp);
      sem_destroy(&_sem_lock);
    }
  public:
    //在这个过程中,sem本身自带同步与互斥,操作是原子性的,所以对数据节点和空闲节点的操作是原子性的,不需要加锁
    //semwait过来之后是直接阻塞的,如果在操作之前进行加锁,等进程过来的时候,可没有解锁这一步
    //这里面唯一需要保护的资源就是要插入数据的位置,即   写指针,所以必须在空闲节点-1之后加锁
    bool push(const int& data)
    {
      sem_wait(&_sem_emp);  //空闲节点-1
      sem_wait(&_sem_lock);  //对锁进行p操作,即加锁    这两步,必须是先进行p操作
      q[_write_ptr] = data;
      _write_ptr = (_write_ptr+1) % capacity;   //环形队列
      sem_post(&_sem_data);   //对消费者进行v操作,+1即唤醒进程
      sem_post(&_sem_lock);  //解锁            最后这两部先后顺序没有要求 
        return true;
    }
    bool pop(int *data)
    {
      sem_wait(&_sem_data);  //数据节点-1                                                                       
      sem_wait(&_sem_lock); 
      *data = q[_read_ptr];
      _read_ptr = (_read_ptr +1)&capacity;
      sem_post(&_sem_emp);    //空闲节点+1  
      sem_post(&_sem_lock);  
      return true;
    }
};

void* producter(void* arg)    
{    
  int data = 0;    
  RingQueue* q = (RingQueue*)arg;    
  while(1)    
  {    
    q->push(data);                                                                                              
    printf("put data %d\n",data++);  //注意插入和打印这块不是原子操作,如果线程多了,我们通过打印看到的结果有
    有可>能是不对的,但是其实际上只要逻辑闭环,就没有问题        
    //后++,插入之后数据变化一下    
  }    
  return NULL;    
} 
void* customer(void* arg)
{
  RingQueue* q = (RingQueue*)arg;
  while(1)
  {
    int data =0;  //由于我们接受数据这里,消费者拿到数据处理,这里的data是一个输出型参数,初不初始化一样
    q->pop(&data);
    printf("get data: %d\n",data--);
  }
  return NULL;                                                                                                  
}
int main()
{
  pthread_t ptid[3],ctid[3];
  RingQueue q;                                                                                                 
  int ret;
  for(int i =0;i<3;++i)
  {
    ret = pthread_create(&ptid[i],NULL,producter,&q);
    if(ret != 0)
      if(ret != 0)
      {
        printf("create producter thread %d failed!\n",i);
        return -1;
      }
    ret = pthread_create(&ctid[i],NULL,customer,&q);
    if(ret != 0)
    {
      printf("create customer thread %d failed!\n",i);                                                          
      return -1;
    }
  }
  for(int i=0;i<3;++i)
  {
    pthread_join(ptid[i],NULL);
    pthread_join(ctid[i],NULL);                                                                                 
  }
  return 0;
}

和条件变量进行区分:

1.条件变量和互斥锁搭配使用实现同步,条件变量可以实现同步
2.信号量是原子操作,cond_wait中解锁和阻塞是原子的
3.pthread_cond_wait()这个接口有三步操作,解锁,阻塞和加锁
而sem_wait()这个接口直接阻塞
所以cond在访问资源之前进行加锁,wait就可以解锁阻塞,sem在进行P操作之后,使用互斥sem加锁
如果在sem P操作之前加了锁,sem PV操作完成唤醒一个进程来再一次访问时,可不会解锁,直接阻塞,程序卡死。
这个顺序不能改变,否则就是逻辑闭环,死锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值