C(Unix)匿名信号量

最近编写AWS IOT 部分代码,发送设备消息时会引入多线程就会引入资源竞争,比如多个线程同时想发送消息进而导致资源占用,即会导致一个线程获得资源,另一个线程则会进入等待状态。

而如何等待则就是一个比较细节的内容。而在维基百科上有对忙碌等待描述:进程反复检查一个条件是否为真为根本的技术。忙碌等待大部分情况很明显是应该避免的,所以两个线程竞争,一个线程获取到锁,另一个线程获取失败采用忙碌等待的方式去查询状态则会十分低效。

在同事的建议下,发现信号量就很适合这种场景下的线程同步。


1 信号量是什么

1.1 信号量的行为

信号量(英语:semaphore)又称为信号标,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。

也就是说每一次release操作 信号量就会加1 ,每次wait信号量就会-1,当wait操作后信号量等于或小于0,则会在等待函数那里一直进行等待。而利用这种机制就可以用来进行线程间同步,比如一个线程wait 后进入等待后,另一个线程release 资源,就会唤醒前一个线程。

在Linux中信号量有两种标准,一种是System V,另一种是Posix标准。其中System V是早期的标准;
Posix(Portable Operating System Interface )是一个由IEEE开发的一系列标准,它还是由ISO(国际标准化组织)和IEC(国际电工委员会)采纳的国际标准。而System v是Unix操作系统众多版本的一个分支。本文基于Posix标准进行设计。

1.2 信号量使用场景

semaphore对象适用于控制一个仅支持有限个用户的共享资源,是一种不需要使用忙碌等待(busy waiting)的方法。

关于信号使用的资源开销:
执行V操作之后若sem小于等于0,则阻塞队列中阻塞的线程或进程个数为|sem|+1个;而阻塞线程或进程是存放在一个阻塞链表中的,会保证顺序依次被唤醒,这一特性就能实现大于3个数量的线程或进程实现互斥。但是由于阻塞会牵扯到睡眠,再唤醒需要线程或进程上下文切换,切换是非常耗费时间的。因此当信号量对被保护的资源占用的时间比线程或进程切换时间长很多的时候,可以选择应用信号量。

semaphore信号量和互斥锁的区别:semaphore信号量会将竞争的线程挂起,保证前一个线程从阻塞态释放后后一个线程能够被处理。而mutex则不保证这一点(可能一个线程恰好每次轮到它执行,而对资源独占或者分配不均的情况)。

2 匿名信号量相关API

匿名信号主要涉及下面四个API:
sem_init
sem_wait
sem_post

int sem_init(sem_t *sem, int pshared, unsigned int value);

sem_init() 初始化一个定位在 sem 的匿名信号量
sem :指向信号量对象
pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。
value : 指定信号量值的大小

int sem_wait(sem_t *sem);

sem_wait是一个函数,也是一个原子操作l它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。如果对一个值为0的信号量调用sem_wait(),这个函数就会原地等待直到有其它线程增加了这个值使它不再是0为止
sem :指向信号量对象

int sem_post(sem_t *sem);

sem_post函数的作用是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的。
sem :指向信号量对象

int sem_destroy(sem_t *sem);

销毁由sem指向的匿名信号量。
sem :指向信号量对象

获取信号量值并存入 valp中

int sem_getvalue(sem_t *sem, int *valp);

3 Sample

下面是个一个多生产者 单消费者的场景。可以比较好体现信号量的使用。
效果是两个生产产生需要发送的数据 一个产生10条,一个消费者发送这些数据。

sample 代码创建了两个生产者线程produce proudce2,可以理解为实际应用在需要发送的数据产生线程。
创建了一个消费者线程consume ,可以理解为用来发送数据的网络线程。

这里用到了两个信号量,consumeSignal, produceSignal。
consumeSignal 信号量在消费者线程消费者线程调用sem_wait函数,等待生产者产生需要发送的数据。当生产者线程产生需要发送的数据后,sem_post() 唤醒消费者线程进行数据发送。
produceSignal信号量用于两个生产这线程同步,及确保两个生产者线程不会同时操作到共享资源。即一个生产线程产生数据到全局变量时,另个一生产者线程需要进行等待。

#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <stdlib.h>
int dataBuffer[10];
int dataProduceIndex = 0;
int dataConsumeIndex = 0;
sem_t consumeSignal;
sem_t produceSignal;
void *consume(void *arg);
void *produce(void *arg);
void *produce2(void *arg);
pthread_t tidConsume;
pthread_t tidProduce1;
pthread_t tidProduce2;

int main(){
	sem_init(&consumeSignal, 0, 0); 
  sem_init(&produceSignal, 0, 1); 
  int tickClock = 0;
  pthread_create(&tidProduce1, NULL, produce, NULL);
  pthread_create(&tidProduce2, NULL, produce2, NULL);
  pthread_create(&tidConsume, NULL, consume, NULL);
  while(1){
    sleep(2);
    if(dataConsumeIndex>10){
      break;
    }
  }
	sem_destroy(&consumeSignal);
  sem_destroy(&produceSignal);

  return 0;
}
void *consume(void *arg)
{
	while(1){
    sem_wait(&consumeSignal);
    if(dataConsumeIndex > 10){
      printf("cunsume exit\n");
      pthread_exit();
    }
    printf("consume count %d send %ld by network\n", dataConsumeIndex, dataBuffer[dataConsumeIndex]);
    dataConsumeIndex++;
  }
}
void *produce(void *arg)
{
	while(1){
    sem_wait(&produceSignal);
    if(dataProduceIndex > 10){
      printf("produce exit\n");
      pthread_exit();
    }
    dataBuffer[dataProduceIndex] = random();
    printf("produce1 count %d  want send %ld \n", dataProduceIndex, dataBuffer[dataProduceIndex]);
    dataProduceIndex++;
    sem_post(&consumeSignal);
    sem_post(&produceSignal);
  }
}
void *produce2(void *arg)
{
	while(1){
    sem_wait(&produceSignal);
    if(dataProduceIndex > 10){
       printf("produce2 exit\n");
       pthread_exit();
    }
    dataBuffer[dataProduceIndex] = random();
    printf("produce2 count %d want send %ld\n", dataProduceIndex, dataBuffer[dataProduceIndex]);
    dataProduceIndex++;
    sem_post(&consumeSignal);
    sem_post(&produceSignal);
  }
}

编译:
gcc sem.c -o test
测试 打印一下内容:

produce1 count 0  want send 1804289383
consume count 0 send 1804289383 by network
produce2 count 1 want send 846930886
consume count 1 send 846930886 by network
produce1 count 2  want send 1681692777
consume count 2 send 1681692777 by network
produce2 count 3 want send 1714636915
consume count 3 send 1714636915 by network
produce1 count 4  want send 1957747793
produce2 count 5 want send 424238335
consume count 4 send 1957747793 by network
produce1 count 6  want send 719885386
consume count 5 send 424238335 by network
produce2 count 7 want send 1649760492
consume count 6 send 719885386 by network
produce1 count 8  want send 596516649
consume count 7 send 1649760492 by network
produce2 count 9 want send 1189641421
consume count 8 send 596516649 by network
produce1 count 10  want send 1025202362
consume count 9 send 1189641421 by network
produce2 exit
consume count 10 send 1025202362 by network
cunsume exit
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章