如何将关键部分传递给另一个线程?

Posted

技术标签:

【中文标题】如何将关键部分传递给另一个线程?【英文标题】: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:线程不需要同步来访问只读共享资源。如果每个线程都有自己的可写资源副本,那么这些资源也不需要同步。只有共享的可写资源才需要同步。如果访问这些资源会导致如此多的线程争用,以至于锁的公平性成为一个主要问题,那么如果您只有一个线程,您的代码可能会运行得更快。 【参考方案1】:

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

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

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

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

【讨论】:

是的,我想防止线程直接重新进入一个部分。我不在乎接下来会运行哪个线程。我尝试了 YieldProcessor() 和 SwitchToThread() 但它们什么也没做。 @Mike:如果这是您想要的,那么您将不得不实际编写该逻辑,正如 F*** 所说。不仅仅是一个简单的功能可以确保为您服务。此外,您几乎不应该遇到您想要的行为(尤其是在现代多核 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 事件公平性。

【讨论】:

以上是关于如何将关键部分传递给另一个线程?的主要内容,如果未能解决你的问题,请参考以下文章

Core Data 将 objectID 传递给另一个线程

如何将 NSMutableArray 传递给另一个视图

将 UI Thread 方法传递给另一个线程以在 C# 中调用

是否可以将一个方法传递给另一个方法? [复制]

如何将标签内的标签快速传递给另一个视图控制器[关闭]

在 Angular 8 中,如何将一个组件传递给另一个组件的输入,然后在路由器模块中强制使用它