第4章 同步控制 Synchronization ----信号量(Semaphore)

  许多文件中都会提到 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 的运作情况了。

时间: 2024-10-21 12:39:00

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

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

Win32 的 Mutex 用途和 critical section 非常类似,但是它牺牲速度以增加弹性.或许你已经猜到了,mutex 是 MUTual EXclusion 的缩写.一个时间内只能够有一个线程拥有 mutex,就好像同一时间内只能够有一个线程进入同一个 critical section 一样.虽然 mutex 和 critical section 做相同的事情,但是它们的运作还是有差别的: 锁住一个未被拥有的 mutex,比锁住一个未被拥有的 critical section,需

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

操作系统Operating Systems进程同步与信号量Processes Synchronization and Semaphore 进程合作:多进程共同完成一个任务 从纸上到实际:生产者− − 消费者实例 共享数据 #define BUFFER_SIZE 10 typedef struct { . . . } item; item buffer[BUFFER_SIZE]; int in = out = counter = 0; 注意:这些都是用户态程序! 生产者进程 while (true

经典线程同步 信号量Semaphore

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> <秒杀多线程第七篇经典线程同步互斥量Mutex> 前面介绍了关键段CS.事件Event.互斥量Mutex在经典线程同步问题中的使用.本篇介绍用信号量Semaphore来解决这个问题. 首先也来看看如何使用信号量,信号量Semaphore常用有三个函数,使用很方便.下面是这几个函数的原型和使

秒杀多线程第八篇 经典线程同步 信号量Semaphore

版权声明:本文为博主原创文章,未经博主允许不得转载. 阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> <秒杀多线程第七篇经典线程同步互斥量Mutex> 前面介绍了关键段CS.事件Event.互斥量Mutex在经典线程同步问题中的使用.本篇介绍用信号量Semaphore来解决这个问题. 首先也来看看如何使用信号量,信号量Semaphore

java 信号量Semaphore

在很多情况下,可能有多个线程需要访问数目很少的资源.假想在服务器上运行着若干个回答客户端请求的线程.这些线程需要连接到同一数据库,但任一时刻 只能获得一定数目的数据库连接.你要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程? 答:1.给方法加同步锁,保证同一时刻只能有一个人去调用此方法,其他所有线程排队等待,但是此种情况下即使你的数据库链接有10个,也始终只有一个处于使 用状态.这样将会大大的浪费系统资源,而且系统的运行效率非常的低下. 2.另外一种方法当然是使用信号量,通过信号量许

JAVA多线程--信号量(Semaphore)

简介 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确.合理的使用公共资源. 一个计数信号量.从概念上讲,信号量维护了一个许可集.如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可.每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者.但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动.拿到信号量的线程可以进入代码,否则就等待.通过acqu

C# 多线程之一:信号量Semaphore

通过使用一个计数器对共享资源进行访问控制,Semaphore构造器需要提供初始化的计数器(信号量)大小以及最大的计数器大小 访问共享资源时,程序首先申请一个向Semaphore申请一个许可证,Semaphore的许可证计数器相应的减一,当计数器为0时,其他申请该信号量许可证的线程将被堵赛,直到先前已经申请到许可证的线程释放他占用的许可证让计数器加一,这样最近去申请许可证的线程将会得到竞争得到被释放的许可证. 常见的操作方法 WaitOne():申请一个许可证  Release():释放占用的许可

Java多线程与并发库高级应用之信号量Semaphore

JDK1.5提供了一个计数信号量Semaphore类.Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目,并提供了同步机制. Semaphore提供了两个构造器来创建对象: 1)Semaphore(int permits):创建具有给定的许可数和非公平的公平设置的Semaphore. 2)Semaphore(int permits, boolean fair):创建具有给定的许可数和给定的公平设置的Semaphore.如果此信号量保证在争用时按先进先出的顺序授予许可,则为

Java多线程-新特征-信号量Semaphore

简介信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确.合理的使用公共资源. 概念Semaphore分为单值和多值两种,前者只能被一个线程获得,后者可以被若干个线程获得. 以一个停车场运作为例.为了简单起见,假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待.这时,有一辆车离开停车场,看门人得知后,