将互斥锁绑定到对象

nwp*_*nwp 23 c++ multithreading llvm-clang c++14

给出以下示例代码:

int var;
int mvar;
std::mutex mvar_mutex;

void f(){
    mvar_mutex.lock();
    mvar = var * var;
    mvar_mutex.unlock();
}
Run Code Online (Sandbox Code Playgroud)

我想表达它mvar_mutex绑定到变量mvar并仅保护该变量.mvar_mutex不应该保护,var因为它不受约束.因此,允许编译器将上面的代码转换为以下代码:

int var;
int mvar;
std::mutex mvar_mutex;

void f(){
    int r = var * var; //possible data race created if binding is not known
    mvar_mutex.lock();
    mvar = r;
    mvar_mutex.unlock();
}
Run Code Online (Sandbox Code Playgroud)

这可能会减少锁定上的争用,因为在保持锁定时工作量较少.

为此int可以使用std::atomic<int> mvar;和删除mvar_mutex,但对于其他类型,std::vector<int>这是不可能的.

如何以C++编译器理解并进行优化的方式表达互斥变量绑定?对于未绑定到该互斥锁的任何变量,应该允许在互斥锁边界上向上或向下重新排序任何变量

由于代码是使用生成的,clang::ASTConsumer并且clang::RecursiveASTVisitor我愿意使用非标准扩展和AST操作,只要clang(理想情况下是clang 4.0)支持它们并且生成的代码不需要优雅或人类可读.

编辑,因为这似乎导致混淆:上述转换在C++中是不合法的.所描述的互斥锁与变量的绑定不存在.问题是如何实现或实现相同的效果.

Aki*_*ira 10

如果您希望实现该std::mutex操作只会在对受保护对象执行操作之前保留,您可以编写一个包装器class,如下所示:

#include <cstdio>
#include <mutex>

template<typename T>
class LockAssignable {
public:
    LockAssignable& operator=(const T& t) {
        std::lock_guard<std::mutex> lk(m_mutex);
        m_protected = t;
        return *this;
    }
    operator T() const {
        std::lock_guard<std::mutex> lk(m_mutex);
        return m_protected;
    }    
    /* other stuff */
private:
    mutable std::mutex m_mutex;
    T m_protected {};
};

inline int factorial(int n) {
    return (n > 1 ? n * factorial(n - 1) : 1);
}

int main() {
    int var = 5;
    LockAssignable<int> mvar;

    mvar = factorial(var);
    printf("Result: %d\n", static_cast<int>(mvar));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,factorial将提前计算并且m_mutex仅在调用赋值或隐式转换运算符时才会获取mvar.

Assembly Output


J. *_*eja 8

对于原始数据类型可以使用std::atomicstd::memory_order_relaxed.文件指出:

没有对其他读取或写入施加同步或排序约束,只保证此操作的原子性

在以下示例中,保证了赋值的原子性,但编译器应该能够移动操作.

std::atomic<int> z = {0};
int a = 3;
z.store(a*a, std::memory_order_relaxed);
Run Code Online (Sandbox Code Playgroud)

对于对象,我想到了几个解决方案,但是:

  • 没有标准的方法来删除订购要求std::mutex.
  • 无法创建std::atomic<std::vector>.
  • 使用std::memory_order_relaxed(参见示例)无法创建自旋锁.

我找到了一些答案,说明:

  • 如果该函数在编译单元中不可见,则编译器会生成一个屏障,因为它不知道它使用了哪些变量.
  • 如果函数可见且存在互斥锁,则编译器会生成屏障.例如,看到这个这个

因此,为了表示mvar_mutex绑定到变量,您可以使用其他答案所述的某些类,但我认为不可能完全允许重新排序代码.


Mic*_*Roy 3

锁定的 var 模板怎么样?

template<typename Type, typename Mutex = std::mutex>
class Lockable
{
public:
   Lockable(_Type t) : var_(std::move(t));
   Lockable(_Type&&) = default;
   // ...  could need a bit more

   T operator = (const T& x) 
   { 
      std::lock_guard<Lockable> lock(*this);
      var_ = x;
      return x;
   }

   T operator *() const
   { 
      std::lock_guard<Lockable> lock(*this);
      return var_;
   }

   void lock() const   { const_cast<Lockable*>(this)->mutex_.lock(); }
   void unlock() const { const_cast<Lockable*>(this)->mutex_.unlock().; }
private:
  Mutex mutex_;
  Type var_;
};
Run Code Online (Sandbox Code Playgroud)

被赋值运算符锁定

Lockable<int>var;
var = mylongComputation();
Run Code Online (Sandbox Code Playgroud)

与 lock_guard 配合使用效果很好

Lockable<int>var;
std::lock_guard<Lockable<int>> lock(var);
var = 3;
Run Code Online (Sandbox Code Playgroud)

在容器上实用

Lockable<std::vector<int>> vec;
Run Code Online (Sandbox Code Playgroud)

ETC...

  • 为什么是“const_cast”?难道“mutable _Mutex”不是更好的解决方案吗? (8认同)
  • 没有针对代码中错误的编译器选项。不妨使用atomic&lt;&gt; (4认同)