Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind mutex to object

Given the following example code:

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

void f(){
    mvar_mutex.lock();
    mvar = var * var;
    mvar_mutex.unlock();
}

I want to express that mvar_mutex is bound to the variable mvar and protects only that variable. mvar_mutex should not protect var because it is not bound to it. Hence the compiler would be allowed to transform the above code into the below code:

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();
}

This might reduce contention on the lock as less work is being done while holding it.

For int this can be done using std::atomic<int> mvar; and removing mvar_mutex, but for other types such as std::vector<int> this is not possible.

How do I express the mutex-variable binding in a way that C++ compilers understand it and do the optimization? It should be allowed to reorder any variable up or down across mutex boundaries for any variable that is not bound to that mutex

Since the code is being generated using clang::ASTConsumer and clang::RecursiveASTVisitor I am willing to use non-standard extensions and AST manipulations as long as clang (ideally clang 4.0) supports them and the resulting code does not need to be elegant or human-readable.

Edit since this seems to be causing confusion: The above transformation is not legal in C++. The described binding of mutex to variable doesn't exist. The question is about how to implement that or achieve the same effect.

like image 596
nwp Avatar asked Jul 04 '17 11:07

nwp


2 Answers

If you wish to achieve that the std::mutex will only be held until an operation is performed on the protected object, you can write a wrapper class as follows:

#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;
}

In the example above the factorial will be calculated in advance and the m_mutex will be acquired only when the assignment or the implicit conversion operator being called on mvar.

Assembly Output

like image 178
Akira Avatar answered Nov 09 '22 02:11

Akira


For the primitive data types you can use std::atomic with std::memory_order_relaxed. The documentation states that:

there are no synchronization or ordering constraints imposed on other reads or writes, only this operation's atomicity is guaranteed

In the following example, the atomicity of the assignation is guaranteed, but the compiler should be able to move the operations.

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

For objects, I thought of several solutions, but:

  • There is no standard way to remove ordering requirements from std::mutex.
  • It is not possible to create a std::atomic<std::vector>.
  • It is not possible to create a spinlock using std::memory_order_relaxed (see the example).

I have found some answers that state that:

  • If the function is not visible in the compilation unit, the compiler generates a barrier because it does not know which variables it uses.
  • If the function is visible and there is a mutex, the compiler generates a barrier. For example, see this and this

So, in order to express that mvar_mutex is bound to the variable, you can use some classes as stated by the other answers but I do not think it is possible to fully allow the reordering of the code.

like image 8
J. Calleja Avatar answered Nov 09 '22 01:11

J. Calleja