Anonymous Entity
Anonymous Entity

Reputation: 3350

Toggling mutex lock using guard

How can I refactor the following code to use the recommended lock_guards?

bool locked = false;
bool sync() {
    if (locked) {
        mutex.unlock();
    } else {
        mutex.lock();
    }
    locked = !locked;
    return locked;
}

Desired usage pattern:

while (sync()) {
    // do safe things
}

Basically I am trying to emulate the with statement from Python. Example:

from multiprocessing import Lock
with Lock():
    # do safe things

Upvotes: 0

Views: 412

Answers (5)

Quentin
Quentin

Reputation: 63124

Simply use a scope:

{
    std::lock_guard<std::mutex> lock{mutex};

    // Your operations here
}

If you really want to have a scope with a header, C++17's if-with-initializer can be bent into that shape easily:

if(std::lock_guard<std::mutex> lock{mutex}; true) {
    // Your operations here
}

... and then you can hide it inside a (thoughtfully named) macro.

Finally, and with my declining of all responsibility about how you use that thing, here is a C++14 implementation:

template <class T>
struct Lock {
    Lock(T &mtx)
    : guard{mtx} { }

    constexpr operator bool() const { return false; }

    std::lock_guard<T> guard;
};

// Replace the pragmas for a compiler other than Clang or GCC
// so it doesn't complain about the unused variable
#define withLock(mtx) \
    _Pragma("GCC diagnostic push") \
    _Pragma("GCC diagnostic ignored \"-Wunused-variable\"") \
    if(auto const &_lockGuard = Lock<std::remove_reference_t<decltype(mtx)>>{mtx}); else \
    _Pragma("GCC diagnostic pop")

// ...

withLock(mutex) {
    // Your operations here
}

... but really, a simple scope works fine and doesn't have to be documented and argued for against code reviewers.

Upvotes: 2

mindriot
mindriot

Reputation: 5668

Here's a pretty evil, but working, hack:

#include <memory>
#include <mutex>
#include <iostream>

#define with(m) for (std::unique_ptr<std::lock_guard<std::mutex>> lock( \
                     new std::lock_guard<std::mutex>(m)); lock; lock.reset())

int main()
{
  std::mutex m;
  with (m)
  {
    std::cout << "got the mutex" << std::endl;
  }
}

The with(m) expands to a for loop header which

  • creates a unique_ptr to the lock_guard that holds the mutex
  • in the loop condition, verifies that the pointer is non-null
  • in the increment part, resets the pointer.

That has the effect of executing the loop body which follows the with() macro exactly once, with the lock being held. Slightly hackish because of the macro and the pointer, but nevertheless a bit cleaner than constructing a while loop and toggling the mutex state.

If you have C++14, you can simplify the macro a bit using std::make_unique. If you can use C++17, Quentin's solution will be more elegant.

Of course, this isn't really the C++ way of doing it, just a hack to get some syntactic sugar. So, unless you really insist on following a Python-like syntax, you could just use the standard C++ way of using a lock_guard like so:

{
  std::lock_guard<std::mutex> lock(my_mutex);
  // Do whatever
}

Upvotes: -1

Richard Hodges
Richard Hodges

Reputation: 69864

A full example of how i'd normally approach it:

#include <mutex>
#include <future>
#include <iostream>
#include <vector>
#include <chrono>
#include <random>

// here is the relevant function

template<class Mutex, class F>
decltype(auto) with_lock(Mutex& mutex, F&& func)
{
    using mutex_type = std::decay_t<Mutex>;
    using lock_type = std::lock_guard<mutex_type>;
    lock_type lock { mutex };
    return func();
}

// here is a test

int main()
{
    std::mutex m;
    std::default_random_engine eng { std::random_device()() };

    std::vector<std::future<void>> futures;
    for (int i = 0 ; i < 100 ; ++i)
    {
        futures.push_back(std::async(std::launch::async, [i, &m, &eng]
        {
            std::uniform_int_distribution<int> dist(10, 100);
            std::this_thread::sleep_for(std::chrono::milliseconds(dist(eng)));
            with_lock(m, [i]
            {
                std::cout << "thread index: " << i << std::endl;
            });
        }));
    }

    for (auto& f : futures)
    {
        f.get();
    }
}

sample output (note, no races on cout):

thread index: 63
thread index: 62
thread index: 30
thread index: 49
thread index: 25
thread index: 1
thread index: 58
thread index: 33
thread index: 72
thread index: 75
thread index: 11
thread index: 22
thread index: 46
thread index: 41
thread index: 20
thread index: 36
thread index: 37
thread index: 23
thread index: 45
thread index: 82
thread index: 0
thread index: 28
thread index: 88
thread index: 3
thread index: 74
thread index: 84
thread index: 31
thread index: 9
thread index: 34
thread index: 93
thread index: 24
thread index: 98
thread index: 38
thread index: 55
thread index: 43
thread index: 52
thread index: 40
thread index: 69
thread index: 67
thread index: 91
thread index: 89
thread index: 86
thread index: 76
thread index: 21
thread index: 29
thread index: 53
thread index: 81
thread index: 10
thread index: 96
thread index: 68
thread index: 7
thread index: 73
thread index: 78
thread index: 54
thread index: 59
thread index: 83
thread index: 60
thread index: 47
thread index: 19
thread index: 6
thread index: 17
thread index: 56
thread index: 57
thread index: 66
thread index: 70
thread index: 39
thread index: 26
thread index: 13
thread index: 79
thread index: 15
thread index: 5
thread index: 94
thread index: 14
thread index: 77
thread index: 32
thread index: 48
thread index: 87
thread index: 92
thread index: 61
thread index: 80
thread index: 18
thread index: 27
thread index: 12
thread index: 71
thread index: 4
thread index: 2
thread index: 99
thread index: 35
thread index: 50
thread index: 51
thread index: 65
thread index: 64
thread index: 16
thread index: 42
thread index: 90
thread index: 8
thread index: 44
thread index: 85
thread index: 97
thread index: 95

Upvotes: 0

Valentin
Valentin

Reputation: 5488

Just create a locker std::lock_guard<std::mutex> lock(mutex); and mutex will be automatically released at the end of lock's life.

std::mutex mutex;
....
{
     std::lock_guard<std::mutex> lock(mutex);
     // do do safe things
     // mutex will be released here
}

Upvotes: 5

SergeyA
SergeyA

Reputation: 62563

Your design is not supported by lock guards, and there is a good reason for it. You shouldn't ever have the need to toggle the lock. The correct usage pattern for shared resource is that every thread accessing the resource first acquires the lock through the guard, than uses the resource, and than exists - which triggers automatic lock release.

No one ever needs to toggle the lock.

Upvotes: 0

Related Questions