使用信号进行同步

信号是 E. W. Dijkstra 在二十世纪六十年代末设计的一种编程架构。Dijkstra 的模型与铁路操作有关:假设某段铁路是单线的,因此一次只允许一列火车通过。

信号将用于同步通过该轨道的火车。火车在进入单一轨道之前必须等待信号灯变为允许通行的状态。火车进入轨道后,会改变信号状态,防止其他火车进入该轨道。火车离开这段轨道时,必须再次更改信号的状态,以便允许其他火车进入轨道。

在计算机版本中,信号以简单整数来表示。线程等待获得许可以便继续运行,然后发出信号,表示该线程已经通过针对信号执行 P 操作来继续运行。

线程必须等到信号的值为正,然后才能通过将信号值减 1 来更改该值。完成此操作后,线程会执行 V 操作,即通过将信号值加 1 来更改该值。这些操作必须以原子方式执行,不能再将其划分成子操作,即,在这些子操作之间不能对信号执行其他操作。在 P 操作中,信号值在减小之前必须为正,从而确保生成的信号值不为负,并且比该值减小之前小 1。

PV 操作中,必须在没有干扰的情况下进行运算。如果针对同一信号同时执行两个 V 操作,则实际结果是信号的新值比原来大 2。

对于大多数人来说,如同记住 Dijkstra 是荷兰人一样,记住 PV 本身的含义并不重要。但是,从真正学术的角度来说,P 代表 prolagen,这是由 proberen te verlagen 演变而来的杜撰词,其意思是尝试减小V 代表 verhogen,其意思是增加。Dijkstra 的技术说明 EWD 74 中介绍了这些含义。

sem_wait(3RT) 和 sem_post(3RT) 分别与 Dijkstra 的 PV 操作相对应。sem_trywait(3RT) 是 P 操作的一种条件形式。如果调用线程不等待就不能减小信号的值,则该调用会立即返回一个非零值。

有两种基本信号:二进制信号和计数信号量。二进制信号的值只能是 0 或 1,计数信号量可以是任意非负值。二进制信号在逻辑上相当于一个互斥锁。

不过,尽管不会强制,但互斥锁应当仅由持有该锁的线程来解除锁定。因为不存在“持有信号的线程”这一概念,所以,任何线程都可以执行 Vsem_post(3RT) 操作。

计数信号量与互斥锁一起使用时的功能几乎与条件变量一样强大。在许多情况下,使用计数信号量实现的代码比使用条件变量实现的代码更为简单,如示例 4–14示例 4–15示例 4–16 中所示。

但是,将互斥锁用于条件变量时,会存在一个隐含的括号。该括号可以清楚表明程序受保护的部分。对于信号则不必如此,可以使用并发编程当中的 go to 对其进行调用。信号的功能强大,但是容易以非结构化的不确定方式使用。

命名信号和未命名信号

POSIX 信号可以是未命名的,也可以是命名的。未命名信号在进程内存中分配,并会进行初始化。未命名信号可能可供多个进程使用,具体取决于信号的分配和初始化的方式。未命名信号可以是通过 fork() 继承的专用信号,也可以通过用来分配和映射这些信号的常规文件的访问保护功能对其进行保护。

命名信号类似于进程共享的信号,区别在于命名信号是使用路径名而非 pshared 值引用的。命名信号可以由多个进程共享。命名信号具有属主用户 ID、组 ID 和保护模式。

对于 openretrievecloseremove 命名信号,可以使用以下函数:sem_open、sem_getvalue、sem_close 和 sem_unlink。通过使用 sem_open,可以创建一个命名信号,其名称是在文件系统的名称空间中定义的。

有关命名信号的更多信息,请参见 sem_open、sem_getvalue、sem_close 和 sem_unlink 手册页。

计数信号量概述

从概念上来说,信号量是一个非负整数计数。信号量通常用来协调对资源的访问,其中信号计数会初始化为可用资源的数目。然后,线程在资源增加时会增加计数,在删除资源时会减小计数,这些操作都以原子方式执行。

如果信号计数变为零,则表明已无可用资源。计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止。

表 4–7 信号例程

 

操作

相关函数说明

初始化信号

sem_init 语法

增加信号

sem_post 语法

基于信号计数阻塞

sem_wait 语法

减小信号计数

sem_trywait 语法

销毁信号状态

sem_destroy 语法

由于信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中。同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁。但是,信号的效率不如互斥锁高。

缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的。

信号在使用前必须先初始化,但是信号没有属性。

初始化信号

使用 sem_init(3RT) 可以将 sem 所指示的未命名信号变量初始化为 value

sem_init 语法

int        sem_init(sem_t *sem, int pshared, unsigned int value);
#include <semaphore.h>

sem_t sem;

int pshared;

int ret;

int value;

/* initialize a private semaphore */

pshared = 0;

value = 1;

ret = sem_init(&sem, pshared, value);

如果 pshared 的值为零,则不能在进程之间共享信号。如果 pshared 的值不为零,则可以在进程之间共享信号。对于 Solaris 线程,请参见sema_init 语法

多个线程决不能初始化同一个信号。

不得对其他线程正在使用的信号重新初始化。

初始化进程内信号

pshared 为 0 时,信号只能由该进程内的所有线程使用。

#include <semaphore.h>

sem_t sem;

int ret;

int count = 4;

/* to be used within this process only */

ret = sem_init(&sem, 0, count);

初始化进程间信号

pshared 不为零时,信号可以由其他进程共享。

#include <semaphore.h>

sem_t sem;

int ret;

int count = 4;

/* to be shared among processes */

ret = sem_init(&sem, 1, count);

sem_init 返回值

sem_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

参数值超过了 SEM_VALUE_MAX

ENOSPC

描述:

初始化信号所需的资源已经用完。到达信号的 SEM_NSEMS_MAX 限制。

ENOSYS

描述:

系统不支持 sem_init() 函数。

EPERM

描述:

进程缺少初始化信号所需的适当权限。

增加信号

使用 sem_post(3RT) 可以原子方式增加 sem 所指示的信号。

sem_post 语法

int        sem_post(sem_t *sem);
#include <semaphore.h>

sem_t sem;

int ret;

ret = sem_post(&sem); /* semaphore is posted */

如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。对于 Solaris 线程,请参见sema_post 语法

sem_post 返回值

sem_post() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

sem 所指示的地址非法。

基于信号计数进行阻塞

使用 sem_wait(3RT) 可以阻塞调用线程,直到 sem 所指示的信号计数大于零为止,之后以原子方式减小计数。

sem_wait 语法

int        sem_wait(sem_t *sem);
#include <semaphore.h>

sem_t sem;

int ret;

ret = sem_wait(&sem); /* wait for semaphore */

sem_wait 返回值

sem_wait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

sem 所指示的地址非法。

EINTR

描述:

此函数已被信号中断。

减小信号计数

使用 sem_trywait(3RT) 可以在计数大于零时,尝试以原子方式减小 sem 所指示的信号计数。

sem_trywait 语法

int        sem_trywait(sem_t *sem);
#include <semaphore.h>

sem_t sem;

int ret;

ret = sem_trywait(&sem); /* try to wait for semaphore*/

此函数是 sem_wait() 的非阻塞版本。sem_trywait() 在失败时会立即返回。

sem_trywait 返回值

sem_trywait() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

sem 所指示的地址非法。

EINTR

描述:

此函数已被信号中断。

EAGAIN

描述:

信号已为锁定状态,因此该信号不能通过 sem_trywait() 操作立即锁定。

销毁信号状态

使用 sem_destroy(3RT) 可以销毁与 sem 所指示的未命名信号相关联的任何状态。

sem_destroy 语法

int        sem_destroy(sem_t *sem);
#include <semaphore.h>

sem_t sem;

int ret;

ret = sem_destroy(&sem); /* the semaphore is destroyed */

不会释放用来存储信号的空间。对于 Solaris 线程,请参见sema_destroy(3C) 语法

sem_destroy 返回值

sem_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EINVAL

描述:

sem 所指示的地址非法。

使用信号时的生成方和使用者问题

示例 4–14 中的数据结构与示例 4–11 中所示的用于条件变量示例的结构类似。两个信号分别表示空缓冲区和满缓冲区的数目,通过这些信号可确保生成方等待缓冲区变空,使用者等待缓冲区变满为止。


示例 4–14 使用信号时的生成方和使用者问题
typedef struct {

    char buf[BSIZE];

    sem_t occupied;

    sem_t empty;

    int nextin;

    int nextout;

    sem_t pmut;

    sem_t cmut;

} buffer_t;

buffer_t buffer;

sem_init(&buffer.occupied, 0, 0);

sem_init(&buffer.empty,0, BSIZE);

sem_init(&buffer.pmut, 0, 1);

sem_init(&buffer.cmut, 0, 1);

buffer.nextin = buffer.nextout = 0;

另一对二进制信号与互斥锁作用相同。在多个生成方使用多个空缓冲槽位,以及多个使用者使用多个满缓冲槽位的情况下,信号可用来控制对缓冲区的访问。在这种情况下,使用互斥锁可能会更好,但这里主要是为了演示信号的用法。


示例 4–15 生成方和使用者问题:生成方
void producer(buffer_t *b, char item) {

    sem_wait(&b->empty);

    sem_wait(&b->pmut);

    b->buf[b->nextin] = item;

    b->nextin++;

    b->nextin %= BSIZE;

    sem_post(&b->pmut);

    sem_post(&b->occupied);

}


示例 4–16 生成方和使用者问题:使用者
char consumer(buffer_t *b) {

    char item;

    sem_wait(&b->occupied);

    sem_wait(&b->cmut);

    item = b->buf[b->nextout];

    b->nextout++;

    b->nextout %= BSIZE;

    sem_post(&b->cmut);

    sem_post(&b->empty);

    return(item);

}
 

使用信号进行同步 sem_post的更多相关文章

  1. Linux互斥和同步应用程序(四):posix互斥信号和同步

           [版权声明:尊重原创.转载请保留源:blog.csdn.net/shallnet 要么 .../gentleliu,文章仅供学习交流,请勿用于商业用途]          在前面讲共享内 ...

  2. linux 异步信号的同步处理方式

    关于代码的可重入性,设计开发人员一般只考虑到线程安全,异步信号处理函数的安全却往往被忽略.本文首先介绍如何编写安全的异步信号处理函数:然后举例说明在多线程应用中如何构建模型让异步信号在指定的线程中以同 ...

  3. 嵌入式开发之信号采集同步---VSYNC和HSYNC的作用以及它们两者之间的关系

    VSYNC和HSYNC的作用以及它们两者之间的关系 VSYNC和HSYNC的作用以及它们两者之间的关系 VSYNC和HSYNC是什么 VSYNC: vertical synchronization,指 ...

  4. 线程同步之信号量(sem_init,sem_post,sem_wait)

    信号量和互斥锁(mutex)的区别:互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区. 不多做解释,要使用信号量同步,需要包含头文件semaphore.h. 主要用到的函数: int ...

  5. Sensor信号输出YUV、RGB、RAW DATA、JPEG【转】

    本文转载自:http://blog.csdn.net/southcamel/article/details/8305873 简单来说,YUV: luma (Y) + chroma (UV) 格式, 一 ...

  6. [置顶] Linux信号相关笔记

    最近又温习了一遍Linux中的信号知识,发现有很多东西以前没有注意到,就通过这篇博客记录一下,巩固一下知识点. 一,信号基础: 信号是什么?为了回答这个问题,首先要从异常说起,这里的异常不是指c++/ ...

  7. Android 4.4(KitKat)中VSync信号的虚拟化

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/17293325 Android 4.1(Jelly Bean)引入了Vsync(Vertic ...

  8. openrisc 之 Wishbone总线学习笔记——接口信号定义

    这部分内容就是copy下来的,网上到处都有.先看看接口啥样子,在详细说明 接口定义copy http://blog.csdn.net/ce123/article/details/6929897.百度文 ...

  9. 1.2 PCI总线的信号定义

    PCI总线是一条共享总线,在一条PCI总线上可以挂接多个PCI设备.这些PCI设备通过一系列信号与PCI总线相连,这些信号由地址/数据信号.控制信号.仲裁信号.中断信号等多种信号组成. PCI总线是一 ...

随机推荐

  1. Leetcode 337. House Robber III

    337. House Robber III Total Accepted: 18475 Total Submissions: 47725 Difficulty: Medium The thief ha ...

  2. ++i? i++? i+=1? i=i+1? 何必纠结?

    前言 今天在牛客上看面经,看到一个问题:num++; num+=1; num = num +1; 哪个效率最高? 自从学习C语言开始,我就在纠结for语言应该写i++,还是++i,其实这个问题,可以通 ...

  3. Android四大组件--服务(Service)

    1. startService和bindService的区别 1. startService: 生命周期: onCreate---onStartCommand---onDestory 与服务的通讯: ...

  4. 使用 Angular 和 RxJS 实现的无限滚动加载

    无限滚动加载应该是怎样的? 无限滚动加载列表在用户将页面滚动到指定位置后会异步加载数据.这是避免寻主动加载(每次都需要用户去点击)的好方法,而且它能真正保持应用的性能.同时它还是降低带宽和增强用户体验 ...

  5. 关于vue.js中slot的理解

    slot这块看官网文档,起初有点不懂,仔细研究还是最终理解了,slot是用来干嘛的呢,先看下一个例子: <script src="https://unpkg.com/vue/dist/ ...

  6. [javaSE] 多线程(join方法)

    多条线程并发执行,随机切换,调用join()方法,会使当前线程所在的线程(一般主线程)冻结,直到当前线程结束,所在的线程才恢复继续执行 class JoinTestDemo implements Ru ...

  7. 使用gunzip、tar、rar、(zip压缩和unzip解压缩)

    ---------------------20171119------------------------------ 解压gz后缀 使用gunzip filename.gz ------------ ...

  8. R 语言—基本绘图

    https://www.harding.edu/fmccown/r/   这个网站上有壮观的 R 绘制的实际图形 下面只记录自己感兴趣的内容 单变量绘图下包含 1. 带状图 2. 茎叶图 3. 直方图 ...

  9. CentOS 安装Parallels Tools

    为了做到Mac和Linux之间共享文件夹,因此需要安装Parallels Tool, 具体安装步骤可以参考 install parallels tool for linux guest http:// ...

  10. C#学习笔记(基础知识回顾)之枚举

    一:枚举的含义 枚举是用户定义的整数类型.在声明一个枚举时,要指定该枚举的示例可以包含的一组可接受的值.还可以给值指定易于记忆的名称.个人理解就是为一组整数值赋予意义. 二:枚举的优势 2.1:枚举可 ...