【问题标题】:Behaviour of TMultiReadExclusiveWriteSynchronizer when demoting write-lock to read-lock将写锁降级为读锁时 TMultiReadExclusiveWriteSynchronizer 的行为
【发布时间】:2014-07-16 01:12:57
【问题描述】:

以下代码导致我的应用程序冻结,即使该程序只有一个线程在运行。

semaphoreboost::shared_ptr<TMultiReadExclusiveWriteSynchronizer>

if (semaphore)
{
  semaphore->BeginWrite();

  // Perform write operations on the shared object here.

  semaphore->BeginRead();
  semaphore->EndWrite();

  // Perform read-only operations on the object, allowing other threads to also read.

  semaphore->EndRead();
  semaphore->BeginWrite(); // Program locks up here.
}

调试信息,供@v.ouddou 评论。

当我单步执行代码时,只有一个线程。顺便说一下,这是一个窗口应用程序,所以程序入口点是 WinMain,如果这很重要的话)。

当我踏入死亡线时(最后一个semaphore->BeginWrite(),程序冻结,如果我停止它,则有两个线程。我的主线程在汇编区,但调用堆栈是WaitForSingleObject-> WaitForSingleObjectEx->ZwWaitForSingleObject.

RtlUserThreadStart的入口点还有第二个没有堆栈信息的线程。我认为这个线程只是为了让我能够暂停应用程序。此时,我的代码中没有任何意义来创建第二个线程。

【问题讨论】:

  • 如果你可以在调试中运行,然后点击调试器的“暂停”,那么你可以检查进程中所有正在运行的线程的堆栈,并检查它们的位置。如果只有您的主线程,则堆栈可能没有意义,在线程处于取消调度调用的情况下。就像一个信号量等待一个信号。在这种情况下,线程进入等待队列,操作系统会立即安排其他事情,在这种情况下,您的堆栈通常没有意义。
  • 基本上这似乎是 TMultiReadExclusiveWriteSynchronizer 实现中的一个错误。但是,您首先调用BeginRead 然后是写锁(EndWrite)然后才调用EndRead 的用例无论如何都很奇怪。你能解释一下为什么需要这个吗?
  • @v.oddou 添加了一些信息来回应你所说的。
  • @Daniel 我发布的代码只是一个最小的例子。在第一个BeginWrite() 之后,主线程打算修改一个共享对象。以下BeginRead(); EndWrite(); 旨在将锁从写锁降级为读锁。然后,该线程向子线程发送通知,告知它们发生了变化,它们需要响应新数据。在所有客户端都收到通知后,我们不再需要担心共享数据的变化,因此我们使用EndRead(); 释放读锁。最后的BeginWrite(); 只是为了复制问题。
  • 重点是,'EndWrite(); BeginRead();` 具有相同的功能(离开写锁,进入读锁)。请注意,在升级或降级锁时,其他写入者总是有可能获得写锁! (这是避免死锁的固有功能)!

标签: c++ multithreading c++builder


【解决方案1】:

这似乎解决了问题。然而,它需要添加一个临界区来防止读取期间出现死锁。

// Care: Two threads attempting to acquire the locks here would deadlock.
critical_section->Acquire();
semaphore->BeginRead();
semaphore->BeginWrite();
critical_section->Leave();

// Perform write operations on the shared object here.

semaphore->EndWrite();

// Perform read-only operations on the object, allowing other threads to also read.

semaphore->EndRead();

semaphore->BeginWrite(); // No longer deadlocks.

【讨论】:

  • 其实不需要临界区。 “修复”是重新排序 BeginReadBeginWrite(旧代码是 BeginWriteBeginReadEndWriteEndRead。这是不受支持的情况)
猜你喜欢
  • 2011-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-23
  • 2011-12-04
  • 1970-01-01
相关资源
最近更新 更多