确保,函数在 lock_guard 下运行

Assure, function runs under lock_guard

我有一个函数 foo(),它在繁重的多线程环境中从不同的地方调用。

它正在修改一些敏感数据,但由于复杂性,我 不能 使用 inside 这个函数 std::lock_guard... - 这必须由调用函数保证。原因是该函数被连续调用多次,修改后的数据 [inside foo()] 必须 而不是 同时被其他线程触及,直到一个线程完成foo().

的几次调用的所有修改

我们都是人 - 尤其是我 - 我正在考虑如何确保 foo() 功能确实在 lock_guard... 控制之下的方法,我只是没有忘记锁定范围。

对我来说 - 运行 在 gcc 下,我不必考虑任何平台无关的解决方案 - 我是这样找到的:

auto foo( __attribute__ ((unused)) std::lock_guard<std::mutex> & guardActive ) -> void
{
   ...
}

那么,foo()必须这样调用,否则gcc已经编译不出来了:

{
    std::lock_guard<std::mutex> lockScope( mutexObject );
    // block of commands calling foo() which must NOT be interrupted
    ...
    foo( lockScope );
    ...
    foo( lockScope );
    ...
    foo( lockScope );
    ...
}

我的问题:

这样可以吗,或者有更好的办法吗?不能使用可重入互斥。

如果 foo 应该始终使用相同的互斥锁,例如,如果它是一个成员函数并且 class 持有一个互斥锁,我会采用这种方法:

void foo() {
    std::lock_guard<std::mutex> lock(someMutex);
    ...
}

否则,它通常会被推送到 method/class 的调用者,以调节来自外部不同线程的调用函数到使用锁的调用,如下所示:

{
    std::lock_guard<std::mutex> lock(someMutex);
    foo();
}

如果您真的认为这很容易出错(我不认为),您可以将它包装在 lambda 中以方便使用:

auto lockedFoo = [&]{ std::lock_guard<std::mutex> lock(someMutex); foo(); };

我确信,我的开始方式是正确的。我发现这个 post On unnamed parameters to functions, C++ 这或多或少证实了我。

现在,我去掉了参数名,它是这样实现的:

auto foo( std::lock_guard<std::mutex> & ) -> void
{
   ...
}

我觉得它更优雅,因为它不使用 gcc 扩展并且完全兼容 C++。

最全面的方法是创建一个'transaction object'。它是 'Delegation Pattern' 的变体,其中事务对象协调对对象的访问。可以认为是速度上的lock_guard<>。与 lock_guard<> 一样,它使用 RAII 锁定然后解锁对象,允许在其间进行多个操作。 也像 lock_guard<> 优化可能只是解包所有内联调用并导致代码与直接管理 mutex.

一样高效

此示例使用 private 禁止在事务外访问 'lock required' 成员。

在许多情况下,这肯定会过度设计,特别是考虑到完整的实现需要 FooConstTransFooTrans 类,具体取决于引用对象的 const 资格.

#include <iostream>
#include <mutex>
#include <type_traits>

class FooTrans;

class Foo {
public:
    Foo():m_x(0)
    {}

friend class FooTrans;    
private:
    int get_x() const{
        return m_x;
    }

    void set_x(int x){
        m_x=x;
    }

    void lock() const{
        m_mutex.lock();
    }

    void unlock() const{
        m_mutex.unlock();
    }

private:
    mutable std::mutex m_mutex;
    int m_x;
};

class FooTrans final {
public: 
    FooTrans(Foo& foo):m_foo(foo){
        m_foo.lock();
    }

    void set_x(int x){
        m_foo.set_x(x);
    }

    int get_x(){
        return m_foo.get_x();
    }

    ~FooTrans(){
        m_foo.unlock();
    }


    private:
      Foo& m_foo;
};

int main() {

    Foo foo;
    FooTrans ft(foo);

    ft.set_x(3);
    //ft is guaranteeing the next line outputs '3' even if other threads are trying to access foo.
    std::cout << ft.get_x() <<std::endl;

    return 0;
}