參考cplusplus
參考cppreference
1.mutex
- 用于保護(hù)臨界區(qū)(critical section)代碼的訪問。
1.1 mutex
- 特定mutex上所有的lock和unlock順序要一致
- 非成員lock函數(shù)允許一次lock多個(gè)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
嘗試加鎖,但不阻塞。如果是同一個(gè)線程重復(fù)加鎖,也會(huì)死鎖。
1.2 timed_mutex
- 跟mutex一致,只不過增加了try_lock_for和try_lock_util
- try_lock_for
嘗試加鎖,最多阻塞rel_time這么長(zhǎng)的時(shí)間。
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;
}
結(jié)果如下:
------------------------------------*
----------------------------------------*
-----------------------------------*
------------------------------*
-------------------------*
--------------------*
---------------*
----------*
-----*
*
- 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
- 同一個(gè)線程可以重復(fù)加鎖
1.5 shared_mutex (頭文件<shared_mutex>)
- 本質(zhì)是讀寫鎖
可以同時(shí)被多個(gè)讀者擁有,但是只能被一個(gè)寫者擁有的鎖。
- 獨(dú)占式
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
- 構(gòu)造函數(shù)
創(chuàng)建的對(duì)象管理m,并且調(diào)用m.lock()鎖住mutex。
//locking (1)
explicit lock_guard (mutex_type& m);
//adopting (2)
lock_guard (mutex_type& m, adopt_lock_t tag);
- C++11的標(biāo)準(zhǔn)庫(kù)中提供了std::lock_guard類模板做mutex的RAII。
采用”資源分配時(shí)初始化”(RAII——Resource Acquisition Is Initialization)方法來加鎖、解鎖,這避免了在臨界區(qū)中因?yàn)閽伋霎惓;騬eturn等操作導(dǎo)致沒有解鎖就退出的問題。
- std::lock_guard類的構(gòu)造函數(shù)禁用拷貝構(gòu)造,且禁用移動(dòng)構(gòu)造。std::lock_guard類除了構(gòu)造函數(shù)和析構(gòu)函數(shù)外沒有其它成員函數(shù)。
- 在std::lock_guard對(duì)象構(gòu)造時(shí),傳入的mutex對(duì)象(即它所管理的mutex對(duì)象)會(huì)被當(dāng)前線程鎖住。在lock_guard對(duì)象被析構(gòu)時(shí),它所管理的mutex對(duì)象會(huì)自動(dòng)解鎖,不需要程序員手動(dòng)調(diào)用lock和unlock對(duì)mutex進(jìn)行上鎖和解鎖操作。lock_guard對(duì)象并不負(fù)責(zé)管理mutex對(duì)象的生命周期,lock_guard對(duì)象只是簡(jiǎn)化了mutex對(duì)象的上鎖和解鎖操作,方便線程對(duì)互斥量上鎖,即在某個(gè)lock_guard對(duì)象的生命周期內(nèi),它所管理的鎖對(duì)象會(huì)一直保持上鎖狀態(tài);而lock_guard的生命周期結(jié)束之后,它所管理的鎖對(duì)象會(huì)被解鎖。程序員可以非常方便地使用lock_guard,而不用擔(dān)心異常安全問題。
// 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加鎖多個(gè)mutexes時(shí),不會(huì)出現(xiàn)死鎖,并且是RAII.
這個(gè)跟std::lock有什么區(qū)別?
- 只有構(gòu)造函數(shù)和析構(gòu)函數(shù)
- 構(gòu)造函數(shù)如下
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對(duì)象以獨(dú)占所有權(quán)的方式(unique owership)管理mutex對(duì)象的上鎖和解鎖操作,即在unique_lock對(duì)象的聲明周期內(nèi),它所管理的鎖對(duì)象會(huì)一直保持上鎖狀態(tài);而unique_lock的生命周期結(jié)束之后,它所管理的鎖對(duì)象會(huì)被解鎖。unique_lock具有l(wèi)ock_guard的所有功能,而且更為靈活。雖然二者的對(duì)象都不能復(fù)制,但是unique_lock可以移動(dòng)(movable),因此用unique_lock管理互斥對(duì)象,可以作為函數(shù)的返回值,也可以放到STL的容器中。
- std::unique_lock還支持同時(shí)鎖定多個(gè)mutex,這避免了多道加鎖時(shí)的資源”死鎖”問題。在使用std::condition_variable時(shí)需要使用std::unique_lock而不應(yīng)該使用std::lock_guard。
- 其lock try_lock 等操作都是調(diào)用mutex對(duì)象的相應(yīng)操作
- 構(gòu)造函數(shù)如下:
//default (1)
unique_lock() noexcept;
//locking (2)
//調(diào)用 m.lock()
explicit unique_lock (mutex_type& m);
//try-locking (3)
//調(diào)用 m.try_lock()
unique_lock (mutex_type& m, try_to_lock_t tag);
//deferred (4)
// m 當(dāng)前應(yīng)該沒有被鎖
unique_lock (mutex_type& m, defer_lock_t tag) noexcept;
//adopting (5)
// m 當(dāng)前應(yīng)該是被鎖的
unique_lock (mutex_type& m, adopt_lock_t tag);
//locking for (6)
// 調(diào)用 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)
// 調(diào)用 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就是讀鎖,被鎖后仍允許其他線程執(zhí)行同樣被shared_lock的代碼。這是一般做讀操作時(shí)的需要。
- unique_lock就是寫鎖。被鎖后不允許其他線程執(zhí)行被shared_lock或unique_lock的代碼。在寫操作時(shí),一般用這個(gè),可以同時(shí)限制unique_lock的寫和share_lock的讀。
- lock try_lock try_lock_for try_lock_until調(diào)用的都是shared版本:如mutex()->lock_shared()
std::shared_lock::mutex如下:
mutex_type* mutex() const noexcept;
shared_lock() noexcept;
shared_lock( shared_lock&& other ) noexcept;
//調(diào)用 m.lock_shared()
explicit shared_lock( mutex_type& m );
//調(diào)用 m.lock_shared()
shared_lock( mutex_type& m, std::defer_lock_t t ) noexcept;
//調(diào)用 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 );
//調(diào)用 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 );
// 調(diào)用 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指定鎖定策略的空結(jié)構(gòu)體標(biāo)簽類型。
// 不用獲取mutex的所有權(quán)
struct defer_lock_t { explicit defer_lock_t() = default; };
// 嘗試獲取mutex的所有權(quán),不阻塞
struct try_to_lock_t { explicit try_to_lock_t() = default; };
// 假設(shè)調(diào)用線程已經(jīng)獲得mutex的所有權(quán)
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分別是空結(jié)構(gòu)體標(biāo)簽類型 std::defer_lock_t 、 std::try_to_lock_t和 std::adopt_lock_t的實(shí)例。
它們用于為 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
- 同時(shí)對(duì)多個(gè)mutex進(jìn)行加鎖
3.1 try_lock
- 嘗試對(duì)所有mutex進(jìn)行加鎖(非阻塞)
- 如果全部加鎖成功,返回-1
如果有一個(gè)加鎖失敗,則將加鎖成功的mutex解鎖,并返回加鎖失敗的那個(gè)鎖的序號(hào)(從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,可能會(huì)阻塞
調(diào)用mutex對(duì)象的lock try_lock
如果不能鎖住所有mutex(因?yàn)槠渲杏幸粋€(gè)拋出異常),則先unlock加鎖成功的mutex。
template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
- 原來多個(gè)鎖會(huì)造成死鎖,換成lock后可正常運(yùn)行
// 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
- 直接阻止指定函數(shù)的并發(fā)執(zhí)行。
4.1 once_flag
- 作為call_one的參數(shù)
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只有一個(gè)函數(shù)正在執(zhí)行,fn使用args進(jìn)行調(diào)用。
- 所有相同的flag的調(diào)用只有一個(gè)是活動(dòng)的執(zhí)行(active execution),其他是passive executions。被動(dòng)執(zhí)行等到活動(dòng)的執(zhí)行返回后才返回,所有調(diào)用的可見副作用同步到這些函數(shù)。
一旦一個(gè)ative execution返回,當(dāng)前和以后有相同標(biāo)識(shí)的call_once都會(huì)直接返回。
- 如果當(dāng)前的活動(dòng)執(zhí)行拋出異常后結(jié)束,從passive里面選擇一個(gè)作為新的活動(dòng)執(zhí)行實(shí)體。
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;
}