将互斥锁用作信号量?

15

我需要两个线程以“tick tock”的模式进行。 使用信号量实现时,它看起来很好:

Semaphore tick_sem(1);
Semaphore tock_sem(0);

void ticker( void )
{
   while( true )
   {
      P( tick_sem );
      do_tick();
      V( tock_sem );
   }
}

void tocker( void )
{
   while( true )
   {
      P( tock_sem );
      do_tock();
      V( tick_sem );
   }
}

然而,如果我使用一个互斥锁(技术上是一个二进制信号量)来做同样的事情,它会有一种奇怪的代码气味。

std::mutex tick_mutex;
std::mutex tock_mutex;
tock_mutex.lock();

void ticker( void )
{
   while( true )
   {
      tick_mutex.lock();
      do_tick();
      tock_mutex.unlock();
   }
}

void tocker( void )
{
   while( true )
   {
      tock_mutex.lock()
      do_tock();
      tick_mutex.unlock();
   }
}

我认为问题出在mutex不应该向另一个线程传递信息。(C++11标准委员会在try_lock中添加了一个无意义的失败来防止意外的信息传递; §30.4.1/14。)似乎mutex的作用是同步对变量的访问,然后变量才能向另一个线程传递信息。

最后,当使用std::condition_variable实现时,看起来是正确的,但更加复杂(需要tick_vs_tock变量、mutex和condition variable)。为了简洁起见,我省略了具体实现,但它非常直接明了。

这个mutex解决方案是否可行?还是有什么微妙的问题?

有没有解决我的tick/tock问题的好方法我还没有想到的?


顺便提一下:这个问题之所以出现,是因为C++0x没有std::semaphore,而双重std::mutex解决方案比std::condition_variable解决方案更简单。 - deft_code
3
在当前没有持有互斥锁的线程中调用 unlock() 函数是否有效? - Steve Jessop
@Steve,这是一个非常好的问题(暗示,暗示,挤眼)。 - deft_code
2个回答

14

互斥锁不仅是一个二进制信号量,它还有一个限制,只允许锁定线程解锁它。

你正在打破这个规则。

编辑:

来自MSDN

如果调用线程不拥有互斥对象,则 ReleaseMutex 函数会失败。

从谷歌搜索的某个关于pthread_mutex_unlock的网站中可以看到:

如果当前线程不拥有互斥锁,则 pthread_mutex_unlock() 函数可能会失败。

EPERM 线程无法拥有该互斥锁。

在其他互斥锁实现中也是一样的。这是有意义的,因为互斥锁应该保护线程对资源的访问,所以另一个线程不应该能够解锁它。


根据史蒂夫的评论。那真的是一个规则吗?这绝对是个好主意。 - deft_code
@deft - 我认为这取决于互斥锁的实现,这是与系统相关的。即使系统允许,解锁他人的互斥锁通常也不是一个好主意。 - littleadv
@deft_code:我还没有阅读FDIS中的互斥锁部分,但如果它不是一条规则,我会感到惊讶的。互斥锁有一个所有者,这是它们作为同步工具基本定义的一部分。 - Steve Jessop
3
在这里,30.1.4.2/22要求:“调用线程必须拥有该互斥锁”。如果没有拥有,则会产生未定义行为。 - Steve Jessop

10

由于您有使用信号量的情况,我认为解决方法是使用互斥锁和条件变量来实现一个可移植的信号量

这可能不是特别高效(因为每个信号量将使用一个互斥锁/条件变量对),但在拥有自己信号量的系统上(如Posix和Windows),您可以切换到替代实现。

显然信号量“太容易出错”。尽管尊重Boost,但我认为我们中至少有一些人可以做到。当然,您可能会用多个信号量进行复杂操作而陷入困境,它们是一个相当低级的工具。但当它们是正确的工具时,没有问题。


我同意“太容易出错”的说法很奇怪。互斥锁也可能出错,锤子也一样。 - asveikau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接