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

Portable way to make a thread sleep for a certain time or until woken up

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

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

所以工作线程看起来像这样(在伪代码中——真正的代码在 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

这是 POSIX 在 pthread.h

中定义的函数

经过一些研究,似乎 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.

上是一个空操作

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