3

对于在多线程应用程序中正确使用关键部分,我有点困惑。在我的应用程序中,有几个对象(一些循环缓冲区和一个串行端口对象)在线程之间共享。对这些对象的访问应该始终放在关键部分,还是只在特定时间?我怀疑只是在某些时候,因为当我尝试用EnterCriticalSection/包装每次使用时,LeaveCriticalSection我遇到了似乎是死锁的情况。您可能拥有的任何见解将不胜感激。谢谢。

4

2 回答 2

6

如果您跨线程共享资源,并且其中一些线程读取而其他线程写入,那么它必须始终受到保护。

如果不了解更多关于您的代码的信息,很难提供更多建议,但这里有一些需要牢记的一般要点。

1)关键部分保护资源,而不是进程

2)在所有线程中以相同的顺序进入/离开关键部分。如果线程 A 进入 Foo,然后进入 Bar,那么线程 B 必须以相同的顺序进入 Foo 和 Bar。如果你不这样做,你可以创造一个种族。

3) 进出必须按相反的顺序进行。例如,由于您输入 Foo 然后输入 Bar,因此您必须在离开 Foo 之前离开 Bar。如果你不这样做,你可能会造成死锁。

4) 在合理可能的最短时间段内保持锁定。如果您在开始使用 Bar 之前已完成 Foo,请在抓取 Bar 之前释放 Foo。但是您仍然必须牢记上面的订购规则。在同时使用 Foo 和 Bar 的每个线程中,您必须以相同的顺序获取和释放:

  Enter Foo
  Use Foo
  Leave Foo
  Enter Bar
  Use Bar
  Leave Bar

5)如果你只读了99.9%的时间,写了0.1%的时间,就不要自作聪明。即使您只是在阅读,您仍然必须输入暴击秒。这是因为您不希望在读取过程中开始写入。

6) 保持关键部分的粒度。每个临界区应该保护一个资源,而不是多个资源。如果您将临界区设置得太“大”,您可能会序列化您的应用程序或创建一组非常神秘的死锁或竞争。

于 2010-12-13T22:31:41.103 回答
2

在支持 RAII 的关键部分周围使用 C++ 包装器:

{
    CriticalSectionLock lock ( mutex_ );

    Do stuff...
}

锁的构造函数获取互斥体,而析构函数释放互斥体,即使抛出异常也是如此。

尽量不要一次获得超过一个锁,并尽量避免在持有锁的同时调用类外的函数;这有助于避免在不同的地方获得锁,因此您往往会减少死锁的可能性。

如果您必须同时获得多个锁,请按地址对锁进行排序并按顺序获得。这样,多个进程在没有协调的情况下以相同的顺序获得相同的锁。

使用 IO 端口,考虑是否需要在输入的同时锁定输出 - 通常情况下,您会遇到一些尝试写入,然后期望读取的情况,反之亦然。如果你有两把锁,那么如果一个线程先写后读,另一个线程先读后写,就会出现死锁。通常有一个执行 IO 的线程和一个请求队列可以解决这个问题,但这比用锁包装调用要复杂一些,而且没有更多细节我不能推荐它。

于 2010-12-13T22:41:56.227 回答