【问题标题】:How do I wake up a sleeping pthread?如何唤醒休眠的 pthread?
【发布时间】:2012-08-15 00:53:04
【问题描述】:

我正在用 C++ 编写程序。我注意到它获得了许多线程,其目的是每隔一段时间做一些事情,其中​​有 3 或 4 个。我决定通过编写一个调度程序服务来重构,使用这些线程的其他地方可以订阅,这应该可以将我在任何时候运行的额外事件线程的数量减少到只有一个。

我还没有任何使用它的代码;在我开始写它之前,我想知道它是否可能,并获得一些关于我的设计的反馈。对我想要完成的事情的简要描述是这样的:

添加事件

  1. 调用者提供事件和时间表
  2. 计划提供事件的下一次发生
  3. (事件,计划)对被添加到事件队列中
  4. 中断睡眠事件线程(即唤醒它)

事件线程主循环

  1. 尝试获取事件队列中的下一个事件
  2. 如果没有待处理的事件,直接跳到4
  3. 获取下一个事件应该发生的时间
  4. 睡眠直到下一个事件(如果没有等待事件,则永远睡眠)
  5. 如果睡眠因任何原因被中断,则循环回 1
  6. 如果睡眠成功完成,则执行当前事件
  7. 更新队列(删除事件,如果是重复事件则重新插入)
  8. 跳回1

我做了一些研究,知道有可能中断正在休眠的线程,我相信只要防止同时访问事件队列,就不应该有任何危险的行为。我想唤醒一个线程是可能的,java 的 Thread 的 sleep() 调用在某些情况下会抛出一个 InterruptedException,除非它不依赖于操作系统的底层 sleep 调用,否则它必须以某种方式成为可能。

问题

谁能评论我的方法?这是一个我最好不要重新发明的轮子吗?具体来说,如何中断休眠线程以便在下一条指令处恢复执行,是否可以从中断的线程中检测到这一点?

关于提升的说明

我敢打赌,你可以编写一个带有 boost 的调度程序,但它编译并运行在一台机器上,因为缺少更好的短语,它是一堆废话。我之前在上面编译过 boost 程序,每个引入 boost 的文件通常需要 30 秒以上的时间来编译。如果我能避免这个恼人的发展障碍,我非常愿意。

附录 - 工作代码 [根据 caf 的建议修改]

这是我生成的有效代码。它已经过初步测试,但可以正确处理具有不同延迟的单个事件和重复事件。

这是事件线程的主体:

void Scheduler::RunEventLoop()
{
    QueueLock();                   // lock around queue access
    while (threadrunning)
    {
        SleepUntilNextEvent();     // wait for something to happen

        while (!eventqueue.empty() && e.Due())
        {                          // while pending due events exist
            Event e = eventqueue.top();
            eventqueue.pop();

            QueueUnlock();         // unlock
            e.DoEvent();           // perform the event
            QueueLock();           // lock around queue access

            e.Next();              // decrement repeat counter
                                   // reschedule event if necessary
            if (e.ShouldReschedule()) eventqueue.push(e);
        }
    }
    QueueUnlock();                 // unlock
    return;                        // if threadrunning is set to false, exit
}

这是睡眠功能:

void Scheduler::SleepUntilNextEvent()
{
    bool empty = eventqueue.empty();  // check if empty

    if (empty)
    {
        pthread_cond_wait(&eventclock, &queuelock); // wait forever if empty
    }
    else
    {
        timespec t =                  // get absolute time of wakeup
            Bottime::GetMillisAsTimespec(eventqueue.top().Countdown() + 
                                         Bottime::GetCurrentTimeMillis());
        pthread_cond_timedwait(&eventclock, &queuelock, &t); // sleep until event
    }
}

最后,添加事件:

void Scheduler::AddEvent(Event e)
{
    QueueLock();
    eventqueue.push(e);
    QueueUnlock();
    NotifyEventThread();
}

相关变量声明:

bool threadrunning;
priority_queue<Event, vector<Event>, greater<Event> > eventqueue;
pthread_mutex_t queuelock; // QueueLock and QueueUnlock operate on this
pthread_cond_t eventclock;

为了处理泛型事件的问题,每个Event 都包含一个指向抽象类型action 的对象的指针,其子类覆盖action::DoEvent。这个方法是从Event::DoEvent 内部调用的。 actions 由他们的事件“拥有”,即如果不再需要重新安排事件,它们会被自动删除。

【问题讨论】:

  • 您等待事件,而不是“睡眠”。您应该阅读 pthreads 文档以了解它提供了什么样的原语。
  • 这不是一个事件处理程序,它是一个调度程序。它需要获取事件,这些事件在未来的某个时候需要发生。这不是等待事件,而是等待 ON 事件。
  • 您当前的解决方案包含竞争条件 - 我在附加答案中做了更长的解释和一些建议的改进。
  • NotifyEventThread() 使用 pthread_cond_signal() 吗?

标签: c++ linux pthreads sleep


【解决方案1】:

您正在寻找的是pthread_cond_t 对象、pthread_cond_timedwaitpthread_cond_wait 函数。您可以创建条件变量 isThereAnyTaskToDo 并在事件线程中等待它。添加新事件时,您只需使用pthread_cond_signal() 唤醒事件线程。

【讨论】:

  • 小心点。您正在描述一个事件变量。条件变量有点微妙。事件变量表示“某事已经发生”,而条件变量则表示“某事可能已经改变”。条件变量只有在与互斥锁配对时才能正确使用;等待条件变量可能会虚假唤醒。
  • 好点,但是 Wug 已经处理好了,请参阅他的事件线程描述的第 5 步。
  • 我不能使用条件对象,因为在该事件运行时必须唤醒它。
  • 我会调查的。我使用过条件,但从未使用过timedwait
  • 我已经生成(但未测试)一个使用 pthread_cond_timedwait 的类。我可能今天晚些时候可以测试它。
【解决方案2】:

在 *NIX 平台和 Windows 上都有多种可能性。您的计时器线程应该在事件/条件变量对象上使用某种定时等待。在 POSIX 平台上,您可以使用 pthread_cond_timedwait()。在 Windows 上,您可以选择计算必要的时间增量并在事件句柄上使用WaitForSingleObject(),或者您可以使用事件对象与CreateTimerQueueTimer()CreateWaitableTimer() 的组合。 Boost 也有一些同步原语,您可以使用类似 POSIX 的原语来实现这一点,但可移植。

更新:

POSIX 也有一些计时器功能,请参阅 create_timer()

【讨论】:

  • 这是纯linux,我不需要也不打算支持windows平台。当我真正实现它时,我会接受一个答案,但这可能是我选择的解决方案。
  • 再想一想,我不认为我会使用计时器,因为我将不得不担心其他安全同步代码中的死锁。我想。我很确定,如果进行了异步调用,并且关闭了 IO 锁并且事件处理程序试图抓住它(这并不超出可能性范围),它将死锁。
【解决方案3】:

我同意Gregwilx - pthread_cond_timedwait() 可用于实现您所追求的行为。我只是想补充一点,您可以简化事件线程主循环:

  1. 尝试获取事件队列中的下一个事件
  2. 如果没有待处理的事件,直接跳到4
  3. 获取下一个事件应该发生的时间
  4. 使用pthread_cond_timedwait() 等待条件变量直到下一个事件(如果没有预定事件,则使用pthread_cond_wait()
  5. 尝试获取事件队列中的下一个事件
  6. 如果还没有事件过期,返回4
  7. 更新队列(删除事件,如果是重复事件则重新插入)
  8. 跳回5

所以你不必关心为什么你醒来 - 每当你醒来时,你都会检查当前时间并运行任何已过期的事件,然后返回等待。在大多数情况下,当添加新事件时,您当然会发现没有任何事件过期 - 您只需重新计算等待时间。

您可能希望将队列实现为优先队列,以便下一个到期事件始终位于最前面。

【讨论】:

  • 是的,就是这个想法。我只是不确定如何处理阻塞事件线程的细节。
【解决方案4】:

您当前的解决方案包含竞争条件 - 例如,这里:

QueueLock();                      // lock around queue access
bool empty = eventqueue.empty();  // check if empty
QueueUnlock();                    // unlock

pthread_mutex_lock(&eventmutex);  // lock event mutex (for condition)
if (empty)
{
    pthread_cond_wait(&eventclock, &eventmutex); // wait forever if empty
}

考虑如果队列最初是空的会发生什么,但另一个线程与此竞争并在QueueUnlock()pthread_mutex_lock(&amp;eventmutex) 之间推送一个新值 - 新事件的唤醒将被错过。另请注意,在SleepUntilNextEvent() 中,您访问eventqueue.top() 时无需持有队列锁。

传递给pthread_cond_wait() 的互斥锁应该是保护与信号相关的共享状态的互斥锁。在这种情况下,“共享状态”就是队列本身,因此您可以通过仅使用一个保护队列的互斥体来解决这些问题:

void Scheduler::RunEventLoop()
{

    pthread_mutex_lock(&queuemutex);
    while (threadrunning)
    {
        while (!eventqueue.empty() && e.Due())
        {                          // while pending due events exist
            Event e = eventqueue.top();
            eventqueue.pop();

            pthread_mutex_unlock(&queuemutex);
            e.DoEvent();           // perform the event
            e.Next();              // decrement repeat counter
            pthread_mutex_lock(&queuemutex);
                                   // reschedule event if necessary
            if (e.ShouldReschedule()) eventqueue.push(e);
        }

        SleepUntilNextEvent();     // wait for something to happen
    }
    pthread_mutex_unlock(&queuemutex);

    return;                        // if threadrunning is set to false, exit
}

/* Note: Called with queuemutex held */
void Scheduler::SleepUntilNextEvent()
{
    if (eventqueue.empty())
    {
        pthread_cond_wait(&eventclock, &queuemutex); // wait forever if empty
    }
    else
    {
        timespec t =                  // get absolute time of wakeup
            Bottime::GetMillisAsTimespec(eventqueue.top().Countdown() + 
                                         Bottime::GetCurrentTimeMillis());
        pthread_cond_timedwait(&eventclock, &queuemutex, &t); // sleep until event
    }
}

请注意,pthread_cond_wait()pthread_cond_timedwait() 在等待时释放互斥锁(互斥锁被释放,等待相对于正在发出信号的互斥锁以原子方式开始),因此调度程序在睡眠时不会持有互斥锁.

【讨论】:

  • 我刚刚确定了一个恶魔般的错误,当我将它插入到我正在使用的其余代码中时。稍后我将提出一个自我回答的问题,敬请期待。
猜你喜欢
  • 2015-05-28
  • 2011-08-05
  • 2014-07-15
  • 1970-01-01
  • 2021-09-11
  • 2014-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多