共计 11377 个字符,预计需要花费 29 分钟才能阅读完成。
头文件介绍
Mutex 系列类(四种)
std::mutex
,最根本的 Mutex 类。std::recursive_mutex
,递归 Mutex 类。std::time_mutex
,定时 Mutex 类。std::recursive_timed_mutex
,定时递归 Mutex 类。
Lock 类(两种)
std::lock_guard
,与 Mutex RAII 相干,不便线程对互斥量上锁。std::unique_lock
,与 Mutex RAII 相干,不便线程对互斥量上锁,但提供了更好的上锁和解锁管制。
其余类型
std::once_flag
标记位,与函数std::call_once
配合应用std::adopt_lock_t
std::defer_lock_t
std::try_to_lock_t
函数
std::try_lock
,尝试同时对多个互斥量上锁。std::lock
,能够同时对多个互斥量上锁。std::scoped_lock
RALL,能够同时对多个互斥量上锁并保障解锁。std::call_once
,如果多个线程须要同时调用某个函数,call_once
能够保障多个线程对该函数只调用一次。
mutex
mutex
的全名为 mutual exclusion(互斥体),其 object 用来帮助采取独占且排他的形式管制“对资源(object 或多个 Object 的组合)的并发拜访”。
- 构造函数,
std::mutex
不容许拷贝结构,也不容许move
拷贝,最后产生的mutex
对象是处于unlocked
状态的。 -
lock()
,调用线程将锁住该互斥量。线程调用该函数会产生上面 3 种状况:- (1). 如果该互斥量以后没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock 之前,该线程始终领有该锁。
- (2). 如果以后互斥量被其余线程锁住,则以后的调用线程被阻塞住。
- (3). 如果以后互斥量被以后调用线程锁住,则会产生死锁(deadlock)。
- unlock(),解锁,开释对互斥量的所有权。
-
try\_lock(),尝试锁住互斥量,如果互斥量被其余线程占有,则以后线程也不会被阻塞。线程调用该函数也会呈现上面 3 种状况,
- (1). 如果以后互斥量没有被其余线程占有,则该线程锁住互斥量,直到该线程调用 unlock 开释互斥量。
- (2). 如果以后互斥量被其余线程锁住,则以后调用线程返回 false,而并不会被阻塞掉。
- (3). 如果以后互斥量被以后调用线程锁住,则会产生死锁(deadlock)。
上面给出一个与 std::mutex
的小例子:
#include <iostream> // std::cout
#include <mutex> // std::mutex
#include <thread> // std::thread
volatile int counter(0); // non-atomic counter
std::mutex mtx; // locks access to counter
void attempt_10k_increases() {for (int i = 0; i < 10000; ++i) {if (mtx.try_lock()) { // only increase if currently not locked:
++counter;
mtx.unlock();}
}
}
int main(int argc, const char* argv[]) {std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(attempt_10k_increases);
for (auto& th : threads) th.join();
std::cout << counter << "successful increases of the counter.\n";
return 0;
}
std::recursive\_mutex 介绍
std::recursive_mutex
与 std::mutex
一样,也是一种能够被上锁的对象,然而和 std::mutex
不同的是,std::recursive_mutex
容许同一个线程对互斥量屡次上锁(即递归上锁),来取得对互斥量对象的多层所有权,std::recursive_mutex
开释互斥量时须要调用与该锁档次深度雷同次数的 unlock()
,可了解为 lock()
次数和 unlock()
次数雷同,除此之外,std::recursive_mutex
的个性和 std::mutex
大致相同。
recursive_mutex g_mutex;
void print123() {g_mutex.lock();
g_mutex.lock();
for (int i = 0; i < 3; i++) {this_thread::sleep_for(chrono::milliseconds(100));
cout << i + 1;
}
g_mutex.unlock();
g_mutex.unlock();}
std::time\_mutex 介绍
std::time_mutex
比 std::mutex
多了两个成员函数,try_lock_for()
,try_lock_until()
。
try_lock_for
函数承受一个工夫范畴,示意在这一段时间范畴之内线程如果没有取得锁则被阻塞住(与 std::mutex
的 try_lock()
不同,try_lock
如果被调用时没有取得锁则间接返回 false),如果在此期间其余线程开释了锁,则该线程能够取得对互斥量的锁,如果超时(即在指定工夫内还是没有取得锁),则返回 false。
try_lock_until
函数则承受一个工夫点作为参数,在指定工夫点未到来之前线程如果没有取得锁则被阻塞住,如果在此期间其余线程开释了锁,则该线程能够取得对互斥量的锁,如果超时(即在指定工夫内还是没有取得锁),则返回 false。
上面的小例子阐明了 std::time_mutex
的用法:
#include <chrono> // std::chrono::milliseconds
#include <iostream> // std::cout
#include <mutex> // std::timed_mutex
#include <thread> // std::thread
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;
}
std::recursive\_timed\_mutex 介绍
和 std:recursive_mutex
与 std::mutex
的关系一样,std::recursive_timed_mutex
的个性也能够从 std::timed_mutex
推导进去,感兴趣的同鞋能够自行查阅。
std::lock\_guard 介绍
与 Mutex RAII 相干,不便线程对互斥量上锁和开释。例子
#include <iostream> // std::cout
#include <mutex> // std::mutex, std::lock_guard
#include <stdexcept> // std::logic_error
#include <thread> // std::thread
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 {
// 应用 lock_guard 锁定 mtx 可确保在销毁 / 异样时解锁: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;
}
std::unique\_lock 介绍
与 Mutex RAII 相干,不便线程对互斥量上锁,但提供了更好的上锁和解锁管制。
类 unique_lock
是通用互斥包装器,容许提早锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一起应用。
类 unique_lock
可挪动,但不可复制——它满足 可挪动结构 (MoveConstructible) 和 可挪动赋值 (MoveAssignable) 但不满足 可复制结构 (CopyConstructible) 或 可复制赋值 (CopyAssignable)。
类 unique\_lock 满足 根本可锁定 (BasicLockable) 要求。若 Mutex 满足 可锁定 (Lockable) 要求,则 unique\_lock 亦满足 可锁定 (Lockable) 要求(例如:能用于 std::lock);若 Mutex 满足 可定时锁定 (TimedLockable) 要求,则 unique\_lock 亦满足 可定时锁定 (TimedLockable) 要求。
性能与 std::lock_guard
相似
#include <iostream> // std::cout
#include <mutex> // std::mutex, std::unique_lock
#include <thread> // std::thread
std::mutex mtx; // mutex for critical section
void print_block(int n, char c) {
// 独占 std::cout:
std::unique_lock<std::mutex> lck(mtx);
for (int i = 0; i < n; ++i) {std::cout << c;}
std::cout << '\n';
}
int main() {std::thread th1(print_block, 50, '*');
std::thread th2(print_block, 50, '$');
th1.join();
th2.join();
return 0;
}
std::lock 介绍
template< class Lockable1, class Lockable2, class... LockableN >
void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockn);
锁定给定的 可锁定 (Lockable) 对象 lock1
、lock2
、...
、lockn
, 用免死锁算法防止死锁。
以对 lock
、try_lock
和 unlock
的未指定系列调用锁定对象。若调用 lock
或 unlock
导致异样,则在重抛前对任何已锁的对象调用 unlock
。
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
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 &) {
// 模仿耗时的发信操作
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;
}
// 用 std::lock 取得二个锁,而不放心对 assign_lunch_partner
// 的其余调用会死锁咱们
{std::lock(e1.m, e2.m);
std::lock_guard<std::mutex> lk1(e1.m, std::adopt_lock);// adopt_lock 示意曾经获取锁了,看上一行代码,这样的程序是保障不死锁
std::lock_guard<std::mutex> lk2(e2.m, std::adopt_lock);
// 等价代码(若须要 unique_locks,例如对于条件变量)// std::unique_lock<std::mutex> lk1(e1.m, std::defer_lock); // defer_lock 示意提早上锁
// std::unique_lock<std::mutex> lk2(e2.m, std::defer_lock);
// std::lock(lk1, lk2);
// C++17 中可用的较优解法
// std::scoped_lock lk(e1.m, e2.m);
{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");
// 在平行线程指派,因为发邮件给用户告知午餐指派,会耗费长时间
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';}
std::try\_lock
emplate< class Lockable1, class Lockable2, class... LockableN>
int try_lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockn);
尝试锁定每个给定的 可锁定 (Lockable) 对象 lock1
、lock2
、...
、lockn
,通过以从头开始的顺序调用 try_lock
。
若调用 try_lock
失败,则不再进一步调用 try_lock
,并对任何已锁对象调用 unlock
,返回锁定失败对象的 0
底下标。
若调用 try_lock
抛出异样,则在重抛前对任何已锁对象调用 unlock
。
锁定胜利的话返回 -1,锁定失败的话返回加锁对象的索引值。
#include <chrono>
#include <functional>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
void increment(int &counter, std::mutex &m, const char *desc) {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(m);
++counter;
std::cout << desc << ":" << counter << '\n';
lock.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
int main() {
int foo_count = 0;
std::mutex foo_count_mutex;
int bar_count = 0;
std::mutex bar_count_mutex;
int overall_count = 0;
bool done = false;
std::mutex done_mutex;
std::thread increment_foo(increment, std::ref(foo_count),
std::ref(foo_count_mutex), "foo");
std::thread increment_bar(increment, std::ref(bar_count),
std::ref(bar_count_mutex), "bar");
std::thread update_overall([&]() {done_mutex.lock();
while (!done) {done_mutex.unlock();
int result = std::try_lock(foo_count_mutex, bar_count_mutex);
if (result == -1) { // 对两个 mutex 都上了锁
overall_count += foo_count + bar_count; // 读取两个值
foo_count = 0;
bar_count = 0;
std::cout << "overall:" << overall_count << '\n';
foo_count_mutex.unlock(); // 开释
bar_count_mutex.unlock(); // 开释}
std::this_thread::sleep_for(std::chrono::seconds(2));
done_mutex.lock();}
done_mutex.unlock();});
increment_foo.join();
increment_bar.join();
done_mutex.lock();
done = true;
done_mutex.unlock();
update_overall.join();
std::cout << "Done processing\n"
<< "foo:" << foo_count << '\n'
<< "bar:" << bar_count << '\n'
<< "overall:" << overall_count << '\n';
}
std::scoped\_lock
类 scoped_lock
是提供便当 RAII 格调机制的互斥包装器,它在作用域块的存在期间占有一或多个互斥。
创立 scoped_lock
对象时,它试图获得给定互斥的所有权。管制来到创立 scoped_lock
对象的作用域时,析构 scoped_lock
并以逆序开释互斥。若给出数个互斥,则应用免死锁算法,如同以 std::lock。
scoped_lock
类不可复制。
上一节中的代码节选,其中三种加锁成果雷同:
{
// 应用 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);
// 等价代码(若须要 unique_locks,例如对于条件变量)// 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);
// C++17 中可用的较优解法:std::scoped_lock lk(e1.m, e2.m);
{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);
}
std::defer\_lock, std::try\_to\_lock, std::adopt\_lock
配合 std::lock_guard
、std::unique_lock
及 std::shared_lock
指定锁定策略。
类型 | 成果 |
---|---|
defer_lock_t | 不取得互斥的所有权。示意该互斥元在结构时放弃未被锁定,这个锁就能够在这之后通过 lock 来上锁,即提早上锁。 |
try_to_lock_t | 尝试取得互斥的所有权而不阻塞,相当于调用 try\_lock。 |
adopt_lock_t | 假如调用方线程已领有互斥的所有权,此时不再上锁,只会转移所有权。 |
std::adopt_lock
示意已拿到锁:
std::mutex test5_mutex;
void test5() {test5_mutex.lock(); // 获取锁
// 交给 lock_guard 来保存,保障锁的开释,ps:有点向 go 中的 defer
lock_guard<std::mutex> lg(test5_mutex, std::adopt_lock);
cout << "hello test5" << endl;
}
void test5(int) {test5_mutex.lock(); // 获取所
cout << "hello test5(int)" << endl;
test5_mutex.unlock(); // 开释锁}
int main() {thread t1([]() { test5(); });
thread t2([]() {test5(2); });
t1.join();
t2.join();
return 0;
}
std::call\_once
template< class Callable, class... Args >
void call_once(std::once_flag& flag, Callable&& f, Args&&... args);
精确执行一次 可调用 (Callable) 对象 f
,即便同时从多个线程调用。
细节为:
- 若在调用
call_once
的时刻,flag
批示曾经调用了f
,则call_once
立刻返回(称这种对call_once
的调用为 \_消极 \_)。 - 否则,
call_once
以参数std::forward<Args>(args)...
调用 std::forward<Callable>(f)
(如同用 std::invoke)。不同于 std::thread 构造函数或 std::async,不挪动或复制参数,因为不须要转移它们到另一执行线程(称这种对call_once
的调用为 \_踊跃 \_)。 - 若该调用抛异样,则流传异样给
call_once
的调用方,并且不翻转flag
,以令其余调用将失去尝试(这种对call_once
的调用被称为 \_异样 \_)。 - 若该调用失常返回(这种对
call_once
的调用被称为 \_返回 \_),则翻转flag
,并保障以同一flag
对call_once
的其余调用为 \_消极 \_。
同一 flag
上的所有 \_踊跃 \_调用组成独自全序,它们由零或多个 \_异样 \_调用后随一个 \_返回 \_调用组成。该程序中,每个 \_踊跃 \_调用的结尾同步于下个 \_踊跃 \_调用。
从 \_返回 \_调用的返回同步于同一 flag
上的所有 \_消极 \_调用:这示意保障所有对 call_once
的同时调用都察看到 \_踊跃 \_调用所做的任何副效应,而无需额定同步。
#include <iostream>
#include <mutex>
#include <thread>
std::once_flag flag1, flag2;
void simple_do_once() {
std::call_once(flag1,
[]() {std::cout << "Simple example: called once\n";});
}
void may_throw_function(bool do_throw) {if (do_throw) {
// 这会呈现多于一次, 因为产生异样后会重置 flag
std::cout << "throw: call_once will retry\n";
throw std::exception();}
std::cout
<< "Didn't throw, call_once will not attempt again\n"; // 保障一次
}
void do_once(bool do_throw) {
try {std::call_once(flag2, may_throw_function, do_throw);
} catch (...) {}}
int main() {std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();}
很多代码示例来自 C++ 参考手册