【问题标题】:How to pass Critical Section to another thread?如何将关键部分传递给另一个线程?
【发布时间】:2020-03-29 19:29:15
【问题描述】:

我有 3 个线程,同时恢复,用不同的参数调用同一个函数。 如何强制线程离开临界区并将其传递给另一个线程?

当我运行下面的代码时,while 循环被多次调用,直到另一个线程进入临界区(它也循环了很多次)。

DWORD WINAPI ClientThread(LPVOID lpParam)
{
    // thread logic 
    while(true)
    {
        EnterCriticalSection(&critical);
        // thread logic 
        LeaveCriticalSection(&critical);
        Sleep(0);
    }
    // thread logic 
    return 0;
}

换句话说,我怎样才能防止线程立即重新进入一个部分?

【问题讨论】:

  • 如果想让它顺序运行,为什么需要多个线程呢?只需在一个线程中按顺序运行您的代码。
  • 仍然不确定为什么需要线程,但也许this other question 会有所帮助。
  • 这听起来很像XY problem
  • 您可能想了解ticket locks。但是,我怀疑这是否是您所需要的,因为我仍然确信这是一个 XY 问题。
  • @Mike:线程不需要同步来访问只读共享资源。如果每个线程都有自己的可写资源副本,那么这些资源也不需要同步。只有共享的可写资源才需要同步。如果访问这些资源会导致如此多的线程争用,以至于锁的公平性成为一个主要问题,那么如果您只有一个线程,您的代码可能会运行得更快。

标签: c++ multithreading winapi critical-section


【解决方案1】:

您不能直接要求线程离开临界区。线程执行完毕后会离开它。

所以唯一的方法是阻止它进入关键部分,或者“要求”它提前完成。例如。通过在该部分中连续检查 atomic_flag 并中止停止线程的操作(如果已检查)。

如果你想阻止一个线程在它离开后直接重新进入一个部分,你可以让它,这将重新安排线程的执行。 如果您想从线程(A->B->C->D->A->B ...)中获得准确的顺序,您需要编写一个自定义调度程序或一个自定义“fair_mutex”来检测其他等待线程。

编辑:
这样的函数是BOOL SwitchToThread();doc

【讨论】:

  • 是的,我想防止线程直接重新进入一个部分。我不在乎接下来会运行哪个线程。我尝试了 YieldProcessor() 和 SwitchToThread() 但它们什么也没做。
  • @Mike:如果这是您想要的,那么您将不得不实际编写该逻辑,正如 Fabian 所说。不仅仅是一个简单的功能可以确保为您服务。此外,您几乎不应该遇到您想要的行为(尤其是在现代多核 CPU 上),因此您可能实施了错误的设计。
【解决方案2】:

正如另一个答案中提到的,您需要 Fair Mutex,Ticket Lock 可能是实现它的方法之一。

还有另一种方法,基于二进制信号量,它实际上接近于以前的临界区。像这样:

class old_cs
{
public:
  old_cs()
  {
     event = CreateEvent(NULL, /* bManualReset = */ FALSE, /* bInitialState =*/ TRUE, NULL);
     if (event == NULL) throw std::runtime_error("out of resources");
  }

  ~old_cs()
  {
     CloseHandle(event);
  }

  void lock()
  {
    if (count.fetch_add(1, std::memory_order_acquire) > 0)
      WaitForSingleObject(event, INFINITE);
  }

  void unlock()
  {
    if (count.fetch_sub(1, std::memory_order_release) > 1)
      SetEvent(event);
  }

  old_cs(const old_cs&) = delete;
  old_cs(old_cs&&) = delete;
  old_cs& operator=(const old_cs&) = delete;
  old_cs& operator=(old_cs&&) = delete;
private:
  HANDLE event;
  std::atomic<std::size_t> count = 0;
};

您可以在Critical Section Objects documentation 中找到以下内容:

从带有 Service Pack 1 (SP1) 的 Windows Server 2003 开始​​,线程 等待临界区不获取临界区 先到先得。此更改提高了性能 对于大多数代码来说意义重大。但是,某些应用程序依赖于 先进先出 (FIFO) 排序,可能表现不佳或不 所有在当前版本的 Windows 上(例如,应用程序 一直使用临界区作为速率限制器)。为了保证 您的代码继续正常工作,您可能需要添加 额外的同步级别。例如,假设您有一个 使用关键线程的生产者线程和消费者线程 section 对象来同步他们的工作。创建两个事件对象, 每个线程使用一个来表示它已为另一个线程准备好 线程继续。消费者线程将等待生产者 在进入临界区之前发出它的事件信号,并且 生产者线程将等待消费者线程发出其事件信号 在进入临界区之前。每个线程离开后 临界区,它发出释放另一个线程的信号。

因此,本文中的算法是 Windows XP 及更早版本中关键部分的简化版本。

上述算法不是一个完整的临界区,它缺乏递归支持、旋转、低资源情况处理。

它还依赖于 Windows 事件公平性。

【讨论】:

    猜你喜欢
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    相关资源
    最近更新 更多