【问题标题】:How to do mutual exclusion with two groups of functions?两组函数如何互斥?
【发布时间】:2015-10-19 23:11:15
【问题描述】:

我不是直接询问互斥体,尽管有些相关。

有2组函数A和B。当A中的函数运行时,B中的任何函数都不应该运行,反之亦然。

有多个线程可以调用A或B中的函数,以上都应该满足。

我可以通过混合一些锁定方式来想出很多方法来做到这一点,但我不知道什么是最好的方法,或者至少是一些缩小范围的好方法。

【问题讨论】:

  • 您使用哪种语言 - C 或 C++?您使用的是该语言的哪个版本?您在哪个平台或平台上工作?您是在寻找使用语言标准提供的设施的代码,还是在寻找主机 O/S 提供的设施?而且,一般来说,为什么您认为互斥锁不合适?一个互斥锁可用于保护 set A 和 set B 功能;您必须在调用任一集中的任何例程之前获取该互斥锁。当然,这会降低并发性,但这似乎是可以接受的。
  • @JonathanLeffler 我可以完全访问 posix 线程。我正在尝试找到一种方法来最小化锁定。
  • 要明确一点——A 组中可以有 200 个线程调用一个函数,只要 B 组中的函数中没有线程,是吗?
  • 如果您有 200 个线程当前正在使用 A 组中的函数,那么需要访问 B 组中的函数的线程如何停止调用 A 组中的函数的新线程,这样它就不会缺乏访问权限。相反,当然。你想强加什么规则?我可以看到一个简单的互斥锁是不合适的,但我不确定是什么。
  • ^^ 我认为这将是锁内状态机的那些混乱的事情之一。释放锁后,不允许运行的线程在事件/信号量上保持。

标签: c++ c multithreading synchronization


【解决方案1】:

在看到 Martin James 的答案之前,我正在编写此代码。事实证明,这是说明他的答案的未经测试的 c++11 代码,所以请为该答案投票。

我认为带引用计数的三向开关可能有效(未测试类)

struct ThreeWaySwitch
{
  std::mutex mtx;
  std::condition_variable cv;
  enum State { IDLE, A, B } state;
  int count;

  ThreeWaySwitch() : state(IDLE), count(0) {}
  void switchA() 
  { 
    std::unique_lock<std::mutex> l(mtx); 
    while (state == B) cv.wait(l); 
    state = A; 
    ++count; 
  }
  void switchB() 
  { 
    std::unique_lock<std::mutex> l(mtx); 
    while (state == A) cv.wait(l); 
    state = B; 
    ++count; 
  }
  void switchIdle() 
  { 
    {
      std::unique_lock<std::mutex> l(mtx); 
      if (count == 0) return;  // already idle
      if (--count != 0) return; // not idle yet
      state = IDLE;
    }
    cv.notify_all(); 
  }
};

所以每个 A 函数在进入时调用 switchA(),在退出时调用 switchIdle(),对 B 函数调用 switchB()。

【讨论】:

  • 嗯.. 我们基因也必须工作:)
  • @MartinJames:谢谢,如果您同意,我可以将代码放入您的答案中并删除我的代码。您的描述与我的想法非常相似。
  • TBH,这样的设计几乎是唯一有机会在复杂的锁定要求下可靠工作的解决方案。每当我看到多个锁时,我都会感到 ~~紧张~~
  • 完美答案。小修改:模板应在锁定对象中指定。例如std::unique_lock&lt;std::mutex&gt; l(mtx);
  • @Homaei C++ 没有多进程锁定。有 posix 信号量 (softprayog.in/programming/posix-semaphores),windows 有它的机制 (docs.microsoft.com/en-us/windows/win32/sync/…)。我还没用过,所以我不评论这些。
【解决方案2】:

好的,所以有三种有效状态:

两个组中都没有线程正在运行函数。

一个或多个线程正在运行组 A 中的函数。

一个或多个线程正在 B 组中运行函数。

与大多数复杂的锁定方案一样,这可以通过锁内的状态进行管理。状态可以是一个枚举,例如 (EgsFree, EgsAonly, EgsBonly),一个 threadCount int 跟踪任一组中有多少线程在受保护的函数中,以及一个同步对象的容器,不允许在其上运行的线程是等待。

函数需要通过在进入时调用锁定状态(使用参数标识它们的组)和退出时进行协作。

在尝试进入时发现不允许运行其功能的线程必须通过引发事件/信号量来阻止自己,将其存储在容器中并在退出锁定状态后等待它。这允许将计数减为 0 的线程(并因此再次将状态设置为 Efree)在退出时迭代容器并准备好所有等待的线程。

有了这个框架,您可以根据需要实现反饥饿算法或其他管理方案。

锁中的状态数据很混乱,我希望看到一个更简单的解决方案来解决这个问题。 OTOH,它仍然是我所知道的复杂资源管理的唯一安全方式,可以依靠它来工作并且没有隐藏的竞争/死锁潜力。如果您必须对锁定方案考虑太多,那么它很可能会在某个时候搞砸:)

【讨论】:

  • 是的,反饥饿算法将是关键,并且是一个持续的实施噩梦。也就是说,OP 不可能避免在这方面想得太多。因此,我得出结论,虽然我不知道 OP 在做什么,但她/他几乎肯定做错了。
  • ^ 我没有建议这是一个好的要求,只是可以满足它;)我理解,如果一个线程调用一个在其主体中循环或阻塞的组函数,那就结束了对于其他组中尝试调用函数的所有线程:(
  • 同意,这就是我投票的原因。塑料袋也很有用,但它们总是带有“窒息危险”的警告。我认为这个答案需要一个类似的警告,“精神错乱的危险”:)
  • 嗯,OP 要求有点不正常,我的回答只是填充单元格。
  • @user3386109 为什么你认为我做错了什么?例如,您有一个复制内存管理器,您当然不希望对象访问和对象移动同时发生。当有多个线程时,这两组动作应该严格分开。
【解决方案3】:

我尝试为您的问题找到更开放/更简单的答案,您可以这样做:

class A
{
public:
    static void foo1();
    static void foo2();
private:
    static std::shared_ptr<MyMutexClass> m_mmc;
};

class B
{
public:
    static void foo3();
    static void foo4();
private:
    static std::shared_ptr<MyMutexClass> m_mmc;
};

在你的 foo 函数中,使用组提供的静态互斥体放置守卫。 您只需要找到一种方法来正确初始化 A::m_mmc 和 B::m_mmc。

【讨论】:

    猜你喜欢
    • 2013-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 2013-07-07
    • 2018-07-25
    相关资源
    最近更新 更多