avatar

Catalog
信号量

前言

前一篇学习了事件(Event)对象,线程在进入临界区之前会通过调用WaitForSingleObject或者WaitForMultipleObjects来判断当前的事件对象是否有信号(SignalState>0),只有当事件对象有信号时,才可以进入临界区。 需要说明的是,这里的临界区指的是广义上的临界区,即只允许一个线程进入直到退出的一段代码,不单指用EnterCriticalSection()和LeaveCriticalSection()而形成的临界区。

通过对Event对象相关函数的分析,我们发现,Event对象的SignalState值只有两种可能:

  • 值为1:
    • CreateEvent函数初始化Event时,第3个参数值为TRUE。
    • 调用SetEvent函数设置Event对象为有信号。
  • 值为0:
    • WaitForSingleObject/WaitForMultipleObjects
    • ResetEvent

事件的应用场景

修改全局变量

在对事件对象的相关知识有所掌握后,就轮到对其应用场景的学习。事件可以运用在 “当多个线程想要对同一个全局变量作修改时” 的情景,此时可以通过事件(例如WaitForSingleObject+SetEvent)形成的临界区,完成对线程进出临界区的控制,以保证同一时间只有一个线程可以修改全局变量。

生产者与消费者问题

生成者与消费者问题是经典的线程同步问题,需要解决的问题是在资源不对等的情况下,该如何确保线程同步。

如图,有1个生产者线程,每回合可以生产出3点资源,此时有5个消费者线程。需要保证在同一时间不会有2个线程消费同1个资源。

在这种情况下,使用事件来控制线程的同步就相当的困难,效率也相对较低:

  • 使用事件同步对象(Type=1),由于事件对象的SignalState的值只能为0或者1,因此同一时间只有一个消费者线程可以获得资源,此时效率是极其低下的。
  • 使用通知类型对象(Type=0),通知类型对象唤醒的线程,在进入KeWaitForSingleObject循环后,不会修改SignalState的值,但是此时仅有3点资源,唤醒5个线程,显然也会造成资源的浪费

综上,在解决的生成者与消费者问题时,使用事件对象来处理,效率明显不够高。需要有另一种形式的同步对象,也就是本篇将要介绍的信号量

信号量的应用场景

信号量和事件大体类似,不同的是,相较于事件对象同一时间仅允许一个线程进入临界区信号量则允许多个线程同时进入临界区

因此信号量可以应用在生成者与消费者问题上。当消费者线程的数量多于可被消耗的资源时,允许和资源数量相同的线程进入临界区,即可使得效率最大化。

信号量的创建与设置

再次回顾KeWaitForSingleObject的关键循环

之前已重复多次,不同类型的等待对象。区别在于对是否符合激活条件的判断,以及对SignalState值的修改。所以从这两个角度入手,会更容易理解信号量。

信号量的创建

下面将信号量创建API,信号量结构体,信号量第一个成员_DISPATCHER_HEADER放在一起看,会比较清晰。

  1. 首先是CreateSemaphore函数,比较关键的是第二个和第三个参数
  2. 调用CreateSemaphore函数创建出信号量,也就是_KSEMAPHORE这个结构体。该结构体比Event对象多了一个Limit字段,该字段CreateSemaphore的第三个参数IMaximumCount决定用来设置最多允许多少线程同时进入临界区
  3. _DISPATCHER_HEADER是每个可等待对象都拥有的成员,其中信号量类型为5(Type=5)SignalState的值由CreateSemaphore的第二个参数IInitialCount决定。

信号量的设置

之前学习的Event对象,它的SignalState由CreateEvent第三个参数决定,也可以通过SetEvent设置信号

信号量的SignalState由CreateSemaphore第二个参数IInitialCount决定,也可以通过ReleaseSemaphore设置信号

根据分析ReleaseSemaphore函数,其执行流程如上图所示,最终会调用内核的KeReleaseSemaphore函数,该函数主要作用也和SetEvent(Type=0)类似区别也是在于对SignalState的修改上

  • SetEvent:将SignalState的值置1。
  • ReleaseSemaphore:设置SignalState = SignalState + N(传入的参数)

ReleaseSemaphore函数,在解决生产者消费者问题时就更有效率,它可以根据生产出来的资源设置相应的信号。

KeWaitForSingleObject

同样,KeWaitForSingleObject这个函数也是必不可少的,几个主要的可等待对象都要经过它的循环,信号量的部分在上一期顺带提到了,这里可以直接拿来用。

这里直接定位到粉色方框部分,只有当Type值为5时,也就是信号量对象时,才会走这里。这里对SignalState值的修改方式是减1,和事件对象不一样。事件对象是直接将SignalState设置为0,信号量则是减1。这样信号量就可以精准控制进入临界区线程的数量,在解决例如生产者与消费者问题时更有效率。

参考资料

参考教程:

  • 海哥中级预习班课程

参考链接:

  1. https://blog.csdn.net/weixin_42052102/article/details/83449347 (CSDN-My classmates学习笔记)
  2. https://blog.csdn.net/qq_41988448/article/details/104895544 (CSDN-lzyddf学习笔记)
Author: cataLoc
Link: http://cata1oc.github.io/2020/08/16/%E4%BF%A1%E5%8F%B7%E9%87%8F/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶