Win32多线程之信号量(Semaphores)

来源:互联网 发布:js 获取div属性 编辑:程序博客网 时间:2024/04/30 16:33

   信号量是解决各种生产者/消费者问题的关键要素。这种问题会存在一个缓冲区,可能在同一时间内被读出数据或被写入数据。
   Win32中的一个semaphore可以被锁住最多n次,其中n是semaphore被产生时指定的。n常常被设计用来代表“可以锁住一份资源”的线程个数,不过并非单独一个线程就不能够拥有所有的锁定,这没什么理由可言。
    理论上可以证明,mutex是semaphore的一种退化。如果你产生一个semaphore并令最大值是1,那就是一个mutex。也因此,mutex又常常被称为binary semaphore。如果某个线程拥有一个binary semaphore,那么就没有其他线程能够获得其拥有权。在Win32中,这两种东西的拥有权(ownership)的意义完全不同,所以它们不能够交换使用,semaphores不像mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。
  在许多系统中,semaphores常被使用,因为mutexes可能并不存在,因为mutex存在的缘故,在Win32中semaphores被使用的情况就少得多。


产生信号量(Semaphore)
   要在Win32环境中产生一个semaphore,必须使用CreateSemaphore(0函数调用:
HANDLE CreateSemaphore(
    LPSECURITY_ATTRIBUTES lpAttributes, 
    LONG lInitialCount, 
    LONG lMaximumCount, 
    LPCTSTR lpName
);


参数
lpAttributes        安全属性,如果是NULL,就表示要使用默认属性
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的运作情况了。