交换互斥锁

Swapping mutex locks

本文关键字:交换      更新时间:2023-10-16

我在正确"交换"锁时遇到问题。请考虑以下情况:

bool HidDevice::wait(const std::function<bool(const Info&)>& predicate)
{
/* A method scoped lock. */
std::unique_lock waitLock(this->waitMutex, std::defer_lock);
/* A scoped, general access, lock. */
{
std::lock_guard lock(this->mutex);
bool exitEarly = false;
/* do some checks... */
if (exitEarly)
return false;
/* Only one thread at a time can execute this method, however
other threads can execute other methods or abort this one. Thus,
general access mutex "this->mutex" should be unlocked (to allow threads
to call other methods) while at the same time, "this->waitMutex" should
be locked to prevent multiple executions of code below. */
waitLock.lock(); // How do I release "this->mutex" here?
}
/* do some stuff... */
/* The main problem is with this event based OS function. It can 
only be called once with the data I provide, therefore I need to 
have a 2 locks - one blocks multiple method calls (the usual stuff) 
and "waitLock" makes sure that only one instance of "osBlockingFunction" 
is ruinning at the time. Since this is a thread blocking function,
"this->mutex" must be unlocked at this point. */
bool result = osBlockingFunction(...);
/* In methods, such as "close", "this->waitMutex" and others are then used 
to make sure that thread blocking methods have returned and I can safely
modify related data. */
/* do some more stuff... */
return result;
}

如何在不使代码过于复杂的情况下解决此"交换"问题?我可以在锁定另一个之前解锁this->mutex,但是我担心在那纳秒内,可能会出现竞争条件。

编辑:

假设有 3 个线程正在调用wait方法。第一个将锁定this->mutex,然后this->waitMutex,然后解锁this->mutex。第二个将锁定this->mutex,并且必须等待this->waitMutex可用。它不会解锁this->mutex.第三个将卡在锁定this->mutex上。

我想让最后 2 个线程等待this->waitMutex可用。

编辑 2:

带有osBlockingFunction的扩展示例。

它闻起来好像设计/实现应该有点不同,HidDevice::wait上的std::condition_variable cv并且只有一个互斥锁。当你写"其他线程可以执行其他方法或中止这个"时,会调用cv.notify_one来"中止"这个等待。cv.wait{enter wait & 解锁互斥体} 以原子方式打开,cv.notify{退出等待并以原子方式锁定互斥锁}。像这样HidDevice::wait更简单:

bool HidDevice::wait(const std::function<bool(const Info&)>& predicate)
{
std::unique_lock<std::mutex> lock(this->m_Mutex); // Only one mutex.
m_bEarlyExit = false;
this->cv.wait(lock,  spurious wake-up check);
if (m_bEarlyExit) // A bool data-member for abort. 
return;
/* do some stuff... */
}
  • 我的假设是(根据函数的名称)线程在/* do some checks... */上等待,直到某些逻辑成真。

"中止"等待,将由其他HidDevice函数负责,由其他线程调用:

void HidDevice::do_some_checks() /* do some checks... */
{
if ( some checks )
{
if ( other checks )
m_bEarlyExit = true;
this->cv.notify_one();
}
}

类似的东西。

我建议创建一个小的"解锁"工具。 这是一个具有反转语义的互斥包装器。lockunlocks,反之亦然:

template <class Lock>
class unlocker
{
Lock& locked_;
public:
unlocker(Lock& lk) : locked_{lk} {}
void lock() {locked_.unlock();}
bool try_lock() {locked_.unlock(); return true;}
void unlock() {locked_.lock();}
};

现在代替:

waitLock.lock(); // How do I release "this->mutex" here?

你可以说:

unlocker temp{lock};
std::lock(waitLock, temp);

其中lockunique_lock而不是持有mutexlock_guard

这将锁定waitLock并解锁mutex,就像通过一条不间断的指令一样。


现在,在编写了所有这些代码之后,我可以推断它可以转换为:

waitLock.lock();
lock.unlock();  // lock must be a unique_lock to do this

第一个版本是否具有或多或少的可读性是一个意见问题。 第一个版本更容易推理(一旦知道std::lock做什么)。 但第二个更简单。 但对于第二个,读者必须更仔细地思考正确性。


更新

只需阅读问题中的编辑。 此解决方案无法解决编辑中的问题:第二个线程将阻止第三个(和后续线程)在任何需要mutex但不waitMutex的代码中取得进展,直到第一个线程释放waitMutex

所以从这个意义上说,我的答案在技术上是正确的,但不能满足所需的性能特征。 我将把它留给参考。