我怎么醒来睡觉的pthread?

时间:2012-08-19 07:40:19

标签: c++ linux pthreads sleep

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

我还没有任何使用它的代码;在我开始编写它之前,我想知道它是否可行,并获得一些关于我的设计的反馈。我想要完成的内容的简要说明如下:

添加活动

  1. 来电者提供活动和时间表
  2. 计划提供下一次出现的事件
  3. (事件,日程安排)对被添加到事件队列
  4. 中断休眠事件线程(即将其唤醒)
  5. 事件线程主循环

    1. 尝试获取事件队列中的下一个事件
    2. 如果没有待处理的事件,请直接进入4
    3. 获取下一个活动应该发生的时间
    4. 睡到下一个事件(或者如果没有等待事件则永远睡觉)
    5. 如果睡眠因任何原因中断,请循环回1
    6. 如果睡眠成功完成,请执行当前事件
    7. 更新队列(删除事件,如果是重复事件则重新插入)
    8. 跳回1
    9. 我已经做了一些研究,并且知道可以中断休眠线程,我相信只要防止同时访问事件队列,就不会有任何危险的行为。我想,唤醒一个线程是可能的,java的Thread的sleep()调用在某些情况下抛出一个InterruptedException,除非它不依赖于操作系统的底层睡眠调用,否则它必须以某种方式。

      问题

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

      关于提升的说明

      我敢打赌你可以用boost来编写一个调度程序,但是这会编译并在一台机器上运行,因为缺少一个更好的短语,就是一堆垃圾。我之前已经编译过boost程序,每个提升程序的文件通常需要30秒才能编译。如果我能避免这种恼人的发展障碍,我非常愿意。

      附录 - 工作守则[根据咖啡馆的建议修改]

      这是我制作的代码。它已经过初步测试,但已经妥善处理了单个和重复事件,并且有不同的延迟。

      这是事件线程的正文:

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

      最后,AddEvent:

      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由其活动“拥有”,即如果不再需要重新安排活动,则会自动删除这些活动。

4 个答案:

答案 0 :(得分:9)

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

答案 1 :(得分:3)

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

<强>更新

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

答案 2 :(得分:3)

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

  1. 尝试获取事件队列中的下一个事件
  2. 如果没有待处理的事件,请直接进入4
  3. 获取下一个活动应该发生的时间
  4. 使用pthread_cond_timedwait()等待条件变量直到下一个事件(如果没有预定事件,则等待pthread_cond_wait()
  5. 尝试获取事件队列中的下一个事件
  6. 如果尚未有任何事件过期,请返回4
  7. 更新队列(删除事件,重新插入,如果它是重复事件)
  8. 跳回5
  9. 所以你不在乎为什么你醒来 - 每当你醒来时,你检查当前时间并运行任何已经过期的事件,然后再回去等待。在大多数情况下,当添加新事件时,您发现当前没有事件过期 - 您只需重新计算等待时间。

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

答案 3 :(得分:1)

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

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(&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()在等待时释放互斥锁(互斥锁被释放,等待以相对于被发信号通知的互斥锁开始),因此调度程序不持有互斥锁正在睡觉。