std::lock
(C++)
Locks the given Lockable objects lock1
, lock2
, …, lockn
using a deadlock avoidance algorithm to avoid Deadlock.
void task_a () {
// foo.lock(); bar.lock(); // replaced by:
std::lock (foo,bar);
std::cout << "task a\n";
foo.unlock();
bar.unlock();
}
Though generally, when we refer to locks in C++, we dont use the “naked” std::lock
. The locks below employ RAII, which automatically lock and unlock a mutex. These are:
std::lock_guard
std::unique_lock
- NEW
std::scoped_lock
Resources
- https://en.cppreference.com/w/cpp/thread/lock
- https://en.cppreference.com/w/cpp/thread/unique_lock
- https://www.modernescpp.com/index.php/prefer-locks-to-mutexes/
Mutex vs. Lock
Always prefer locks to mutexes, using a
1ock_guard
Motivation is here: https://www.modernescpp.com/index.php/prefer-locks-to-mutexes/
Locks take care of their resource following the RAII idiom. A lock automatically binds its mutex in the constructor and releases it in the destructor. This considerably reduces the risk of a deadlock because the runtime takes care of the mutex.
BAD
mutex m;
m.lock();
sharedVariable= getVar();
m.unlock();
- Easy to forget to unlock, easy to result in Deadlock
GOOD
std::mutex m,
std::lock_guard<std::mutex> lockGuard(m);
sharedVariable= getVar();
- Mutex gets released at the end of critical section (destructor gets called)
- he lifetime of std::lock_guard is limited by the brackets (http://en.cppreference.com/w/cpp/language/scope#Block_scope). That means its lifetime ends when it leaves the critical section.
Types of locks
std::lock_guard
https://cplusplus.com/reference/mutex/lock_guard/
The class lock_guard
is a mutex wrapper that provides a convenient RAII-style mechanism for owning a mutex for the duration of a scoped block.
// lock_guard example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::lock_guard
#include <stdexcept> // std::logic_error
std::mutex mtx;
void print_even (int x) {
if (x%2==0) std::cout << x << " is even\n";
else throw (std::logic_error("not even"));
}
void print_thread_id (int id) {
try {
// using a local lock_guard to lock mtx guarantees unlocking on destruction / exception:
std::lock_guard<std::mutex> lck (mtx);
print_even(id);
}
catch (std::logic_error&) {
std::cout << "[exception caught]\n";
}
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads) th.join();
return 0;
}
unique_lock
std::unique_lock
is mightier but more expansive than its small brother std::lock_guard.
unique_lock
locks in exclusive mode.
- https://en.cppreference.com/w/cpp/thread/unique_lock
- https://stackoverflow.com/questions/14709233/how-to-use-create-unique-lock-in-c
#include <mutex>
#include <thread>
#include <iostream>
struct Box
{
explicit Box(int num) : num_things{num} {}
int num_things;
std::mutex m;
};
void transfer(Box &from, Box &to, int num)
{
// don't actually take the locks yet
std::unique_lock lock1{from.m, std::defer_lock};
std::unique_lock lock2{to.m, std::defer_lock};
// lock both unique_locks without deadlock
std::lock(lock1, lock2);
from.num_things -= num;
to.num_things += num;
// 'from.m' and 'to.m' mutexes unlocked in 'unique_lock' dtors
}
int main()
{
Box acc1{100};
Box acc2{50};
std::thread t1{transfer, std::ref(acc1), std::ref(acc2), 10};
std::thread t2{transfer, std::ref(acc2), std::ref(acc1), 5};
t1.join();
t2.join();
std::cout << "acc1: " << acc1.num_things << "\n"
"acc2: " << acc2.num_things << '\n';
}
shared_lock
shared_lock
allows deferred locking, timed locking and transfer of lock ownership.
https://en.cppreference.com/w/cpp/thread/shared_lock/shared_lock
#include <shared_mutex>
#include <syncstream>
#include <iostream>
#include <thread>
#include <chrono>
std::shared_timed_mutex m;
int i = 10;
void read_shared_var(int id)
{
// both the threads get access to the integer i
std::shared_lock<std::shared_timed_mutex> slk(m);
const int ii = i; // reads global i
std::osyncstream(std::cout) << "#" << id << " read i as " << ii << "...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::osyncstream(std::cout) << "#" << id << " woke up..." << std::endl;
}
int main()
{
std::thread r1 {read_shared_var, 1};
std::thread r2 {read_shared_var, 2};
r1.join();
r2.join();
}