【发布时间】:2014-07-15 05:08:29
【问题描述】:
我开发了一个具有异步 API 的库。 其中一个 API,当它被调用时,它会创建一个任务,将其推送到任务线程并返回其任务 ID。 任务完成后,任务线程通过回调函数将结果通知给调用者。
顺序如下
来电者
C.1。调用者调用 API
C.2。库。创建任务并将其推送到任务线程的队列中
C.3。库。通过调用 condition_variable 的 notify_all 来唤醒任务线程
C.4。此时可以发生上下文切换,该线程将被挂起
C.6。此线程恢复后,Lib.返回任务ID
任务线程
T.1。任务线程执行任务。
T.2。任务完成后,任务线程通过回调将结果通知给调用者
问题
调用者通过任务ID检查回调函数的结果数据,但偶尔在API返回之前调用回调,调用者无法检查结果。
问题
我想完美地保证 API 在调用其回调之前返回任务 ID。 我能做什么?
我在 API 的主体中使用 lock_guard 来防止回调被调用,它大大降低了重现此问题的可能性。
但是因为lock_guard在API返回之前解锁了mutex,如果在mutex解锁之后发生context-switch,在API返回之前,这个问题很少会重现。
我也想阻止这种情况。
摘要代码
long AcbCore::setState(long appState, long playState) // API
{
StateTask* task = (StateTask*) createTask(TaskType::PLAYER_STATE_CHANGE, &isAppSwitchingStateFlag, appState);
std::lock_guard<std::mutex> lockGd (*task->getEventMutex());
pushTask(task); // at this position, context switch can occur by condTaskThread_.notify_all() of resumeLoop()
return taskId;
}
void AcbCore::pushTask(Task* task)
{
mtxTaskQueue_.lock();
queueTask_.push_back(task);
mtxTaskQueue_.unlock();
resumeLoop();
}
void AcbCore::resumeLoop()
{
mtxTaskThread_.lock();
mtxTaskThread_.unlock();
condTaskThread_.notify_all();
}
bool AcbCore::suspendLoop()
{
bool isTimeout = false;
if (ingTask_ != NULL) {
isTimeout = (condTaskThread_.wait_for(lockTaskThread_, std::chrono::seconds(AWAKE_TASK_THREAD_TIMEOUT)) == std::cv_status::timeout);
} else {
condTaskThread_.wait(lockTaskThread_);
}
return isTimeout;
}
void AcbCore::taskLoop() // loop of Task Thread
{
Task* task = NULL;
Action* action = NULL;
while (isInitialized_) {
while (popTask(task)) {
if (task->isCompleted()) {
fireEvent(task);
} else {
doNextTask(task);
}
}
if (suspendLoop()) { // if awaked by timeout
cancelTask(ingTask_, true);
}
}
}
void AcbCore::fireEvent(Task* task, bool bDelete)
{
std::string errorInfo = task->getErrorInfo();
task->waitToUnlockEvent();
// eventHandler_ : callback set by caller when Acb is initialized
eventHandler_(task->getTaskId(), task->getEventType(), appState_.now_, playState_.now_, errorInfo.c_str());
if (bDelete) {
deleteTask(task);
}
}
【问题讨论】:
-
不要将完成作为回调,而是将其设置为信号对象。然后调用者可以检查信令对象。 (无论哪种方式,你都必须有某种形式的同步对象,因为回调将在与调用者不同的线程中)
-
感谢您的热心回复,但我无法编辑来电者的代码,所以应该在图书馆管理。
-
我使用条件变量来控制两个线程,但是当任务线程被notify_all恢复时,调用者的线程可以被上下文切换挂起并在API返回之前调用回调
-
您根本无法编辑来电者?因为如果您再进行一次 API 调用以先同步生成任务 ID,然后让调用者将其与请求一起传递,则很容易解决。
-
很难从口头描述中理解发生了什么。请发布(伪)代码,详细说明对调用者和任务线程之间共享数据的每次访问,以及每次相关的同步调用。
标签: c++ multithreading context-switch