A few points:
- you should (strictly) prefer
std::scoped_lock
overstd::lock_guard
. - your scope locked takes an
std::mutex
, not a map (ie in its constructor) - the lambda passed to
foo
is 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
p
in the block as a whole, and within the completion handler, so be aware that thep
outside 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 makefoo
an awaitable functor whichco_yields
an index (p
, above), one which we canco_await
as 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