Namespaces
Variants
Views
Actions

Difference between revisions of "cpp/thread/scoped lock"

From cppreference.com
< cpp‎ | thread
m (mark optional -> cond present)
 
(7 intermediate revisions by 6 users not shown)
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}}
  
Line 20: Line 20:
 
{{dsc begin}}
 
{{dsc begin}}
 
{{dsc hitem|Member type|Definition}}
 
{{dsc hitem|Member type|Definition}}
{{dsc|{{tt|mutex_type}} {{mark|1=if {{tt|1=sizeof...(MutexTypes)==1}}}}|Mutex, the sole type in {{tt|MutexTypes...}}}}
+
{{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}}
  
Line 31: Line 34:
  
 
===Notes===
 
===Notes===
A schoolboy error would be to "forget" to give a {{tt|scoped_lock}} variable a name, e.g. {{c|std::scoped_lock(mtx);}} (which is a functional-style cast that results in the direct initialization of a prvalue expression) 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.
+
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}}]]}}
 
{{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=
 
|code=
 
#include <chrono>
 
#include <chrono>
Line 56: Line 60:
 
     {
 
     {
 
         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 += partner + " ";
+
             ret += (count++ ? ", " : "") + partner;
 
         return ret;
 
         return ret;
 
     }
 
     }
 
};
 
};
 
   
 
   
void send_mail(Employee &, Employee &)
+
void send_mail(Employee&, Employee&)
 
{
 
{
     // simulate a time-consuming messaging operation
+
     // Simulate a time-consuming messaging operation
 
     std::this_thread::sleep_for(1s);
 
     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 77: Line 81:
  
 
     {
 
     {
         // use std::scoped_lock to acquire two locks without worrying about
+
         // 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 108: Line 112:
 
     Employee alice("Alice"), bob("Bob"), christina("Christina"), dave("Dave");
 
     Employee alice("Alice"), bob("Bob"), christina("Christina"), dave("Dave");
 
   
 
   
     // assign in parallel threads because mailing users about lunch assignments
+
     // 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 116: 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)
+
     for (auto& thread : threads)
 
         thread.join();
 
         thread.join();
 
     std::cout << alice.partners() << '\n'  << bob.partners() << '\n'
 
     std::cout << alice.partners() << '\n'  << bob.partners() << '\n'
Line 131: Line 135:
 
Christina and Alice got locks
 
Christina and Alice got locks
 
Christina and Bob got locks
 
Christina and Bob got locks
Employee Alice has lunch partners: Bob Christina
+
Employee Alice has lunch partners: Bob, Christina
Employee Bob has lunch partners: Alice Dave Christina
+
Employee Bob has lunch partners: Alice, Dave, Christina
Employee Christina has lunch partners: Alice Bob
+
Employee Christina has lunch partners: Alice, Bob
 
Employee Dave has lunch partners: Bob
 
Employee Dave has lunch partners: Bob
 
}}
 
}}

Latest revision as of 00:07, 12 July 2024

 
 
Concurrency support library
Threads
(C++11)
(C++20)
this_thread namespace
(C++11)
(C++11)
(C++11)
Cooperative cancellation
Mutual exclusion
(C++11)
Generic lock management
(C++11)
(C++11)
scoped_lock
(C++17)
(C++11)
(C++11)
(C++11)
Condition variables
(C++11)
Semaphores
Latches and Barriers
(C++20)
(C++20)
Futures
(C++11)
(C++11)
(C++11)
(C++11)
Safe Reclamation
(C++26)
Hazard Pointers
Atomic types
(C++11)
(C++20)
Initialization of atomic types
(C++11)(deprecated in C++20)
(C++11)(deprecated in C++20)
Memory ordering
Free functions for atomic operations
Free functions for atomic flags
 
 
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 mutex_type is the same as Mutex, the sole type in MutexTypes.... Otherwise, there is no member mutex_type.

[edit] Member functions

constructs a scoped_lock, optionally locking the given mutexes
(public member function) [edit]
destructs the scoped_lock object, unlocks the underlying mutexes
(public member function) [edit]
operator=
[deleted]
not copy-assignable
(public member function) [edit]

[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

implements movable mutex ownership wrapper
(class template) [edit]
implements a strictly scope-based mutex ownership wrapper
(class template) [edit]