简体   繁体   English

我怎么醒来睡觉的pthread?

[英]How do I wake up a sleeping pthread?

I'm writing a program in C++. 我正在用C ++编写程序。 I've noticed that it's gaining a number of threads whose purpose is to do something at intervals, there are 3 or 4 of them. 我注意到它正在获得许多线程,其目的是每隔一段时间做一些事情,其中​​有3个或4个。 I decided to refactor by writing a scheduler service that the other places that use these threads could subscribe to, which should reduce the number of extra event threads I have running at any time to just one. 我决定通过编写一个调度程序服务来重构,使用这些线程的其他地方可以订阅,这应该减少我在任何时候运行的额外事件线程的数量。

I don't have any code that uses this yet; 我还没有任何使用它的代码; before I start writing it I'd like to know if it's possible, and get some feedback on my design. 在我开始编写它之前,我想知道它是否可行,并获得一些关于我的设计的反馈。 A brief description of what I'd like to accomplish is this: 我想要完成的内容的简要描述如下:

To add an event 添加活动

  1. Caller provides an event and a schedule 来电者提供活动和时间表
  2. Schedule provides the next occurrence of the event Schedule提供下一次事件
  3. (event, schedule) pair is added to an event queue (事件,计划)对被添加到事件队列中
  4. interrupt sleeping event thread (ie wake it up) 中断休眠事件线程(即唤醒它)

The event thread main loop 事件线程主循环

  1. try to get the next event in the event queue 尝试获取事件队列中的下一个事件
  2. If there is no pending event, go straight to 4 如果没有待处理事件,请直接转到4
  3. Get the time that the next event is supposed to occur 获取下一个事件应该发生的时间
  4. Sleep until next event (or forever if no waiting event) 睡到下一个事件(如果没有等待事件则永远睡觉)
  5. If sleeping was interrupted for any reason, loop back to 1 如果睡眠因任何原因中断,请循环回1
  6. If sleeping completed successfully, perform current event 如果睡眠成功完成,请执行当前事件
  7. Update queue (remove event, re-insert if it's a repeating event) 更新队列(删除事件,如果是重复事件则重新插入)
  8. Jump back to 1 跳回到1

I've done a bit of research and know that it's possible to interrupt a sleeping thread, and I believe that as long as simultaneous access to the event queue is prevented, there shouldn't be any dangerous behavior. 我已经做了一些研究,并且知道可以中断一个休眠线程,我相信只要防止同时访问事件队列,就不应该有任何危险的行为。 I'd imagine that waking a thread HAS to be possible, java's Thread's sleep() call throws an InterruptedException under some circumstances, and unless it doesn't rely on the operating system's underlying sleep call, it's got to be possible somehow. 我想,唤醒一个线程是可能的,java的Thread的sleep()调用在某些情况下抛出一个InterruptedException,除非它不依赖于操作系统的底层睡眠调用,否则它必须以某种方式。

Question

Can anyone comment on my approach? 任何人都可以评论我的方法吗? Is this a wheel I'd be better off not reinventing? 这是一个轮子,我最好不要重新发明? How, specifically, can you interrupt a sleeping thread such that execution resumes at the next instruction, and is it possible to detect this from the interrupted thread? 具体来说,如何中断睡眠线程,以便在下一条指令处恢复执行,是否可以从被中断的线程中检测到这一点?

A note about boost 关于提升的说明

I'd bet you can write a scheduler with boost, but this compiles and runs on a machine that's, for lack of a better phrase, a load of crap. 我敢打赌你可以编写一个带有提升的调度程序,但是这会编译并在一台机器上运行,因为缺少一个更好的短语,一堆垃圾。 I've compiled boost programs before on it, and each file that pulls boost in usually takes upwards of 30 seconds to compile. 我之前已经编译过boost程序,每个提升程序的文件通常需要30秒才能编译。 If I can avoid this irritating development obstacle, I'd very much like to. 如果我能避免这种恼人的发展障碍,我非常愿意。

Addendum - Working Code [Amended as per caf's suggestion] 附录 - 工作守则[根据咖啡馆的建议修订]

This is the code I've produced that works. 这是我生产的代码有效。 It's been rudimentarily tested but has properly handled both single and repeated events with varying delays. 它已经过初步测试,但已经妥善处理了具有不同延迟的单个和重复事件。

Here's the event thread's body: 这是事件线程的正文:

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
}

Here's the sleep function: 这是睡眠功能:

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
    }
}

Finally, AddEvent: 最后,AddEvent:

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

Relevant variable declarations: 相关变量声明:

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

To deal with the issue of generic events, each Event contains a pointer to an object of abstract type action , whos subclasses override action::DoEvent . 为了处理泛型事件的问题,每个Event包含一个指向抽象类型action对象的指针,子类重写action::DoEvent this method is called from inside Event::DoEvent . Event::DoEvent调用此方法。 actions are 'owned' by their events, ie they are automatically deleted if the event no longer needs to be rescheduled. actions由其事件“拥有”,即如果事件不再需要重新安排,则会自动删除它们。

What you are looking for is pthread_cond_t object, pthread_cond_timedwait and pthread_cond_wait functions. 您正在寻找的是pthread_cond_t对象, pthread_cond_timedwaitpthread_cond_wait函数。 You could create conditional variable isThereAnyTaskToDo and wait on it in event thread. 您可以创建条件变量isThereAnyTaskToDo并在事件线程中等待它。 When new event is added, you just wake event thread with pthread_cond_signal() . 添加新事件时,只需使用pthread_cond_signal()唤醒事件线程。

You have several possibilities both on *NIX platforms and on Windows. 在* NIX平台和Windows上都有几种可能性。 Your timer thread should wait using some kind of timed wait on event/conditional variable object. 您的计时器线程应该使用事件/条件变量对象上的某种定时等待来等待。 On POSIX platforms you can use pthread_cond_timedwait() . 在POSIX平台上,您可以使用pthread_cond_timedwait() On Windows, you can either choose to compute the necessary time delta and use WaitForSingleObject() on event handle, or you could use combination of event object with CreateTimerQueueTimer() or CreateWaitableTimer() . 在Windows上,您可以选择计算必要的时间增量并在事件句柄上使用WaitForSingleObject() ,也可以将事件对象与CreateTimerQueueTimer()CreateWaitableTimer() Boost does also have some synchronization primitives that you could use to implement this with the POSIX-like primitives but portably. Boost还有一些同步原语,您可以使用这些原语来实现与类似POSIX的原语,但可移植。

UPDATE: 更新:

POSIX does have some timer functionality as well, see create_timer() POSIX也有一些计时器功能,请参阅create_timer()

I agree with Greg and wilx - pthread_cond_timedwait() can be used to implement the behaviour you're after. 我同意Gregwilx - pthread_cond_timedwait()可以用来实现你所追求的行为。 I just wanted to add that you can simplify your event thread main loop: 我只是想补充一点,你可以简化你的事件线程主循环:

  1. try to get the next event in the event queue 尝试获取事件队列中的下一个事件
  2. If there is no pending event, go straight to 4 如果没有待处理事件,请直接转到4
  3. Get the time that the next event is supposed to occur 获取下一个事件应该发生的时间
  4. Wait on condition variable with pthread_cond_timedwait() until next event (or with pthread_cond_wait() if no scheduled events) 使用pthread_cond_timedwait()等待条件变量直到下一个事件(如果没有预定事件,则使用pthread_cond_wait()
  5. Try to get the next event in the event queue 尝试在事件队列中获取下一个事件
  6. If there are no events that have expired yet, go back to 4 如果还没有已过期的事件,请返回4
  7. Update queue (remove event, re-insert if it's a repeating event) 更新队列(删除事件,如果是重复事件则重新插入)
  8. Jump back to 5 跳回到5

So you don't care why you woke up - whenever you wake up, you check the current time and run any events that have expired, then go back to waiting. 所以你不在乎你为什么醒来 - 每当你醒来时,你检查当前时间并运行任何已经过期的事件,然后再回去等待。 In most cases when a new event is added you'll find that no events have expired, of course - you'll just recalculate the wait time. 在大多数情况下,当添加新事件时,您会发现没有事件已过期 - 当然 - 您只需重新计算等待时间。

You'll probably want to implement the queue as a Priority Queue, so that the next-event-to-expire is always at the front. 您可能希望将队列实现为优先级队列,以便下一个到期事件始终位于前端。

Your current solution contains race conditions - for example, here: 您当前的解决方案包含竞争条件 - 例如,此处:

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
}

Consider what happens if the queue is initially empty, but another thread races with this and pushes a new value in between QueueUnlock() and the pthread_mutex_lock(&eventmutex) - the wakeup for the new event will be missed. 考虑如果队列最初为空会发生什么,但另一个线程与此QueueUnlock()并在QueueUnlock()pthread_mutex_lock(&eventmutex)之间推送新值 - 将错过新事件的唤醒。 Note also that in SleepUntilNextEvent() you access eventqueue.top() without holding the queue lock. 另请注意,在SleepUntilNextEvent()您无需保留队列锁即可访问eventqueue.top()

The mutex passed to pthread_cond_wait() is supposed to be the mutex protecting the shared state that the signal is relevant to. 传递给pthread_cond_wait()的互斥锁应该是保护信号与之相关的共享状态的互斥锁。 In this case that "shared state" is the queue itself, so you can fix these problems by using just one mutex protecting the queue: 在这种情况下,“共享状态”是队列本身,因此您可以通过仅使用一个保护队列的互斥锁来解决这些问题:

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
    }
}

Note that pthread_cond_wait() and pthread_cond_timedwait() release the mutex while they are waiting (the mutex is released and the wait begins atomically with respect to the mutex being signalled), so the Scheduler is not holding the mutex while it is sleeping. 请注意, pthread_cond_wait()pthread_cond_timedwait()在等待时释放互斥锁(互斥锁被释放,等待以相对于被发信号的互斥锁的原子方式开始),因此调度程序在休眠时不持有互斥锁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM