C++ - [Concurrency 8] Scoped Lock and `adopt_lock`
Posted by Rico's Nerd Cluster on June 1, 2023
adopt_lock
In a bank transfer system, if we transfer A->B and B->A concurrently. std::lock() locks two locks in a dead-lock free way, but does not unlock automatically, so we need to unlock them. One method is to transfer the mutex ownership to lock_guard with std::adopt_lock. std::adopt_lock is a tag saying “I’ve acquired the lock properly, just transfer the mutex ownership to me”. Then, lock_guard will unlock.
#include<iostream>
#include<mutex>
#include<thread>
#include<vector>structAccount{explicitAccount(intbalance):balance(balance){}intbalance;std::mutexm;};voidtransfer(Account&from,Account&to,intamount){// Lock both mutexes *atomically* to avoid deadlockstd::lock(from.m,to.m);// These lock_guards adopt already-locked mutexesstd::lock_guard<std::mutex>lock_from(from.m,std::adopt_lock);std::lock_guard<std::mutex>lock_to(to.m,std::adopt_lock);// ---- critical section: both accounts are locked here ----if(from.balance>=amount){from.balance-=amount;to.balance+=amount;std::cout<<"Transferred "<<amount<<" (from="<<&from<<" to="<<&to<<")\n";}else{std::cout<<"Insufficient funds ("<<&from<<")\n";}// ---------------------------------------------------------// When this function returns, lock_from and lock_to go out of scope// and automatically call from.m.unlock() and to.m.unlock().}intmain(){Accounta{1000};Accountb{1000};// Two threads transferring in opposite directions.// Without std::lock(...) this could deadlock if they lock in opposite orders.autot1=std::thread([&]{for(inti=0;i<100;++i){transfer(a,b,5);}});autot2=std::thread([&]{for(inti=0;i<100;++i){transfer(b,a,5);}});t1.join();t2.join();std::cout<<"Final balances: a="<<a.balance<<", b="<<b.balance<<"\n";}
Scoped_Lock (C++17)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
voidtransfer(Account&from,Account&to,intamount){std::scoped_locklock(from.m,to.m);// ---- critical section: both accounts are locked here ----if(from.balance>=amount){from.balance-=amount;to.balance+=amount;std::cout<<"Transferred "<<amount<<" (from="<<&from<<" to="<<&to<<")\n";}else{std::cout<<"Insufficient funds ("<<&from<<")\n";}}