条件变量与本地互斥锁的奇怪使用
And odd use of conditional variable with local mutex
仔细研究旧的大型项目的遗留代码,我发现使用了一些奇怪的方法来创建线程安全队列,如下所示:
template < typename _Msg>
class WaitQue: public QWaitCondition
{
public:
typedef _Msg DataType;
void wakeOne(const DataType& msg)
{
QMutexLocker lock_(&mx);
que.push(msg);
QWaitCondition::wakeOne();
}
void wait(DataType& msg)
{
/// wait if empty.
{
QMutex wx; // WHAT?
QMutexLocker cvlock_(&wx);
if (que.empty())
QWaitCondition::wait(&wx);
}
{
QMutexLocker _wlock(&mx);
msg = que.front();
que.pop();
}
}
unsigned long size() {
QMutexLocker lock_(&mx);
return que.size();
}
private:
std::queue<DataType> que;
QMutex mx;
};
wakeOne
从线程中用作一种“发布”功能,wait
从其他线程中调用并无限期地等待,直到消息出现在队列中。在某些情况下,线程之间的角色相反在不同的阶段并使用单独的队列。
这是通过创建本地 QMutex
使用 QMutex
的合法方式吗?我有点理解为什么有人可以在读取 que
的大小时这样做来避免死锁,但它是如何工作的呢?有没有更简单、更惯用的方法来实现这种行为?
拥有局部条件变量是合法的。但它通常没有意义。
正如你在这种情况下的计算是错误的。您应该使用成员:
void wait(DataType& msg)
{
QMutexLocker cvlock_(&mx);
while (que.empty())
QWaitCondition::wait(&mx);
msg = que.front();
que.pop();
}
另请注意,在 QWaitCondition::wait
的调用周围必须使用 while
而不是 if
。这是出于关于(可能的)虚假唤醒的复杂原因——Qt 文档在这里并不清楚。但更重要的是,唤醒和随后重新获取互斥锁不是原子操作,这意味着您必须重新检查变量队列是否为空。这可能是您之前获得 deadlocks/UB.
的最后一个案例
考虑一个空队列和一个调用者(线程 1)wait
进入 QWaitCondition::wait
的场景。该线程阻塞。然后线程 2 出现并向队列添加一个项目并调用 wakeOne
。线程 1 被唤醒并尝试重新获取互斥量。但是,线程 3 在您的 wait 实现中出现,在线程 1 之前获取互斥锁,看到队列不为空,处理单个项目并继续前进,释放互斥锁。然后被唤醒的线程 1 最终从 QWaitCondition::wait
获取互斥体 returns 并尝试处理...一个空队列。哎呀。
仔细研究旧的大型项目的遗留代码,我发现使用了一些奇怪的方法来创建线程安全队列,如下所示:
template < typename _Msg>
class WaitQue: public QWaitCondition
{
public:
typedef _Msg DataType;
void wakeOne(const DataType& msg)
{
QMutexLocker lock_(&mx);
que.push(msg);
QWaitCondition::wakeOne();
}
void wait(DataType& msg)
{
/// wait if empty.
{
QMutex wx; // WHAT?
QMutexLocker cvlock_(&wx);
if (que.empty())
QWaitCondition::wait(&wx);
}
{
QMutexLocker _wlock(&mx);
msg = que.front();
que.pop();
}
}
unsigned long size() {
QMutexLocker lock_(&mx);
return que.size();
}
private:
std::queue<DataType> que;
QMutex mx;
};
wakeOne
从线程中用作一种“发布”功能,wait
从其他线程中调用并无限期地等待,直到消息出现在队列中。在某些情况下,线程之间的角色相反在不同的阶段并使用单独的队列。
这是通过创建本地 QMutex
使用 QMutex
的合法方式吗?我有点理解为什么有人可以在读取 que
的大小时这样做来避免死锁,但它是如何工作的呢?有没有更简单、更惯用的方法来实现这种行为?
拥有局部条件变量是合法的。但它通常没有意义。
正如你在这种情况下的计算是错误的。您应该使用成员:
void wait(DataType& msg)
{
QMutexLocker cvlock_(&mx);
while (que.empty())
QWaitCondition::wait(&mx);
msg = que.front();
que.pop();
}
另请注意,在 QWaitCondition::wait
的调用周围必须使用 while
而不是 if
。这是出于关于(可能的)虚假唤醒的复杂原因——Qt 文档在这里并不清楚。但更重要的是,唤醒和随后重新获取互斥锁不是原子操作,这意味着您必须重新检查变量队列是否为空。这可能是您之前获得 deadlocks/UB.
考虑一个空队列和一个调用者(线程 1)wait
进入 QWaitCondition::wait
的场景。该线程阻塞。然后线程 2 出现并向队列添加一个项目并调用 wakeOne
。线程 1 被唤醒并尝试重新获取互斥量。但是,线程 3 在您的 wait 实现中出现,在线程 1 之前获取互斥锁,看到队列不为空,处理单个项目并继续前进,释放互斥锁。然后被唤醒的线程 1 最终从 QWaitCondition::wait
获取互斥体 returns 并尝试处理...一个空队列。哎呀。