借鉴自CSDN
概述
在多线程同步时,条件变量是一个很好的选择,在使用时也需要注意。
一个线程调用QWaitCondition::wait
进行等待,另一个线程调用QWatiCondition::wakeAll
或QWaitCondition::wakeOne
进行唤醒。
示例
在下面的情况中,我们使用a
线程往b
线程发送一个数据包,然后阻塞等待回包才继续执行。b
线程不断从通信接口接收并解析数据,然后唤醒a
线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ... Send(&package); mutex.lock(); condition.wait(&lock); if(receivedPackage) { HandlePackage(package); } mutex.unlock(); ...
... receivedPackage = ParsePackage(buffer); condition.wakeAll(); ...
|
通常情况下,上述代码能跑得很好。但在某些特殊情况下,可能会出现混乱,大大降低通信可靠性。
在主线程中,调用Send(&packet)
发送后,假如通信线程立即收到回包,在主线程还来不及调用wait()
的时候,已经先wakeAll
了,显然这次唤醒是无效的,但主线程继续调用wait
,然后一直阻塞在那里,因为该回的包已经回了。
改进方法
QWaitCondition::wait
的第一个入参(此处只考虑此,不考虑截止时间)为一个锁的指针,且该锁必须已经lock
。在执行后,传入的锁会立即unlock
并阻塞当前线程。
在上述代码中,我们虽然传入了mutex
,但却没有任何意义,只是形式上为了满足参数传递。而这个mutex
本身就是为了让多个线程协调工作。
要了解如何使用他,首先看源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| bool QWaitCondition::wait(QMutex *mutex, QDeadlineTimer deadline) { if (!mutex) return false; if (mutex->isRecursive()) { qWarning("QWaitCondition: cannot wait on recursive mutexes"); return false; } report_error(pthread_mutex_lock(&d->mutex), "QWaitCondition::wait()", "mutex lock"); ++d->waiters; mutex->unlock(); bool returnValue = d->wait(deadline); mutex->lock(); return returnValue; }
bool QWaitConditionPrivate::wait(QDeadlineTimer deadline) { int code; forever { if (!deadline.isForever()) { code = wait_relative(deadline); } else { code = pthread_cond_wait(&cond, &mutex); } if (code == 0 && wakeups == 0) { continue; } break; } Q_ASSERT_X(waiters > 0, "QWaitCondition::wait", "internal error (waiters)"); --waiters; if (code == 0) { Q_ASSERT_X(wakeups > 0, "QWaitCondition::wait", "internal error (wakeups)"); --wakeups; } report_error(pthread_mutex_unlock(&mutex), "QWaitCondition::wait()", "mutex unlock"); if (code && code != ETIMEDOUT) report_error(code, "QWaitCondition::wait()", "cv wait"); return (code == 0); }
|
在wait
执行过程中,首先释放锁,进行等待,等待结束时再将其锁住。
通过这个原理可以对上述代码做出如下改动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ... Send(&package); mutex.lock(); condition.wait(&lock); if(receivedPackage) { HandlePackage(package); } mutex.unlock(); ...
... mutex.lock(); receivedPackage = ParsePackage(buffer); condition.wakeAll(); mutex.unlock(); ...
|
此时,执行的顺序如下:
- 发送完数据包后,
a
线程首先调用mutex.lock
进行加锁。
b
线程获取mutex
,假设a
线程还未执行到condition.wait
,那么b
线程无法获取到mutex
,将一直阻塞。直到a
线程调用condition.wait
,将mutex
释放并阻塞当前线程。
b
线程成功获取并锁住mutex
后,执行完相关操作并调用 condition.wakeAll
,此时a
线程condition.wait
完成,尝试对mutex
进行加锁,但此时mutex
被b
线程锁获取,a
线程继续阻塞。
- 直到
b
线程调用完成mutex.unlock
,a
线程condition.wait
才可以对mutex
进行加锁,执行后续操作,待完成相关逻辑,执行mutex.unlock
。
结语
至此,有关QWaitCondition
的相关注意事项总结完毕。且此类设计模式在原生C++
的std::condition_variable
与std::mutex
同样试用。