Reputation: 3350
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
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
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
unique_ptr
to the lock_guard
that holds the mutexThat 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
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
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
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