使线程休眠一段时间或直到被唤醒的便携式方法

问题描述

在我的项目中,我生成一个处理信息源的工作线程。它需要轮询(它订阅的)源,并在需要时更改订阅(即告诉源它所需信息的过滤条件已更改)。

在分钟范围内定期对源进行轮询。当主线程识别出这样做的条件时,需要立即触发对订阅的更改。但是,由于操作可能很长,因此需要在工作线程上运行。

所以工作线程看起来像这样(在伪代码中——真正的代码是在 C 中):

while(true) {
    acquireWriteLock();
    if (subscriptionChangeNeeded) {
        changeSubscription();
        subscriptionChangeNeeded = false;
    }
    releaseWriteLock();
    pollSource();
    sleep(pollInterval);
}

现在,如果主线程在工作线程完成一次循环并进入睡眠状态后立即设置 subscriptionChangeNeeded,则订阅更改将延迟几乎 pollInterval 的持续时间.

因此,我需要一种方法将线程从睡眠中提前唤醒——或者,与其告诉它“睡眠 X”,“睡眠直到我叫醒你,但不超过 X”。

我不需要明确告诉线程为什么它从睡眠中醒来——它可以从 subscriptionChangeNeeded 推断出来。在更改订阅后过早地进行轮询是一种理想的副作用。

设置 subscriptionChangeNeeded 发生在一个位置,因此我可以轻松地在那里合并“唤醒工作线程”操作。

挑战:代码需要在类 Unix 操作系统和 Windows 上运行。它通过围绕各自的 API 实现线程抽象包装器来解决这个问题。

我知道两个平台(pthread 和 Windows)都支持条件变量,但实现方式有所不同。其中,Windows 使用临界区对象保护临界区,而 pthread 使用互斥锁。此外,较旧的 Windows 平台(直到 XP/2003)根本不支持条件变量。最后,条件变量比我需要的更强大——我真正需要的是一种让线程进入睡眠状态并允许它被另一个线程唤醒的方法

这两个平台还提供哪些其他结构来实现这一点?

解决方法

您可以使用 pthread_cond_wait() 等待来自主线程的信号,表明条件已满足并且工作可以恢复(例如,等待 subscriptionChangeNeeded 更改)。这将消除使用睡眠的需要,但是一旦线程在此调用上挂起,恢复它的唯一方法是调用 pthread_cond_signal()

如果您的工作线程的循环有其他任务要做并且您依赖定期唤醒, 您可以使用 pthread_cond_timedwait() 等待 subscriptionChangeNeeded 更改;或达到超时;无论先发生什么。

主线程将有一个任务来识别条件的变化,一旦识别出需要更新,主线程将调用pthread_cond_signal()来通知写入线程他需要唤醒。如果在指定的超时时间内没有收到信号,线程将恢复(唤醒),即使条件满足,所以这类似于sleep

无论哪一个先发生(超时或条件改变),线程都会恢复,他可以执行changeSubscription();

您可以查看更多详细信息和示例here

这是在 pthread.h 中定义的 POSIX 函数

,

经过一些研究,似乎 WinAPI 上的事件与我想要完成的最接近,并且它们至少从 Windows XP 开始就存在(我相信甚至更早的版本都有它)。在 POSIX 上,最接近的匹配实际上是条件变量,尽管互斥锁并不是真正必要的。

由于项目实现了自己的线程抽象包装器,这给了我们一些额外的自由。

我们将介绍一种数据类型 event。这是一个不透明的数据结构。在 Windows 上,这对应于一个事件(作为自动重置事件创建);在 POSIX 上,这是一个包含条件和互斥锁的结构体。

当一个线程进入休眠状态时,POSIX 实现会锁定互斥锁,调用 pthread_cond_timedwait 并在它继续时立即解锁互斥锁。 Windows 实现只调用 WaitForSingleObject

为了唤醒休眠线程,POSIX 实现会锁定互斥锁,调用 pthread_cond_signal 并再次解锁互斥锁。 Windows 实现调用 SetEvent

在 Windows 上,事件可以自动重置或不自动重置。自动重置事件将在第一个等待线程被唤醒后立即重置(因此它永远不会唤醒多个线程),但如果该线程恰好在事件发出信号时正在运行,则将保持设置状态。其他事件将保持在信号状态直到明确重置,因此它们可以唤醒任意数量的线程,甚至首先阻止它们进入睡眠状态。

在 POSIX 上,可以发出信号(唤醒一个等待线程)或广播(唤醒所有等待线程)。据我了解,即使没有线程被唤醒,它也会在触发后重置。

因此,只要我们永远不会有多个线程在等待同一事件,Windows 上的自动重置事件的行为与 POSIX 上的 pthread_cond_signal 非常相似。唯一的区别是我们需要在处理完事件后重置它。我们的线程抽象包装器需要为此提供一个函数,该函数在 Windows 上调用 ResetEvent,而在 POSIX 上则无操作。

与条件不同,此构造不提供互斥锁来提供同步访问。因此,如果等待线程和信令线程访问共享数据结构,它们必须实现自己的机制来同步访问,例如使用互斥锁或锁。