Skip to content

条件变量


创建、等待/通知和销毁条件变量的内置过程。

前言:

条件变量是一种允许线程等待(而不浪费 CPU 周期)某个事件发生的机制。

多个线程可以在条件变量上等待,直到另一个线程向该条件变量发出信号(从而发送通知)。

此时,等待该条件变量的其中一个线程会被唤醒,并可以对该事件作出响应。还可以通过在该变量上使用广播方法来唤醒等待该条件变量的所有线程。

条件变量不提供锁定功能。因此,在访问条件变量时,必须将互斥锁与条件变量一起使用,以提供必要的锁定。

即使对于已分离的线程(其句柄不再可通过其标识符访问),条件变量功能(以及互斥锁功能)也可以完全使用。

创建/销毁条件变量

CondCreate 创建一个条件变量,返回一个句柄标识符,用于在销毁条件变量时引用。

使用 CondCreate 创建的条件变量在不再需要时或在程序结束前应使用 CondDestroy 销毁(以避免操作系统资源泄漏)。

创建

  • 语法:

declare function CondCreate ( ) as any ptr

  • 用法:

conditionalid = CondCreate

  • 返回值:

创建的条件变量的 any ptr 句柄(conditionalid),失败时返回空指针(0)。

销毁

  • 语法:

declare sub CondDestroy ( byval conditionalid as any ptr )

  • 用法:

CondDestroy ( conditionalid )

  • 参数:

conditionalid

要销毁的条件变量的 any ptr 句柄。

说明

CondDestroy 销毁条件变量,释放其可能持有的资源。

在进入 CondDestroy 时,不得有线程在等待条件变量。

等待/通知条件变量

条件变量机制允许线程挂起执行并放弃处理器,直到某个条件为真。

CondWait 停止当前线程的执行,直到某个条件变为真。

CondSignal 允许重启一个等待条件变量的线程,而 CondBroadCast 允许重启所有等待条件变量的线程。

等待

  • 语法:

declare sub CondWait ( byval conditionalid as any ptr, byval mutexid as any ptr )

  • 用法:

CondWait ( conditionalid, mutexid )

  • 参数:

conditionalid

条件变量的句柄标识符。

mutexid

与此条件变量关联的互斥锁的句柄标识符,在测试条件和调用 CondWait 时必须被锁定。

通知

  • 语法:

declare sub CondSignal ( byval conditionalid as any ptr )

declare sub CondBroadCast ( byval conditionalid as any ptr )

  • 用法:

CondSignal ( conditionalid )

CondBroadCast ( conditionalid )

  • 参数:

conditionalid

要通知的条件变量的 any ptr 句柄。

说明

一旦使用 CondCreate 创建条件变量并启动线程,其中一个或多个线程(包括执行主程序的隐式主线程)可以通过 CondWait 设置为等待条件状态。

它们将被停止,直到另一个线程通过 CondSignal 发出信号,表明等待的线程中有一个可以重启。

CondBroadCast 可用于重启所有等待条件的线程。

条件变量必须始终与互斥锁关联,以避免由一个线程准备等待而另一个线程可能在第一个线程实际等待之前发出条件信号而导致死锁的竞态条件。该线程将永远等待一个永不发送的信号。任何互斥锁都可以使用,互斥锁和条件变量之间没有显式链接。

调用 CondWait 时,互斥锁应该已经被锁定(使用与 CondSignalCondBroadCast 使用的相同互斥锁)。

详细顺序如下:

  • 在进入等待条件变量之前,对互斥锁进行原子性解锁,以释放使用此互斥锁的其他可能线程(这就是为什么 CondWait 同时以互斥锁和条件变量作为参数的原因)。

  • 线程执行被挂起,在条件变量被通知之前不消耗任何 CPU 时间。

  • 当条件变量被通知时,互斥锁被原子性地重新锁定。

  • CondWait 语句后,线程执行可以恢复,但由于信号发送者拥有的锁定互斥锁而被挂起。

  • 因此,信号发送者负责解锁互斥锁,以便被调用线程完成 CondWait 子程序,并且 CondWait 调用后的执行才能真正恢复。

CondSignal 重启一个等待的线程。它应该在互斥锁锁定后调用(使用与 CondWait 使用的相同互斥锁):

  • 如果没有线程在等待条件,则什么都不发生(信号永远丢失)。

  • 如果有多个线程在等待,则只有一个被重启:

. 可能发生这种情况:一个有多个线程等待的条件变量被多次通知,但其中一个等待的线程永远不会被唤醒。

. 这是因为当变量被通知时,不知道哪个等待线程会被唤醒。

. 可能被唤醒的线程很快又回到等待条件变量的状态,当变量再次被通知时再次被唤醒,以此类推(不保证基于历史的唤醒优先级)。

. 如果这种情况意味着不良行为,程序员需要确保不会发生这种情况。

使用 CondBroadCast 时,这并不意味着所有线程同时运行:

  • 每个线程在从等待函数返回之前尝试再次锁定互斥锁。

  • 因此它们将逐一开始运行,每个线程锁定互斥锁,完成其工作,并在下一个线程有机会运行之前释放互斥锁。

注意:

  • 使用 CondWait 来防范可能的虚假唤醒是一个好习惯。

  • 为此,将 CondWait 放在一个循环中,用于检查当线程完成等待时某个布尔谓词确实为真(谓词由另一个线程在执行 CondSignalCondBroadCast 之前设置为真):

信号发送者:

predicate = True
Condsignal(conditionalid)

等待被调用者:

While predicate <> True

Condwait(conditionalid, mutexid)

Wend
predicate = False
  • 只有当谓词为真时,循环才能终止。

  • 另一方面,如果谓词在线程到达循环之前已经为真,CondWait 将被直接跳过(允许考虑一种 CondSignalCondBroadCast 丢失的情况,因为它在第一个线程真正等待之前在第二个线程中提前执行)。

应用上述所有规则进行详细编码的伪代码:

  • 线程子代码段的伪代码:
  • 线程代码段的伪代码:
  • 两个线程各自代码段的伪代码示例:

示例

前一页(互斥)的第二个示例通过使用条件变量进行修改,以展示同步示例。

因此,之前由互斥块 [Mutexlock ... Mutexunlock] 保护的两个代码段,现在被同步,使得每个线程依次显示其字符序列("[M]" 或 "(C)")。

在此示例中,两个线程的临界区子段的顺序相反:

  • 主线程临界区:先通知,再等待,

  • 子线程临界区:先等待,再通知。

因此主线程总是第一个显示其序列:

vb
'  Principle of mutual exclusion + mutual synchronization between 2 threads with loop
'  (connecting lines join the sender(s) and receiver(s) impacted by each action occurring during the sequence)
'
'          Main Thread                                                                  Child Thread
'  .....                                                                          .....
'  Loop                                                                           Loop
'      MUTEXLOCK(mutID) `<-----------------------------.     .-------------.--------->` MUTEXLOCK(mutID)
'      Do_something_with_exclusion              .---- | --- | ----------- | --------> While boolC <> True
'      boolC = True ----------------------------'     |     |             |     .-------- ( atomic_mutex_unlock(mutID) )
'      CONDSIGNAL(condID) --------------------------- | --- | ----------- | --- | ------> CONDWAIT(condID, mutID)
'      While boolM <> True `<------------------------- | --- | ---------.  '---- | ------>` ( atomic_mutex_re-lock(mutID) )
'          ( atomic_mutex_unlock(mutID) ) ------.     |     |          |        |     Wend
'          CONDWAIT(condID, mutID) <----------- | --- | --- | ------.  |        |     boolC = False
'          ( atomic_mutex_re-lock(mutID) ) <--- | ----'---- | ---.  |  |        |     Do_something_with_exclusion
'      Wend                                     |           |    |  |  '------- | --- boolM = True
'      boolM = False                            |           |    |  '---------- | --- CONDSIGNAL(condID)
'      MUTEXUNLOCK(mutID) ----------------------'-----------'    '--------------'---- MUTEXUNLOCK(mutID)
'  End Loop                                                                       End Loop
'  .....                                                                          .....

start GeSHi

vb
Declare Sub thread (ByVal userdata As Any Ptr)

Dim As Any Ptr threadID             '' declaration of an 'Any Ptr' thread-ID of the child thread
Dim Shared As Any Ptr mutID         '' declaration of a global 'Any Ptr' mutex-ID
    mutID = MutexCreate             '' creation of the mutex
Dim Shared As Boolean boolM, boolC  '' declaration of 2 global 'Boolean' boolM and boolC as predicates
Dim Shared As Any Ptr condID        '' declaration of a global 'Any Ptr' conditional-ID
    condID = CondCreate             '' creation of the conditional

Print """[M]"": from 'Main' thread"
Print """(C)"": from 'Child' thread"
Print

threadID = ThreadCreate(@thread)  '' creation of the child thread from the main thread

For I As Integer = 1 To 10       '' 'For' loop of the main thread
    MutexLock(mutID)             '' set mutex locked at the beginning of the exclusive section
    Print "[";
    Sleep 50, 1
    Print "M";
    Sleep 50, 1
    Print "]";
    boolC = True                 '' set to 'True' the predicate for the child thread
    CondSignal(condID)           '' signal to the child thread
    While boolM <> True          '' test predicate from the child thread
        CondWait(condID, mutID)  '' wait for signal from the child thread
    Wend
    boolM = False                '' reset the predicate from the child thread
    MutexUnlock(mutID)           '' set mutex unlocked at the end of the exclusive section
    Sleep 50, 1
Next I

ThreadWait(threadID)  '' waiting for the child thread termination
Print
Print "'Child' thread finished"

MutexDestroy(mutID)  '' destruction of the mutex
CondDestroy(condID)  '' destruction of the conditional

Sleep

Sub thread (ByVal userdata As Any Ptr)  '' sub executed by the child thread
    For I As Integer = 1 To 10          '' 'For' loop of the child thread
        MutexLock(mutID)                '' set mutex locked at the beginning of the exclusive section
        While boolC <> true             '' test predicate from the main thread
            CondWait(condID, mutID)     '' wait for signal from the main thread
        Wend
        boolC = False                   '' reset the predicate from the main thread
        Print "(";
        Sleep 50, 1
        Print "C";
        Sleep 50, 1
        Print ")";
        boolM = True                    '' set to 'True' the predicate for the main thread
        CondSignal(condID)              '' signal to the child thread
        MutexUnlock(mutID)              '' set mutex unlocked at the end of the exclusive section
        Sleep 250, 1
    Next I
End Sub

end GeSHi

输出:

vb
"[M]": from 'Main' thread
"(C)": from 'Child' thread

[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)[M](C)
'Child' thread finished

另请参阅

返回 目录

基于 FreeBASIC 官方文档翻译 如有侵权请联系我们删除
FreeBASIC 是开源项目,与微软公司无隶属关系