【问题标题】:Multithreading and Critical Sections Use - C++多线程和关键部分的使用 - C++
【发布时间】:2010-12-13 22:18:43
【问题描述】:

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

【问题讨论】:

    标签: c++ multithreading visual-studio-2008 winapi critical-section


    【解决方案1】:

    如果您跨线程共享资源,并且其中一些线程读取而其他线程写入,则必须始终对其进行保护。

    如果不了解更多关于您的代码的信息,很难提供更多建议,但请记住以下几点。

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

    【讨论】:

    • 谢谢,约翰。我一定会记住这些提示,尤其是没有。 6. 不过,我想我仍然缺少一些东西。进入临界区是否会阻止其他线程访问所有跨线程资源,或者EnterCriticalSection/LeaveCriticalSection 是否执行了一些魔法来阻止其他线程仅访问它们封装的引用资源?或者,有没有办法将资源与我完全错过的 CRITICAL_SECTION 对象相关联?
    • 严格来说,如果有写入器,则在读取时总是必须获取锁,这并不完全正确——有些数据结构和访问模式并非如此。但这是一个很好的经验法则。此外,如果读者被阻塞是一个常见的瓶颈,那么重要的是要知道还有诸如读写锁之类的东西,它允许并发读取。
    • @asveikau:是的,这只是适合新的多线程程序员的经验法则。还回复:读写器锁(又名 Gates),除非读取是长寿命的,否则使用 Gate 通常比只使用 crit sec 慢。有关更多信息,请参阅本文:queue.acm.org/detail.cfm?id=1454462
    • @Jim:进入 Crit Sec Foo 不会影响另一个试图进入 Crit Sec Bar 的线程。这是否回答了您的问题,还是我误解了?
    • @John Dibling:有点。假设我有一个临界区 Foo,并在其中访问了一个循环缓冲区对象。其他线程如何知道等待访问循环缓冲区对象,直到临界区完成?或者,EnterCriticalSection 是否会停止第二个线程,直到在第一个线程中调用 LeaveCriticalSection?如果是这种情况,是否应该调用 EnterCriticalSectionLeaveCriticalSection 引用 same CRITICAL_SECTION 对象?
    【解决方案2】:

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

    {
        CriticalSectionLock lock ( mutex_ );
    
        Do stuff...
    }
    

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

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

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

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

    【讨论】:

    • +1 用于避免在课堂外调用函数。我会把它提升到一个新的水平,并说通常你应该避免在持有锁的同时尽可能地调用库代码。 (除非你有充分的理由。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多