我正在用C ++编写程序。我注意到它正在获得许多线程,其目的是每隔一段时间做一些事情,其中有3个或4个。我决定通过编写调度程序服务来重构,使用这些线程的其他地方可以订阅,这应该减少我在任何时候运行的额外事件线程的数量。
我还没有任何使用它的代码;在我开始编写它之前,我想知道它是否可行,并获得一些关于我的设计的反馈。我想要完成的内容的简要说明如下:
添加活动
事件线程主循环
我已经做了一些研究,并且知道可以中断休眠线程,我相信只要防止同时访问事件队列,就不会有任何危险的行为。我想,唤醒一个线程是可能的,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
由其活动“拥有”,即如果不再需要重新安排活动,则会自动删除这些活动。
答案 0 :(得分:9)
您要找的是pthread_cond_t
个对象,pthread_cond_timedwait
和pthread_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)
我同意Greg和wilx - pthread_cond_timedwait()
可用于实施您之后的行为。我只是想补充一点,你可以简化你的事件线程主循环:
pthread_cond_timedwait()
等待条件变量直到下一个事件(如果没有预定事件,则等待pthread_cond_wait()
)所以你不在乎为什么你醒来 - 每当你醒来时,你检查当前时间并运行任何已经过期的事件,然后再回去等待。在大多数情况下,当添加新事件时,您发现当前没有事件过期 - 您只需重新计算等待时间。
您可能希望将队列实现为优先级队列,以便下一个到期事件始终位于前端。
答案 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()
在等待时释放互斥锁(互斥锁被释放,等待以相对于被发信号通知的互斥锁开始),因此调度程序不持有互斥锁正在睡觉。