许多文件中都会提到 semaphores(信号量),因为在电脑科学中它是最具历史的同步机制。它可以让你陷入理论的泥淖之中,教授们则喜欢问你一些有关于信号量的疑难杂 症。你可能不容易找到一些关于 semaphores 的有用例子,但是我告诉你,它是解决各种 producer/consumer 问题的关键要素。这种问题会存有一个缓冲区,可能在同一时间内被读出数据或被写入数据。

  Win32 中的一个 semaphore 可以被锁住最多 n 次,其中 n 是 semaphore 被产生时指定的。n 常常被设计用来代表“可以锁住一份资源”的线程个数,不过并非单独一个线程就不能够拥有所有的锁定。这没有什么理由可言。

  这 里有一个例子,告诉你为什么你需要一个 semaphore。考虑一下某人(我称之为 Steve)的情况。他想在加州租一辆车。租车店柜台后面坐了好几位租车代理人。Steve 告诉租车代理人说他想要一部敞篷车,接待他的那位代理人往窗外一看,有三辆敞篷车可以用,于是开始写派车单。不幸的是,就那么巧,有另三个人也同时要一辆 敞篷车,而他们的租车代理人也正在做 Steve 的代理人的相同动作。现在,有四个人想租三辆车,而必然有某个人要被淘汰出局。

让我们留下这小 小的悬疑画面,并祈祷 Steve 租得到车。租车公司这边的问题是,他们不可能即时写下派车单并且马上给租车人钥匙。整个租车程序过长,长到足够让另一位代理人把同一辆车租给另一个人。这
种情况我们已经在多线程的情况下一再地看到了。如果有许多个线程正在处理相同的资源,那么必须有某些机制被用来阻止线程干扰其他线程。

  如果我们尝试写一个程序解决汽车出租问题,方法之一就是为每辆车加上一个 mutex 保护之。这虽然可行,但你可能得为一家大租车公司生产成百甚至成千个 mutexes。

  另一个方法就是以单一的 mutex 为所有车辆服务,或说为所有的敞篷车服务,但这样的话一次就只能有一个店员出租敞篷车。这或许可以减少店员人数,但是面对一家忙碌的出租公司,客户可能因此转移到其竞争对手那里去。

  解 决之道是:首先,所有的敞篷车都被视为相同(是啊,什么时候你租车还选颜色的?),在钥匙被交到客户手上之前,唯一需要知道的就是现在有几辆车可以用。我
们可以用 semaphore 来维护这个数字,并保证不论是增加或减少其值,都是在一个不可分割的动作内完成。当 semaphore 的数值降为0时,不论什么人要租车,就得等待了。

理论可以证明,mutex 是 semaphore 的一种退化。如果你产生一个semaphore 并令最大值为 1,那就是一个 mutex。也因此,mutex 又常被称为binary semaphore。如果某个线程拥有一个 binary semaphore,那么就没有其他线程能够获得其拥有权。在 Win32 中,这两种东西的拥有权(ownership)的意义完全不同,所以它们不能够交换使用。semaphores 不像 mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。

在许多系统中,semaphores 常被使用,因为
mutexes 可能并不存在。在Win32 中
semaphores 被使用的情况就少得多,因为 mutex 存在的缘故。

产生信号量(Semaphore)要在
Win32 环境中产生一个 semaphore,必须使用
CreateSemaphore()函数调用:

HANDLE CreateSemaphore(

    LPSECURITY_ATTRIBUTES lpAttributes,

    LONG lInitialCount,

    LONG lMaximumCount,

    LPCTSTR lpName

);

参数

    lpAttributes         安全属性。如果是 NULL 就表示要使用默认属性。Windows 95 忽略这一参数。

    lInitialCount       
 semaphore 的初值。必须大于或等于 0,并且小于或等于 lMaximumCount。

    lMaximumCount       
 Semaphore 的最大值。这也就是在同一时间内能够锁住 semaphore 之线程的最多个数。

    lpName           
 Semaphore 的名称(一个字符串)。任何线程(或进程) 都可以根据这一名称引用到这个semaphore。这个值可以是 NULL,意思是产生一个没有名字的 semaphore。

返回值

    如果成功就传回一个 handle,否则传回 NULL。不论哪一种情况,GetLastError() 都会传回一个合理的结果。如果指定的 semaphore 名称已经存在,则该函数还是成功的,GetLastError() 会传回 ERROR_ALREADY_EXISTS。

获得锁定

  Semaphore 的各个相关术语,其晦涩比起 mutexes 真是有过之而无不及。首先请你了解,semaphore 的现值代表的意义是目前可用的资源数。如果semaphore 的现值为 1,表示还有一个锁定动作可以成功。如果现值为 5,就表示还有五个锁定动作可以成功。

  每当一个锁定动作成功,semaphore 的现值就会减 1。你可以使用任何一种 Wait...() 函数(例如 WaitForSingleObject())要求锁定一个 semaphore。因此,如果 semaphore 的现值不为 0,Wait...()
函数会立刻返回。这和 mutex 很像,如果没有任何线程拥有
mutex,Wait...() 会立刻返回。

  如果锁 定成功,你也不会收到 semaphore 的拥有权。因为可以有一个以上的线程同时锁定一个 semaphore,所以谈 semaphore 的拥有权并没有太多帮助。在 semaphore 身上并没有所谓“独占锁定”这种事情。也因为没有拥有权的观念,一个线程可以反复调用 Wait...() 函数以产生新的锁定。这和 mutex绝不相同:拥有 mutex 的线程不论再调用多少次 Wait...() 函数,也不会被阻塞住。

  一旦 semaphore 的现值降到 0,就表示资源已经耗尽。此时,任何线程如果调用 Wait...() 函数,必然要等待,直到某个锁定被解除为止。

解除锁定(Releasing Locks)

  为了解除锁定,你必须调用 ReleaseSemaphore()。这个函数将 semaphore 的现值增加一个定额,通常是 1,并传回 semaphore 的前一个现值。

  ReleaseSemaphore() 和 ReleaseMutex() 旗鼓相当。当你调用WaitForSingleObject()
并获得一个 semaphore 锁定之后,你就需要调用ReleaseSemaphore()。Semaphore 常常被用来保护固定大小的环状缓冲区(ring buffer)。程序如果要读取环状缓冲区的内容,必须等待 semaphore。

  线程将数据写入环状缓冲区,写入的数据可能不只一笔,在这种情况下解除锁定时的
semaphore 增额应该等于写入的数据笔数。

BOOL ReleaseSemaphore(

    HANDLE hSemaphore,

    LONG lReleaseCount,

    LPLONG lpPreviousCount

);

参数

hSemaphore
        Semaphore 的
handle。

lReleaseCount
        Semaphore 现值的增额。该值不可以是负值或 0。

lpPreviousCount
    藉此传回 semaphore 原来的现值。

返回值

如果成功,则传回TRUE。否则传回 FALSE。失败时可调用 GetLastError()获得原因。

  ReleaseSemaphore() 对于 semaphore 所造成的现值的增加,绝对不会超过CreateSemaphore()
时所指定的 lMaximumCount。

  请记住, lpPreviousCount 所传回来的是一个瞬间值。你不可以把lReleaseCount 加上 *lpPreviousCount,就当作是 semaphore 的现值,因为其他线程可能已经改变了 semaphore 的值。

  与 mutex 不同的是,调用 ReleaseSemaphore()
的那个线程,并不一定就得是调用 Wait...() 的那个线程。任何线程都可以在任何时间调用ReleaseSemaphore(),解除被任何线程锁定的 semaphore。

为什么 semaphore 要有一个初值

  CreateSemaphore() 的第二个参数是 lInitialCount , 它的存在理由和CreateMutex() 的 bInitialOwner 参数的存在理由是一样的。如果你把初值设定为 0,你的线程就可以在产生 semaphore 之后进行所有必要的初始化工作。待初始化工作完成后,调用
ReleaseSemaphore() 就可以把现值增加到其最大可能值。

  以环状缓冲区(ring buffer)为例,semaphore 通常被产生时是以 0 为初值,所以任何一个等待中的线程都会停下来。一旦有东西被加到环状缓冲区中,我们就以 ReleaseSemaphore() 增加 semaphore 的值,于是等待中的线程就可以继续进行。

如果“将数据写入环状缓冲区”的那个线程,在它(或任何其他线程)调用 Wait...() 函数之前,先调用 ReleaseSemaphore(),会出现想象不到的结果。就某种意义而言,这就完全退缩到 mutex 的运作情况了。

第4章 同步控制 Synchronization ----信号量(Semaphore)的更多相关文章

  1. 第4章 同步控制 Synchronization ----同步机制的摘要

    同步机制摘要Critical Section Critical section(临界区)用来实现"排他性占有".适用范围是单一进程的各线程之间.它是:  一个局部性对象,不是一个核 ...

  2. 第4章 同步控制 Synchronization ----事件(Event Objects)

    Win32 中最具弹性的同步机制就属 events 对象了.Event 对象是一种核心对象,它的唯一目的就是成为激发状态或未激发状态.这两种状态全由程序来控制,不会成为 Wait...() 函数的副作 ...

  3. 第4章 同步控制 Synchronization ---哲学家进餐问题(The Dining Philosophers)

    哲学家进餐问题是这样子的:好几位哲学家围绕着餐桌坐,每一位哲学家要么思考,要么等待,要么就吃饭.为了吃饭,哲学家必须拿起两支筷子(分放于左右两端).不幸的是,筷子的数量和哲学家相等,所以每支筷子必须由 ...

  4. 第4章 同步控制 Synchronization ----critical section 互斥区 ,临界区

    本章讨论 Win32 同步机制,并特别把重点放在多任务环境的效率上.撰写多线程程序的一个最具挑战性的问题就是:如何让一个线程和另一个线程合作.除非你让它们同心协力,否则必然会出现如第2章所说的&quo ...

  5. 第4章 同步控制 Synchronization ----Interlocked Variables

    同步机制的最简单类型是使用 interlocked 函数,对着标准的 32 位变量进行操作.这些函数并没有提供"等待"机能,它们只是保证对某个特定变量的存取操作是"一个一 ...

  6. 第4章 同步控制 Synchronization ----互斥器(Mutexes)

    Win32 的 Mutex 用途和 critical section 非常类似,但是它牺牲速度以增加弹性.或许你已经猜到了,mutex 是 MUTual EXclusion 的缩写.一个时间内只能够有 ...

  7. 第4章 同步控制 Synchronization ----死锁(DeadLock)

    Jeffrey Richter 在他所主持的 Win32 Q&A 专栏(Microsoft Systems Journal,1996/07)中曾经提到过,Windows NT 和 Window ...

  8. [No00003C]操作系统Operating Systems进程同步与信号量Processes Synchronization and Semaphore

    操作系统Operating Systems进程同步与信号量Processes Synchronization and Semaphore 进程合作:多进程共同完成一个任务 从纸上到实际:生产者− − ...

  9. windows核心编程-信号量(semaphore)

    线程同步的方式主要有:临界区.互斥区.事件.信号量四种方式. 前边讲过了互斥器线程同步-----windows核心编程-互斥器(Mutexes),这章我来介绍一下信号量(semaphore)线程同步. ...

随机推荐

  1. java 基础四

    1 for循环嵌套 简而言之,就是一个for循环语句里面,还有一个for循环语句. 外层循环,每循环一次,内层循环,循环一周. 示例 package java003; /** * 2017/9/1. ...

  2. Mysql 掌握要点

    1. 引擎 InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁. 行级锁和表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题. 1.1 I ...

  3. css 找到隐藏元素个数

    <form>   <input type="hidden" name="email" />   <input type=" ...

  4. [js高手之路]深入浅出webpack系列1-安装与基本打包用法和命令参数

    webpack,我想大家应该都知道或者听过,Webpack是前端一个工具,可以让各个模块进行加载,预处理,再进行打包.现代的前端开发很多环境都依赖webpack构建,比如vue官方就推荐使用webpa ...

  5. 通过ssh协议实现用户key认证登录

    author:JevonWei 版权声明:原创作品 用户实现key认证登录 主机A 192.168.198,134 主机B 192.168.198,131 主机C 192.168.198,136 创建 ...

  6. Spring @ResponseBody 返回中文乱码问题

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt102 今天在使用spring 的时候,发现中文返回的是乱码. 经过研究发现, ...

  7. Spring中ApplicationContext加载机制

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp33 加载器目前有两种选择:ContextLoaderListener和Co ...

  8. Httprequest 获取url 常用方法

    HttpServletRequest常用获取URL的方法         1.request.getRequestURL() 返回的是完整的url,包括Http协议,端口号,servlet名字和映射路 ...

  9. Git简易参考手册

    如果用过mercury(HG),那么理解Git的运作方式就轻松多了.两者是相同的分布式版本管理工具,只是某些功能有着细微的差别 - Git的管理粒度更加细腻,因此操作上也比HG复杂一点.例如,修改文件 ...

  10. jQuery的less和scss之less的基本介绍(一)

    简单的整理了一下less的基本用法,希望对大家有所帮助ㅎㅎ 一.less基础语法 1.声明变量:@变量名:变量值 使用变量:@变量名 例如 @color : #ff0000; @length : 10 ...