
多线程-信号量.doc
9页信号是 E.W.Dijkstra 在二十世纪六十年代末设计的一种编程架构Dijkstra 的模型与铁路操作有关:假设某段铁路是单线的,因此一次只允许一列火车通过信号将用于同步通过该轨道的火车火车在进入单一轨道之前必须等待信号灯变为允许通行的状态火车进入轨道后,会改变信号状态,防止其他火车进入该轨道火车离开这段轨道时,必须再次更改信号的状态,以便允许其他火车进入轨道在计算机版本中,信号以简单整数来表示线程等待获得许可以便继续运行,然后发出信号,表示该线程已经通过针对信号执行 P 操作来继续运行线程必须等到信号的值为正,然后才能通过将信号值减 1 来更改该值完成此操作后,线程会执行 V 操作,即通过将信号值加 1 来更改该值这些操作必须以原子方式执行,不能再将其划分成子操作,即,在这些子操作之间不能对信号执行其他操作在 P 操作中,信号值在减小之前必须为正,从而确保生成的信号值不为负,并且比该值减小之前小 1在 P 和 V 操作中,必须在没有干扰的情况下进行运算如果针对同一信号同时执行两个 V 操作,则实际结果是信号的新值比原来大 2对于大多数人来说,如同记住Dijkstra 是荷兰人一样,记住 P 和 V 本身的含义并不重要。
但是,真正学术的角度来说,P 代表 prolagen,这是由 proberen te verlagen 演变而来的杜撰词,其意思是尝试减小V 代表 verhogen,其意思是增加Dijkstra 的技术说明 EWD74 中介绍了这些含义sem_wait(3RT)和 sem_post(3RT)分别与 Dijkstra 的 P 和 V 操作相对应sem_trywait(3RT)是P 操作的一种条件形式如果调用线程不等待就不能减小信号的值,则该调用会立即返回一个非零值有两种基本信号:二进制信号和计数信号量二进制信号的值只能是 0 或1,计数信号量可以是任意非负值二进制信号在逻辑上相当于一个互斥锁不过,尽管不会强制,但互斥锁应当仅由持有该锁的线程来解除锁定因为不存在“持有信号的线程”这一概念,所以,任何线程都可以执行 V 或 sem_post(3RT)操作计数信号量与互斥锁一起使用时的功能几乎与条件变量一样强大在许多情况下,使用计数信号量实现的代码比使用条件变量实现的代码更为简单但是,将互斥锁用于条件变量时,会存在一个隐含的括号该括号可以清楚表明程序受保护的部分对于信号则不必如此,可以使用并发编程当中的 go to 对其进行调用。
信号的功能强大,但是容易以非结构化的不确定方式使用1 命名信号量和未命名信号量POSIX 信号可以是未命名的,也可以是命名的未命名信号在进程内存中分配,并会进行初始化未命名信号可能可供多个进程使用,具体取决于信号的分配和初始化的方式未命名信号可以是通过 fork()继承的专用信号,也可以通过用来分配和映射这些信号的常规文件的访问保护功能对其进行保护命名信号类似于进程共享的信号,区别在于命名信号是使用路径名而非 pshared 值引用的命名信号可以由多个进程共享命名信号具有属主用户 ID、组 ID 和保护模式对于 open、retrieve、close 和 remove 命名信号,可以使用以下函数:sem_open、sem_getvalue、sem_close 和 sem_unlink通过使用 sem_open,可以创建一个命名信号,其名称是在文件系统的名称空间中定义的2 计数信号量概述从概念上来说,信号量是一个非负整数计数信号量通常用来协调对资源的访问,其中信号计数会初始化为可用资源的数目然后,线程在资源增加时会增加计数,在删除资源时会减小计数,这些操作都以原子方式执行如果信号计数变为零,则表明已无可用资源。
计数为零时,尝试减小信号的线程会被阻塞,直到计数大于零为止由于信号无需由同一个线程来获取和释放,因此信号可用于异步事件通知,如用于信号处理程序中同时,由于信号包含状态,因此可以异步方式使用,而不用象条件变量那样要求获取互斥锁但是,信号的效率不如互斥锁高缺省情况下,如果有多个线程正在等待信号,则解除阻塞的顺序是不确定的信号在使用前必须先初始化,但是信号没有属性3 初始化信号量使用 sem_init(3RT)可以将 sem 所指示的未命名信号变量初始化为 valuesem_init 语法int sem_init(sem_t *sem, int pshared, unsigned int value);#include sem_t sem;int pshared;int ret;int value;/* initialize a private semaphore */pshared =0;value =1;ret = sem_init(如果 pshared 的值为零,则不能在进程之间共享信号如果 pshared 的值不为零,则可以在进程之间共享信号注意:(1)多个线程决不能初始化同一个信号。
2)不得对其他线程正在使用的信号重新初始化4 初始化进程内信号量pshared 为 0 时,信号只能由该进程内的所有线程使用include sem_t sem;int ret;int count = 4;/* to be used within this process only */ret = sem_init(5 初始化进程间信号量pshared 不为零时,信号可以由其他进程共享include sem_t sem;int ret;int count = 4;/* to be shared among processes */ret = sem_init(6 sem_init 返回值sem_init()在成功完成之后会返回零其他任何返回值都表示出现了错误如果出现以下任一情况,该函数将失败并返回对应的值EINVAL描述:参数值超过了 SEM_VALUE_MAXENOSPC描述:初始化信号所需的资源已经用完到达信号的 SEM_NSEMS_MAX 限制ENOSYS描述:系统不支持 sem_init()函数EPERM描述:进程缺少初始化信号所需的适当权限7 增加信号sem_post 语法int sem_post(sem_t *sem);#include sem_t sem;int ret;ret = sem_post( /* semaphore is posted */如果所有线程均基于信号阻塞,则会对其中一个线程解除阻塞。
sem_post 返回值sem_post()在成功完成之后会返回零其他任何返回值都表示出现了错误如果出现以下情况,该函数将失败并返回对应的值EINVAL描述: sem 所指示的地址非法8 基于信号计数进行阻塞使用 sem_wait(3RT)可以阻塞调用线程,直到 sem 所指示的信号计数大于零为止,之后以原子方式减小计数sem_wait 语法int sem_wait(sem_t *sem);#include sem_t sem;int ret;ret = sem_wait( /* wait for semaphore */sem_wait 返回值sem_wait()在成功完成之后会返回零其他任何返回值都表示出现了错误如果出现以下任一情况,该函数将失败并返回对应的值EINVAL描述: sem 所指示的地址非法EINTR描述:此函数已被信号中断9 减小信号计数使用 sem_trywait(3RT)可以在计数大于零时,尝试以原子方式减小 sem 所指示的信号计数sem_trywait 语法int sem_trywait(sem_t *sem);#include sem_t sem;int ret;ret = sem_trywait( /* try to wait for semaphore*/此函数是 sem_wait()的非阻塞版本。
sem_trywait()在失败时会立即返回sem_trywait 返回值sem_trywait()在成功完成之后会返回零其他任何返回值都表示出现了错误如果出现以下任一情况,该函数将失败并返回对应的值EINVAL描述: sem 所指示的地址非法EINTR描述:此函数已被信号中断EAGAIN描述:信号已为锁定状态,因此该信号不能通过 sem_trywait()操作立即锁定10 销毁信号状态使用 sem_destroy(3RT)可以销毁与 sem 所指示的未命名信号相关联的任何状态sem_destroy 语法int sem_destroy(sem_t *sem);#include sem_t sem;int ret;ret = sem_destroy( /* the semaphore is destroyed */sem_destroy 返回值sem_destroy()在成功完成之后会返回零其他任何返回值都表示出现了错误如果出现以下情况,该函数将失败并返回对应的值EINVAL描述: sem 所指示的地址非法信号量的数据类型为结构 sem_t,它本质上是一个长整型的数函数 sem_init()用来初始 化一个信号量。
它的原型为: extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value)); sem 为指向信号量结构的一个指针;pshared 不为0时此信号量在进程间共享,否则只能为 当前进程的所有线程共享;value 给出了信号量的初始值 函数 sem_post( sem_t *sem )用来增加信号量的值当有线程阻塞在这个信号量上时,调用 这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的 函数 sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量 sem 的值大于 0,解除阻塞后 将 sem 的值减一,表明公共资源经使用后减少函数 sem_trywait ( sem_t *sem )是函数 sem_wait()的非阻塞版本,它直接将信号量 sem 的值减一 函数 sem_destroy(sem_t *sem)用来释放信号量 sem 信号量用 sem_init 函数创建的,下面是它的说明: #includeint sem_init (sem_t *sem, int pshared, unsigned int value);这个函数的作用是对由 sem 指定的信号量进行初始化,设置好它的共享选项,并 指定一个整数类型的初始值。
pshared 参数控制着信号量的类型如果 pshared 的值是0, 就表示它是当前里程的局部信号量;否则,其它进程就能够共享这个信号量我们现在只对不让进程共享的信号量感兴趣 (这个参数 受版本影响) , pshared 传递一个非零将 会使函数调用失败这两个函数控制着信号量的值,它们的定义如下所示:#include int sem_wait(sem_t * sem);int sem_post(sem_t * sem);这两个函数都要用一个由 sem_init 调用初始化的信号量对象的指针做参数sem_post 函数的作用是给信号量的值加上一个“1” ,它是一个“原子操作”--- 即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;而同 时对同一个文件进 行读、加和写操作的两个程序就有可能会引起冲突信号量的值永远会正确地加一个“2” --因为有两个线程试图改变它sem_wait 函数也是一个原子操作,它的作用是从信号量的值减去一个“1” ,但它 永远会先等待该信号量为一个非零值才开始做减法也就是说,如果你对 一个值为 2 的信 号量调用 sem_wait(),线程将会继续执行,介信号量的值将减到 1。
如果对一个值为 0 的信 号量调用 sem_wait(),这个函数就 会地。
