许多文件中都会提到 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. JavaScript笔记之第五天

    JavaScript 对象 JavaScript 中的所有事物都是对象:字符串.数值.数组.函数... 此外,JavaScript 允许自定义对象. 所有事物都是对象 JavaScript 提供多个内 ...

  2. .net core 2.0 登陆权限验证

    首先在Startup的ConfigureServices方法添加一段权限代码 services.AddAuthentication(x=> { x.DefaultAuthenticateSche ...

  3. 论述Redis和Memcached的差异

    原文 https://yq.aliyun.com/articles/60981?utm_campaign=wenzhang&utm_medium=article&utm_source= ...

  4. 拨开字符编码的迷雾--MySQL数据库字符编码

    拨开字符编码迷雾系列文章链接: 拨开字符编码的迷雾--字符编码概述 拨开字符编码的迷雾--编译器如何处理文件编码 拨开字符编码的迷雾--字符编码转换 拨开字符编码的迷雾--MySQL数据库字符编码 1 ...

  5. postman 第5节 Runner的使用(转)

    1.首先在postman新建要批量运行的接口文件夹,新建一个接口,并设置好全局变量. 2.然后在Test里面设置好要断言的方法 如: tests["Status code is 200&qu ...

  6. 多路复用(select、epoll)实现tcp服务

    -------------------------------多路复用的服务器(select)------------------------------- 网络通信被Unix系统抽象为文件的读写,通 ...

  7. Haproxy基于ACL做访问控制

    author:JevonWei 版权声明:原创作品 haproxy配置文档 https://cbonte.github.io/haproxy-dconv/ 基于ACL做访问控制(四层代理) 网络拓扑 ...

  8. 程序员也是弱势群体?——从WePhone开发者事件说起

    作为一名不爱凑热闹的人,今天一直在持续关注一个热点事件--WePhone开发者自杀,即使前几天热议的孕妇跳楼新闻我都不太关注,但是这个事件却让我深深的震撼,花了几个小时在微博上搜索了相关的信息,去了解 ...

  9. 团队作业8----第二次项目冲刺(Beta阶段) 第一天

    BETA阶段冲刺第一天 1.开了个小会议 2.每个人的工作 (1) 昨天已完成的工作: 今天是第一天,所以是新的开始. (2) 今天计划完成的工作: (3) 工作中遇到的困难: 由于有新的成员加入,默 ...

  10. 201521123017 《Java程序设计》第5周学习总结

    1. 本周学习总结 2. 书面作业 1.代码阅读:Child压缩包内源代码 1.1 com.parent包中Child.java文件能否编译通过?哪句会出现错误?试改正该错误.并分析输出结果. 1.2 ...