参考cplusplus
参考cppreference
1.mutex
- 用于保护临界区(critical section)代码的访问。
1.1 mutex
- 特定mutex上所有的lock和unlock顺序要一致
- 非成员lock函数允许一次lock多个mutex,可以避免多线程mutex的lock/unlock顺序不同造成的死锁。
// mutex::lock/unlock
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex
std::mutex mtx; // mutex for critical section
void print_thread_id (int id) {
// critical section (exclusive access to std::cout signaled by locking mtx):
mtx.lock();
std::cout << "thread #" << id << '\n';
mtx.unlock();
}
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;
}
- try_lock
尝试加锁,但不阻塞。如果是同一个线程重复加锁,也会死锁。
1.2 timed_mutex
- 跟mutex一致,只不过增加了try_lock_for和try_lock_util
- try_lock_for
尝试加锁,最多阻塞rel_time这么长的时间。
template <class Rep, class Period>
bool try_lock_for (const chrono::duration<Rep,Period>& rel_time);
// timed_mutex::try_lock_for example
#include <iostream> // std::cout
#include <chrono> // std::chrono::milliseconds
#include <thread> // std::thread
#include <mutex> // std::timed_mutex
std::timed_mutex mtx;
void fireworks () {
// waiting to get a lock: each thread prints "-" every 200ms:
while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
std::cout << "-";
}
// got a lock! - wait for 1s, then this thread prints "*"
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
std::cout << "*\n";
mtx.unlock();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(fireworks);
for (auto& th : threads) th.join();
return 0;
}
结果如下:
------------------------------------*
----------------------------------------*
-----------------------------------*
------------------------------*
-------------------------*
--------------------*
---------------*
----------*
-----*
*
- try_lock_until
尝试加锁,阻塞最多到abs_time。
template <class Clock, class Duration>
bool try_lock_until (const chrono::time_point<Clock,Duration>& abs_time);
1.3 recursive_mutex
1.4 recursive_timed_mutex
- 支持try_lock_for和try_lock_until
- 同一个线程可以重复加锁
1.5 shared_mutex (头文件<shared_mutex>)
- 本质是读写锁
可以同时被多个读者拥有,但是只能被一个写者拥有的锁。
- 独占式
lock try_lock unlock
- 共享式
lock_shared try_lock_shared unlock_shared
#include <iostream>
#include <mutex> // For std::unique_lock
#include <shared_mutex>
#include <thread>
class ThreadSafeCounter {
public:
ThreadSafeCounter() = default;
// Multiple threads/readers can read the counter's value at the same time.
unsigned int get() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return value_;
}
// Only one thread/writer can increment/write the counter's value.
void increment() {
std::unique_lock<std::shared_mutex> lock(mutex_);
value_++;
}
// Only one thread/writer can reset/write the counter's value.
void reset() {
std::unique_lock<std::shared_mutex> lock(mutex_);
value_ = 0;
}
private:
mutable std::shared_mutex mutex_;
unsigned int value_ = 0;
};
int main() {
ThreadSafeCounter counter;
auto increment_and_print = [&counter]() {
for (int i = 0; i < 3; i++) {
counter.increment();
std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';
// Note: Writing to std::cout actually needs to be synchronized as well
// by another std::mutex. This has been omitted to keep the example small.
}
};
std::thread thread1(increment_and_print);
std::thread thread2(increment_and_print);
thread1.join();
thread2.join();
}
1.6 shared_timed_mutex
2.lock——Generic mutex management
2.1 lock_guard
- 构造函数
创建的对象管理m,并且调用m.lock()锁住mutex。
//locking (1)
explicit lock_guard (mutex_type& m);
//adopting (2)
lock_guard (mutex_type& m, adopt_lock_t tag);
- C++11的标准库中提供了std::lock_guard类模板做mutex的RAII。
采用”资源分配时初始化”(RAII——Resource Acquisition Is Initialization)方法来加锁、解锁,这避免了在临界区中因为抛出异常或return等操作导致没有解锁就退出的问题。
- std::lock_guard类的构造函数禁用拷贝构造,且禁用移动构造。std::lock_guard类除了构造函数和析构函数外没有其它成员函数。
- 在std::lock_guard对象构造时,传入的mutex对象(即它所管理的mutex对象)会被当前线程锁住。在lock_guard对象被析构时,它所管理的mutex对象会自动解锁,不需要程序员手动调用lock和unlock对mutex进行上锁和解锁操作。lock_guard对象并不负责管理mutex对象的生命周期,lock_guard对象只是简化了mutex对象的上锁和解锁操作,方便线程对互斥量上锁,即在某个lock_guard对象的生命周期内,它所管理的锁对象会一直保持上锁状态;而lock_guard的生命周期结束之后,它所管理的锁对象会被解锁。程序员可以非常方便地使用lock_guard,而不用担心异常安全问题。
// 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;
}
2.2 scoped_lock
- scoped_lock加锁多个mutexes时,不会出现死锁,并且是RAII.
这个跟std::lock有什么区别?
- 只有构造函数和析构函数
- 构造函数如下
explicit scoped_lock( MutexTypes&... m );
scoped_lock( std::adopt_lock_t, MutexTypes&... m );
#include <mutex>
#include <thread>
#include <iostream>
#include <vector>
#include <functional>
#include <chrono>
#include <string>
struct Employee {
Employee(std::string id) : id(id) {}
std::string id;
std::vector<std::string> lunch_partners;
std::mutex m;
std::string output() const
{
std::string ret = "Employee " + id + " has lunch partners: ";
for( const auto& partner : lunch_partners )
ret += partner + " ";
return ret;
}
};
void send_mail(Employee &, Employee &)
{
// simulate a time-consuming messaging operation
std::this_thread::sleep_for(std::chrono::seconds(1));
}
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.output() << '\n' << bob.output() << '\n'
<< christina.output() << '\n' << dave.output() << '\n';
}
2.3 unique_lock
- std::unique_lock对象以独占所有权的方式(unique owership)管理mutex对象的上锁和解锁操作,即在unique_lock对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而unique_lock的生命周期结束之后,它所管理的锁对象会被解锁。unique_lock具有lock_guard的所有功能,而且更为灵活。虽然二者的对象都不能复制,但是unique_lock可以移动(movable),因此用unique_lock管理互斥对象,可以作为函数的返回值,也可以放到STL的容器中。
- std::unique_lock还支持同时锁定多个mutex,这避免了多道加锁时的资源”死锁”问题。在使用std::condition_variable时需要使用std::unique_lock而不应该使用std::lock_guard。
- 其lock try_lock 等操作都是调用mutex对象的相应操作
- 构造函数如下:
//default (1)
unique_lock() noexcept;
//locking (2)
//调用 m.lock()
explicit unique_lock (mutex_type& m);
//try-locking (3)
//调用 m.try_lock()
unique_lock (mutex_type& m, try_to_lock_t tag);
//deferred (4)
// m 当前应该没有被锁
unique_lock (mutex_type& m, defer_lock_t tag) noexcept;
//adopting (5)
// m 当前应该是被锁的
unique_lock (mutex_type& m, adopt_lock_t tag);
//locking for (6)
// 调用 m.try_lock_for(rel_time)
template <class Rep, class Period>
unique_lock (mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
//locking until (7)
// 调用 m.try_lock_until(abs_time)
template <class Clock, class Duration>
unique_lock (mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
//copy [deleted] (8)
unique_lock (const unique_lock&) = delete;
//move (9)
unique_lock (unique_lock&& x);
// unique_lock constructor example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::lock, std::unique_lock
// std::adopt_lock, std::defer_lock
std::mutex foo,bar;
void task_a () {
std::lock (foo,bar); // simultaneous lock (prevents deadlock)
std::unique_lock<std::mutex> lck1 (foo,std::adopt_lock);
std::unique_lock<std::mutex> lck2 (bar,std::adopt_lock);
std::cout << "task a\n";
// (unlocked automatically on destruction of lck1 and lck2)
}
void task_b () {
// foo.lock(); bar.lock(); // replaced by:
std::unique_lock<std::mutex> lck1, lck2;
lck1 = std::unique_lock<std::mutex>(bar,std::defer_lock);
lck2 = std::unique_lock<std::mutex>(foo,std::defer_lock);
std::lock (lck1,lck2); // simultaneous lock (prevents deadlock)
std::cout << "task b\n";
// (unlocked automatically on destruction of lck1 and lck2)
}
int main ()
{
std::thread th1 (task_a);
std::thread th2 (task_b);
th1.join();
th2.join();
return 0;
}
2.4 shared_lock
- shared_lock就是读锁,被锁后仍允许其他线程执行同样被shared_lock的代码。这是一般做读操作时的需要。
- unique_lock就是写锁。被锁后不允许其他线程执行被shared_lock或unique_lock的代码。在写操作时,一般用这个,可以同时限制unique_lock的写和share_lock的读。
- lock try_lock try_lock_for try_lock_until调用的都是shared版本:如mutex()->lock_shared()
std::shared_lock::mutex如下:
mutex_type* mutex() const noexcept;
shared_lock() noexcept;
shared_lock( shared_lock&& other ) noexcept;
//调用 m.lock_shared()
explicit shared_lock( mutex_type& m );
//调用 m.lock_shared()
shared_lock( mutex_type& m, std::defer_lock_t t ) noexcept;
//调用 m.try_lock_shared()
shared_lock( mutex_type& m, std::try_to_lock_t t );
shared_lock( mutex_type& m, std::adopt_lock_t t );
//调用 m.try_lock_shared_until(timeout_duration)
template< class Rep, class Period >
shared_lock( mutex_type& m,
const std::chrono::duration<Rep,Period>& timeout_duration );
// 调用 m.try_lock_shared_for(timeout_time)
template< class Clock, class Duration >
shared_lock( mutex_type& m,
const std::chrono::time_point<Clock,Duration>& timeout_time );
#include <shared_mutex>
#include <iostream>
#include <thread>
#include <chrono>
std::shared_timed_mutex m;
int i = 10;
void read()
{
// both the threads get access to the integer i
std::shared_lock<std::shared_timed_mutex> slk(m);
std::cout << "read i as " << i << "...\n"; // this is not synchronized
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::cout << "woke up...\n";
}
int main()
{
std::thread r1(read);
std::thread r2(read);
r1.join();
r2.join();
return 0;
}
2.5 defer_lock_t / try_to_lock_t /adopt_lock_t
- std::defer_lock_t 、 std::try_to_lock_t和 std::adopt_lock_t 是用于为 std::lock_guard、 std::scoped_lock、 std::unique_lock 和 std::shared_lock指定锁定策略的空结构体标签类型。
// 不用获取mutex的所有权
struct defer_lock_t { explicit defer_lock_t() = default; };
// 尝试获取mutex的所有权,不阻塞
struct try_to_lock_t { explicit try_to_lock_t() = default; };
// 假设调用线程已经获得mutex的所有权
struct adopt_lock_t { explicit adopt_lock_t() = default; };
2.6 defer_lock / try_to_lock / adopt_lock
- std::defer_lock 、 std::try_to_lock和 std::adopt_lock分别是空结构体标签类型 std::defer_lock_t 、 std::try_to_lock_t和 std::adopt_lock_t的实例。
它们用于为 std::lock_guard 、 std::unique_lock及 std::shared_lock指定锁定策略。
inline constexpr std::defer_lock_t defer_lock {};
inline constexpr std::try_to_lock_t try_to_lock {};
inline constexpr std::adopt_lock_t adopt_lock {};
#include <mutex>
#include <thread>
struct bank_account {
explicit bank_account(int balance) : balance(balance) {}
int balance;
std::mutex m;
};
void transfer(bank_account &from, bank_account &to, int amount)
{
// lock both mutexes without deadlock
std::lock(from.m, to.m);
// make sure both already-locked mutexes are unlocked at the end of scope
std::lock_guard<std::mutex> lock1(from.m, std::adopt_lock);
std::lock_guard<std::mutex> lock2(to.m, std::adopt_lock);
// equivalent approach:
// std::unique_lock<std::mutex> lock1(from.m, std::defer_lock);
// std::unique_lock<std::mutex> lock2(to.m, std::defer_lock);
// std::lock(lock1, lock2);
from.balance -= amount;
to.balance += amount;
}
int main()
{
bank_account my_account(100);
bank_account your_account(50);
std::thread t1(transfer, std::ref(my_account), std::ref(your_account), 10);
std::thread t2(transfer, std::ref(your_account), std::ref(my_account), 5);
t1.join();
t2.join();
}
3.Generic locking algorithms
3.1 try_lock
- 尝试对所有mutex进行加锁(非阻塞)
- 如果全部加锁成功,返回-1
如果有一个加锁失败,则将加锁成功的mutex解锁,并返回加锁失败的那个锁的序号(从0开始)
template <class Mutex1, class Mutex2, class... Mutexes>
int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
// std::lock example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::try_lock
std::mutex foo,bar;
void task_a () {
foo.lock();
std::cout << "task a\n";
bar.lock();
// ...
foo.unlock();
bar.unlock();
}
void task_b () {
int x = try_lock(bar,foo);
if (x==-1) {
std::cout << "task b\n";
// ...
bar.unlock();
foo.unlock();
}
else {
std::cout << "[task b failed: mutex " << (x?"foo":"bar") << " locked]\n";
}
}
int main ()
{
std::thread th1 (task_a);
std::thread th2 (task_b);
th1.join();
th2.join();
return 0;
}
3.2 lock
- 锁住所有的mutex,可能会阻塞
调用mutex对象的lock try_lock
如果不能锁住所有mutex(因为其中有一个抛出异常),则先unlock加锁成功的mutex。
template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
// std::lock example
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::lock
std::mutex foo,bar;
void task_a () {
// foo.lock(); bar.lock(); // replaced by:
std::lock (foo,bar);
std::cout << "task a\n";
foo.unlock();
bar.unlock();
}
void task_b () {
// bar.lock(); foo.lock(); // replaced by:
std::lock (bar,foo);
std::cout << "task b\n";
bar.unlock();
foo.unlock();
}
int main ()
{
std::thread th1 (task_a);
std::thread th2 (task_b);
th1.join();
th2.join();
return 0;
}
4.Call once
4.1 once_flag
- 作为call_one的参数
Using the same object on different calls to call_once in different threads causes a single execution if called concurrently.
struct once_flag {
constexpr once_flag() noexcept;
once_flag (const once_flag&) = delete;
once_flag& operator= (const once_flag&) = delete;
};
4.2 call_once
- 相同的flag只有一个函数正在执行,fn使用args进行调用。
- 所有相同的flag的调用只有一个是活动的执行(active execution),其他是passive executions。被动执行等到活动的执行返回后才返回,所有调用的可见副作用同步到这些函数。
一旦一个ative execution返回,当前和以后有相同标识的call_once都会直接返回。
- 如果当前的活动执行抛出异常后结束,从passive里面选择一个作为新的活动执行实体。
template <class Fn, class... Args>
void call_once (once_flag& flag, Fn&& fn, Args&&... args);
// call_once example
#include <iostream> // std::cout
#include <thread> // std::thread, std::this_thread::sleep_for
#include <chrono> // std::chrono::milliseconds
#include <mutex> // std::call_once, std::once_flag
int winner;
void set_winner (int x) { winner = x; }
std::once_flag winner_flag;
void wait_1000ms (int id) {
// count to 1000, waiting 1ms between increments:
for (int i=0; i<1000; ++i)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// claim to be the winner (only the first such call is executed):
std::call_once (winner_flag,set_winner,id);
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(wait_1000ms,i+1);
std::cout << "waiting for the first among 10 threads to count 1000 ms...\n";
for (auto& th : threads) th.join();
std::cout << "winner thread: " << winner << '\n';
return 0;
}