I want to free my code from the 5 std::mutex::unlock calls per function in favor of std::lock_guard. But I have the problem, that I have to keep the mutex locked when entering asynchronous callbacks.
Take this code for example:
std::map<std::size_t, std::set<std::size_t>> my_map;
size_t bar1 = ...;
size_t bar2 = ...;
std::lock_guard guard(my_map); // lock the map
my_map[p].insert(10);
foo(bar1, [&my_map](const auto& p){
my_map[p].insert(10);
// here we can unlock
});
foo is computing sth and then asynchronously calling the given lambda function and passing a parameter e to it. I need my_map to be locked the whole time.
Keep in mind, that this is just an code example which might not map the real problem, so please don’t optimize my given code.
std::lock_guardand it’s superior alternativestd::scoped_lockboth deliberately cannot be moved, so you won’t be able to make it work with these types. But, assumingfoowill cause the lambda to be destroyed once it is done executing you can use the more flexiblestd::unique_lockand move that into the lambda:std::unique_lock lock(my_map); my_map[p].insert(10); foo(bar1, [&my_map, l = std::move(lock)] (const auto& p) { my_map[p].insert(10); });If the lambda does not capture anything whose destructor could call code that tries to lock this same mutex by value and
foodestroys the lambda immediately after the lambda has finished executing then this does exactly what you want.A few points:
- you should (strictly) prefer
std::scoped_lockoverstd::lock_guard. - your scope locked takes an
std::mutex, not a map (ie in its constructor) - the lambda passed to
foois called a completion handler; one way to thread a bunch of (related) handlers without needing explicit locks is to use so-called strands. As long as all the operations which have to be performed serially are coroutines, spawned within strand in question, you can actually have a thread pool of executors running, and asio will take care of all the locking complexity for you. - you’re using
pin the block as a whole, and within the completion handler, so be aware that thepoutside has to be well-defined, and that the interior one (in the lambda) shadows the outer one. (I’m a fan of shadowing, btw, the company I used to have lint settings which yelled when shadowing happens, but for me it’s one of the features I want, because it leads to more concise, uniform, clear names – and that in turn is because shadowing allows them to be reused, but in the specific context… anyyyyway) - modern C++ tends to favour async style code. Instead of passing a completion handler to
foo, you makefooan awaitable functor whichco_yieldsan index (p, above), one which we canco_awaitas in:// note: the below has to run in a coroutine ... my_map[q].insert(10); // renamed outer p to q to avoid collision now that async-style leaves p in the same scope as outer code! const auto& p = co_await foo(bar1); // use p - If you want to do 5 on existing code, follow the guidelines here to wrap legacy callback code into something that works with C++20 awaitables.
- If I may be so bold, you’re describing something intrinsically async, so you may want to consider using
boost::asio, then you get access to all this. - and nowadays, all this is dead easy to install using conan
deleted by creator
- you should (strictly) prefer



