Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/150.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 何时使用递归互斥?_C++_Multithreading_Recursion_Mutex_Recursive Mutex - Fatal编程技术网

C++ 何时使用递归互斥?

C++ 何时使用递归互斥?,c++,multithreading,recursion,mutex,recursive-mutex,C++,Multithreading,Recursion,Mutex,Recursive Mutex,我知道递归互斥允许多次锁定互斥而不会出现死锁,并且应该解锁相同的次数。但是在什么特定情况下需要使用递归互斥?我正在寻找设计/代码级别的情况。例如,当您有递归调用它的函数,并且希望获得对它的同步访问时: void foo() { ... mutex_acquire(); ... foo(); ... mutex_release(); } 如果没有递归互斥,您必须首先创建一个“入口点”函数,当您有一组相互递归的函数时,这会变得很麻烦。没有递归互斥: void foo_entry(

我知道递归互斥允许多次锁定互斥而不会出现死锁,并且应该解锁相同的次数。但是在什么特定情况下需要使用递归互斥?我正在寻找设计/代码级别的情况。

例如,当您有递归调用它的函数,并且希望获得对它的同步访问时:

void foo() {
   ... mutex_acquire();
   ... foo();
   ... mutex_release();
}
如果没有递归互斥,您必须首先创建一个“入口点”函数,当您有一组相互递归的函数时,这会变得很麻烦。没有递归互斥:

void foo_entry() {
   mutex_acquire(); foo(); mutex_release(); }

void foo() { ... foo(); ... }

如果一个线程试图获取(再次)它已经拥有的互斥锁时被阻塞,这肯定会是一个问题


是否有理由不允许同一线程多次获取互斥

递归和非递归互斥锁有不同的用例。没有一种互斥类型可以轻松替换另一种。非递归互斥体的开销较小,递归互斥体在某些情况下具有有用甚至需要的语义,在其他情况下具有危险甚至破坏的语义。在大多数情况下,有人可以使用基于非递归互斥的不同的更安全、更有效的策略来取代使用递归互斥的任何策略

  • 如果您只想将其他线程排除在使用受互斥体保护的资源之外,那么您可以使用任何互斥体类型,但可能希望使用非递归互斥体,因为它的开销较小
  • 如果要递归调用锁定同一互斥锁的函数,则它们
    • 必须使用一个递归互斥体,或
    • 必须一次又一次地解锁和锁定同一个非递归互斥锁(当心并发线程!)(假设这在语义上是合理的,它仍然可能是一个性能问题),或者
    • 必须以某种方式注释它们已经锁定的互斥体(模拟递归所有权/互斥体)
  • 如果要从一组这样的对象中锁定多个受互斥保护的对象(这些对象可能是通过合并生成的),可以选择
    • 要精确地为每个对象使用一个互斥对象,允许多个线程并行工作,或者
    • 使用每个对象对任何可能共享的递归互斥体进行一次引用,以降低无法将所有互斥体锁定在一起的概率,或
    • 对每个对象使用一个可比较的引用到任何可能共享的非递归互斥对象,避免多次锁定的意图
  • 如果您想在不同的线程中释放一个锁,而不是它已经被锁定,那么您必须使用非递归锁(或者递归锁,它显式地允许释放,而不是抛出异常)
  • 如果要使用同步变量,则需要能够在等待任何同步变量时显式解锁互斥锁,以便允许在其他线程中使用该资源。这只有在使用非递归互斥体时才可能,因为递归互斥体可能已经被当前函数的调用方锁定

如果您想查看使用递归互斥体的代码示例,请查看Linux/Unix“Electric Fence”的源代码TWA是一种常见的Unix工具,用于查找“边界检查”读/写溢出和不足,以及使用以前释放的内存


只需编译并将ElectricFence与源代码链接(选项-g与gcc/g++),然后使用link选项-lefence将其与软件链接,然后开始逐步调用malloc/free

我今天遇到了递归互斥的需要,我认为这可能是迄今为止发布的答案中最简单的例子: 这是一个公开两个API函数的类,Process(…)和reset()

这两个函数不能同时运行,因为它们修改了类的内部结构,所以我想使用互斥锁。 问题是,Process()在内部调用reset(),这会造成死锁,因为已经获取了mMutex。
相反,使用递归锁锁定它们可以解决问题。

如果您希望能够从类的其他公共方法中的不同线程调用公共方法,并且这些公共方法中的许多都会更改对象的状态,则应使用递归互斥锁。事实上,我习惯在默认情况下使用递归互斥,除非有充分的理由(例如,特殊的性能考虑)不使用它

它带来了更好的接口,因为您不必将实现划分为非锁定部分和锁定部分,并且您可以自由地使用公共方法,在所有方法中也可以放心使用


在我的经验中,它还导致了在锁定方面更容易获得正确的接口。

一般来说,正如在座的每个人所说,这更多的是关于设计。递归互斥通常用于递归函数中

其他人没有告诉你的是,递归互斥体实际上几乎没有开销

一般来说,简单互斥体是一个32位的密钥,位0-30包含所有者的线程id,位31是一个标志,表示互斥体是否有等待者。它有一个锁定方法,这是一个CAS原子竞赛,在失败时用系统调用声明互斥。这里的细节并不重要。看起来是这样的:

class mutex {
public:
  void lock();
  void unlock();
protected:
  uint32_t key{}; //bits 0-30: thread_handle, bit 31: hasWaiters_flag
};
递归_互斥通常实现为:

class recursive_mutex : public mutex {
public:
  void lock() {
    uint32_t handle = current_thread_native_handle(); //obtained from TLS memory in most OS
    if ((key & 0x7FFFFFFF) == handle) { // Impossible to return true unless you own the mutex.
      uses++; // we own the mutex, just increase uses.
    } else {
      mutex::lock(); // we don't own the mutex, try to obtain it.
      uses = 1;
    }
  }

  void unlock() {
    // asserts for debug, we should own the mutex and uses > 0
    --uses;
    if (uses == 0) {
      mutex::unlock();
    }
  }
private:
  uint32_t uses{}; // no need to be atomic, can only be modified in exclusion and only interesting read is on exclusion.
};
正如您所看到的,它完全是一个用户空间结构。(但基本互斥不是,如果它在原子比较和交换锁定中无法获得密钥,它可能会陷入系统调用,如果has_waitersFlag打开,它将在解锁时执行系统调用)


对于基本互斥体实现:

虽然这是真的,但锁定有很大的开销,因此创建线程不安全的代码版本可能不是一个坏主意
class recursive_mutex : public mutex {
public:
  void lock() {
    uint32_t handle = current_thread_native_handle(); //obtained from TLS memory in most OS
    if ((key & 0x7FFFFFFF) == handle) { // Impossible to return true unless you own the mutex.
      uses++; // we own the mutex, just increase uses.
    } else {
      mutex::lock(); // we don't own the mutex, try to obtain it.
      uses = 1;
    }
  }

  void unlock() {
    // asserts for debug, we should own the mutex and uses > 0
    --uses;
    if (uses == 0) {
      mutex::unlock();
    }
  }
private:
  uint32_t uses{}; // no need to be atomic, can only be modified in exclusion and only interesting read is on exclusion.
};