Difference between revisions of "cpp/thread/scoped lock"
(the unlocks are not necessarily in any order) |
m (mark optional -> cond present) |
||
(13 intermediate revisions by 8 users not shown) | |||
Line 1: | Line 1: | ||
{{cpp/title|scoped_lock}} | {{cpp/title|scoped_lock}} | ||
{{cpp/thread/scoped_lock/navbar}} | {{cpp/thread/scoped_lock/navbar}} | ||
− | {{ddcl | header=mutex | since=c++17 | 1= | + | {{ddcl|header=mutex|since=c++17|1= |
template< class... MutexTypes > | template< class... MutexTypes > | ||
class scoped_lock; | class scoped_lock; | ||
}} | }} | ||
− | The class {{tt|scoped_lock}} is a mutex wrapper that provides a convenient [[enwiki:Resource_Acquisition_Is_Initialization|RAII-style]] mechanism for owning | + | The class {{tt|scoped_lock}} is a mutex wrapper that provides a convenient [[enwiki:Resource_Acquisition_Is_Initialization|RAII-style]] mechanism for owning zero or more mutexes for the duration of a scoped block. |
When a {{tt|scoped_lock}} object is created, it attempts to take ownership of the mutexes it is given. When control leaves the scope in which the {{tt|scoped_lock}} object was created, the {{tt|scoped_lock}} is destructed and the mutexes are released. If several mutexes are given, deadlock avoidance algorithm is used as if by {{lc|std::lock}}. | When a {{tt|scoped_lock}} object is created, it attempts to take ownership of the mutexes it is given. When control leaves the scope in which the {{tt|scoped_lock}} object was created, the {{tt|scoped_lock}} is destructed and the mutexes are released. If several mutexes are given, deadlock avoidance algorithm is used as if by {{lc|std::lock}}. | ||
Line 14: | Line 14: | ||
===Template parameters=== | ===Template parameters=== | ||
{{par begin}} | {{par begin}} | ||
− | {{par | MutexTypes | the types of the mutexes to lock. The types must meet the {{named req|Lockable}} requirements unless {{c|1=sizeof...(MutexTypes)==1}}, in which case the only type must meet {{named req|BasicLockable}}}} | + | {{par|MutexTypes|the types of the mutexes to lock. The types must meet the {{named req|Lockable}} requirements unless {{c|1=sizeof...(MutexTypes) == 1}}, in which case the only type must meet {{named req|BasicLockable}}}} |
{{par end}} | {{par end}} | ||
===Member types=== | ===Member types=== | ||
{{dsc begin}} | {{dsc begin}} | ||
− | {{dsc hitem | Member type | Definition}} | + | {{dsc hitem|Member type|Definition}} |
− | {{dsc | {{tt|mutex_type}} {{mark| | + | {{dsc|{{tt|mutex_type}}<br>{{mark cond present}}| |
+ | If {{c|1=sizeof...(MutexTypes) == 1}}, member type {{tt|mutex_type}} is the same as {{tt|Mutex}}, the sole type in {{tt|MutexTypes...}}. | ||
+ | Otherwise, there is no member {{tt|mutex_type}}. | ||
+ | }} | ||
{{dsc end}} | {{dsc end}} | ||
===Member functions=== | ===Member functions=== | ||
{{dsc begin}} | {{dsc begin}} | ||
− | {{dsc inc | cpp/thread/scoped_lock/dsc constructor}} | + | {{dsc inc|cpp/thread/scoped_lock/dsc constructor}} |
− | {{dsc inc | cpp/thread/scoped_lock/dsc destructor}} | + | {{dsc inc|cpp/thread/scoped_lock/dsc destructor}} |
− | {{dsc inc | cpp/thread/scoped_lock/dsc operator | + | {{dsc inc|1=cpp/thread/scoped_lock/dsc operator=}} |
{{dsc end}} | {{dsc end}} | ||
+ | |||
+ | ===Notes=== | ||
+ | A common beginner error is to "forget" to give a {{tt|scoped_lock}} variable a name, e.g. {{c|std::scoped_lock(mtx);}} (which default constructs a {{tt|scoped_lock}} variable named {{tt|mtx}}) or {{c|std::scoped_lock{mtx};}} (which constructs a prvalue object that is immediately destroyed), thereby not actually constructing a lock that holds a mutex for the rest of the scope. | ||
+ | |||
+ | {{feature test macro|__cpp_lib_scoped_lock|std=C++17|value=201703L|[[#Top|{{tt|std::scoped_lock}}]]}} | ||
===Example=== | ===Example=== | ||
− | {{example|The following example uses {{tt|std::scoped_lock}} to lock pairs of mutexes without deadlock and is RAII-style. | + | {{example |
− | + | |The following example uses {{tt|std::scoped_lock}} to lock pairs of mutexes without deadlock and is RAII-style. | |
+ | |code= | ||
+ | #include <chrono> | ||
+ | #include <functional> | ||
+ | #include <iostream> | ||
#include <mutex> | #include <mutex> | ||
+ | #include <string> | ||
#include <thread> | #include <thread> | ||
− | |||
#include <vector> | #include <vector> | ||
− | + | using namespace std::chrono_literals; | |
− | + | ||
− | + | ||
− | struct Employee { | + | struct Employee |
− | + | { | |
+ | std::vector<std::string> lunch_partners; | ||
std::string id; | std::string id; | ||
− | |||
std::mutex m; | std::mutex m; | ||
− | std::string | + | Employee(std::string id) : id(id) {} |
+ | std::string partners() const | ||
{ | { | ||
std::string ret = "Employee " + id + " has lunch partners: "; | std::string ret = "Employee " + id + " has lunch partners: "; | ||
− | for( const auto& partner : lunch_partners ) | + | for (int count{}; const auto& partner : lunch_partners) |
− | ret += | + | ret += (count++ ? ", " : "") + partner; |
return ret; | return ret; | ||
} | } | ||
}; | }; | ||
− | void send_mail(Employee &, Employee &) | + | void send_mail(Employee&, Employee&) |
{ | { | ||
− | // | + | // Simulate a time-consuming messaging operation |
− | std::this_thread::sleep_for( | + | std::this_thread::sleep_for(1s); |
} | } | ||
− | void assign_lunch_partner(Employee &e1, Employee &e2) | + | void assign_lunch_partner(Employee& e1, Employee& e2) |
{ | { | ||
static std::mutex io_mutex; | static std::mutex io_mutex; | ||
Line 70: | Line 81: | ||
{ | { | ||
− | // | + | // Use std::scoped_lock to acquire two locks without worrying about |
// other calls to assign_lunch_partner deadlocking us | // other calls to assign_lunch_partner deadlocking us | ||
// and it also provides a convenient RAII-style mechanism | // and it also provides a convenient RAII-style mechanism | ||
Line 99: | Line 110: | ||
int main() | int main() | ||
{ | { | ||
− | Employee alice(" | + | Employee alice("Alice"), bob("Bob"), christina("Christina"), dave("Dave"); |
− | // | + | // Assign in parallel threads because mailing users about lunch assignments |
// takes a long time | // takes a long time | ||
std::vector<std::thread> threads; | std::vector<std::thread> threads; | ||
Line 109: | Line 120: | ||
threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); | threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); | ||
− | for (auto &thread : threads) thread.join(); | + | for (auto& thread : threads) |
− | std::cout << alice. | + | thread.join(); |
− | << christina. | + | std::cout << alice.partners() << '\n' << bob.partners() << '\n' |
+ | << christina.partners() << '\n' << dave.partners() << '\n'; | ||
} | } | ||
|p=true | |p=true | ||
− | | output= | + | |output= |
− | + | Alice and Bob are waiting for locks | |
− | + | Alice and Bob got locks | |
− | + | Christina and Bob are waiting for locks | |
− | + | Christina and Alice are waiting for locks | |
− | + | Dave and Bob are waiting for locks | |
− | + | Dave and Bob got locks | |
− | + | Christina and Alice got locks | |
− | + | Christina and Bob got locks | |
− | Employee | + | Employee Alice has lunch partners: Bob, Christina |
− | Employee | + | Employee Bob has lunch partners: Alice, Dave, Christina |
− | Employee | + | Employee Christina has lunch partners: Alice, Bob |
− | Employee | + | Employee Dave has lunch partners: Bob |
}} | }} | ||
− | === Defect reports === | + | |
+ | ===Defect reports=== | ||
{{dr list begin}} | {{dr list begin}} | ||
− | {{dr list item|wg=lwg|dr=2981|std=C++17|before=redundant deduction guide from {{tt|scoped_lock<MutexTypes...>}} was provided | after=removed}} | + | {{dr list item|wg=lwg|dr=2981|std=C++17|before=redundant deduction guide from {{tt|scoped_lock<MutexTypes...>}} was provided|after=removed}} |
{{dr list end}} | {{dr list end}} | ||
===See also=== | ===See also=== | ||
{{dsc begin}} | {{dsc begin}} | ||
− | {{dsc inc | cpp/thread/dsc unique_lock | + | {{dsc inc|cpp/thread/dsc unique_lock}} |
− | + | {{dsc inc|cpp/thread/dsc lock_guard}} | |
− | + | ||
− | {{dsc inc | cpp/thread/dsc lock_guard}} | + | |
{{dsc end}} | {{dsc end}} | ||
{{langlinks|de|es|fr|it|ja|pt|ru|zh}} | {{langlinks|de|es|fr|it|ja|pt|ru|zh}} |
Latest revision as of 00:07, 12 July 2024
Defined in header <mutex>
|
||
template< class... MutexTypes > class scoped_lock; |
(since C++17) | |
The class scoped_lock
is a mutex wrapper that provides a convenient RAII-style mechanism for owning zero or more mutexes for the duration of a scoped block.
When a scoped_lock
object is created, it attempts to take ownership of the mutexes it is given. When control leaves the scope in which the scoped_lock
object was created, the scoped_lock
is destructed and the mutexes are released. If several mutexes are given, deadlock avoidance algorithm is used as if by std::lock.
The scoped_lock
class is non-copyable.
Contents |
[edit] Template parameters
MutexTypes | - | the types of the mutexes to lock. The types must meet the Lockable requirements unless sizeof...(MutexTypes) == 1, in which case the only type must meet BasicLockable |
[edit] Member types
Member type | Definition |
mutex_type (conditionally present) |
If sizeof...(MutexTypes) == 1, member type |
[edit] Member functions
constructs a scoped_lock , optionally locking the given mutexes (public member function) | |
destructs the scoped_lock object, unlocks the underlying mutexes (public member function) | |
operator= [deleted] |
not copy-assignable (public member function) |
[edit] Notes
A common beginner error is to "forget" to give a scoped_lock
variable a name, e.g. std::scoped_lock(mtx); (which default constructs a scoped_lock
variable named mtx
) or std::scoped_lock{mtx}; (which constructs a prvalue object that is immediately destroyed), thereby not actually constructing a lock that holds a mutex for the rest of the scope.
Feature-test macro | Value | Std | Feature |
---|---|---|---|
__cpp_lib_scoped_lock |
201703L | (C++17) | std::scoped_lock
|
[edit] Example
The following example uses std::scoped_lock
to lock pairs of mutexes without deadlock and is RAII-style.
#include <chrono> #include <functional> #include <iostream> #include <mutex> #include <string> #include <thread> #include <vector> using namespace std::chrono_literals; struct Employee { std::vector<std::string> lunch_partners; std::string id; std::mutex m; Employee(std::string id) : id(id) {} std::string partners() const { std::string ret = "Employee " + id + " has lunch partners: "; for (int count{}; const auto& partner : lunch_partners) ret += (count++ ? ", " : "") + partner; return ret; } }; void send_mail(Employee&, Employee&) { // Simulate a time-consuming messaging operation std::this_thread::sleep_for(1s); } void assign_lunch_partner(Employee& e1, Employee& e2) { static std::mutex io_mutex; { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " are waiting for locks" << std::endl; } { // Use std::scoped_lock to acquire two locks without worrying about // other calls to assign_lunch_partner deadlocking us // and it also provides a convenient RAII-style mechanism std::scoped_lock lock(e1.m, e2.m); // Equivalent code 1 (using std::lock and std::lock_guard) // std::lock(e1.m, e2.m); // std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock); // std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock); // Equivalent code 2 (if unique_locks are needed, e.g. for condition variables) // std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock); // std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock); // std::lock(lk1, lk2); { std::lock_guard<std::mutex> lk(io_mutex); std::cout << e1.id << " and " << e2.id << " got locks" << std::endl; } e1.lunch_partners.push_back(e2.id); e2.lunch_partners.push_back(e1.id); } send_mail(e1, e2); send_mail(e2, e1); } int main() { Employee alice("Alice"), bob("Bob"), christina("Christina"), dave("Dave"); // Assign in parallel threads because mailing users about lunch assignments // takes a long time std::vector<std::thread> threads; threads.emplace_back(assign_lunch_partner, std::ref(alice), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(bob)); threads.emplace_back(assign_lunch_partner, std::ref(christina), std::ref(alice)); threads.emplace_back(assign_lunch_partner, std::ref(dave), std::ref(bob)); for (auto& thread : threads) thread.join(); std::cout << alice.partners() << '\n' << bob.partners() << '\n' << christina.partners() << '\n' << dave.partners() << '\n'; }
Possible output:
Alice and Bob are waiting for locks Alice and Bob got locks Christina and Bob are waiting for locks Christina and Alice are waiting for locks Dave and Bob are waiting for locks Dave and Bob got locks Christina and Alice got locks Christina and Bob got locks Employee Alice has lunch partners: Bob, Christina Employee Bob has lunch partners: Alice, Dave, Christina Employee Christina has lunch partners: Alice, Bob Employee Dave has lunch partners: Bob
[edit] Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR | Applied to | Behavior as published | Correct behavior |
---|---|---|---|
LWG 2981 | C++17 | redundant deduction guide from scoped_lock<MutexTypes...> was provided
|
removed |
[edit] See also
(C++11) |
implements movable mutex ownership wrapper (class template) |
(C++11) |
implements a strictly scope-based mutex ownership wrapper (class template) |