C++11语法之并发支持库(线程管理 | 同步与互斥部分中的std::mutex)
一、线程管理
1.1 std::thread
封装系统原生线程,可创建、管理线程生命周期,不可拷贝、可移动。
例如:
#include <thread>
#include <iostream>
void func(int x) {
std::cout << "线程运行: " << x << "\n";
}
int main() {
std::thread t(func, 10); // 创建线程并启动
if (t.joinable()) t.join(); // 等待线程结束
// t.detach(); // 分离线程,后台运行(谨慎使用)
return 0;
}
常用成员:
join():阻塞等待线程完成detach():分离线程,生命周期脱离主线程get_id():获取线程 IDjoinable():是否可 join(未 join/detach)
1.2 std::this_thread命名空间
注意:this_thread只是一个命名空间
提供当前线程操作:
get_id():获取当前线程 IDsleep_for(duration):休眠指定时间(duration是一个类,具体可见官方文档或者问ai)yield():让出 CPU,触发调度
我们可以通过this_thread::get_id()来获取当前线程的id
二、同步与互斥
2.1互斥锁(Mutex)
- std::mutex:基础互斥锁,独占访问
- std::recursive_mutex:递归锁,同一线程可多次加锁
- std::timed_mutex:带超时的锁
- std::lock_guard<M>:RAII 自动锁,构造加锁、析构解锁(异常安全)
- std::unique_lock<M>:灵活锁,支持手动 lock/unlock、超时、转移所有权。
2.1.1 std::mutex
mutex是封装的互斥锁的类,用于保护临界区的共享数据。mutex主要提供lock和unlock两个接口函数来获取锁。 mutex 提供排他性非递归所有权语义:(1)调用方线程从它成功调用lock或try_lock开始,到它调用unlock为止占有mutex。(2)线程占有mutex时,其他线程如果试图要求mutex 的所有权,那么就会阻塞(对于lock的调用),对于try_lock就会返回false,但并不阻塞,调用方线程会继续执行下去。
std::mutex不支持递归加锁(即:线程不得对自身已持有的互斥锁再次加锁),如需递归加锁,可使用recursive_mutex替代类。
我们可以通过C++文档来看该类的介绍,例如:

说明了std::mutex是一个不支持拷贝的类,毕竟互斥锁的状态(是否被持有、哪个线程持有)是全局共享、唯一的,一旦拷贝,就会出现 “两个锁实例都指向同一个内核对象” 的混乱状态,导致死锁、数据竞争等问题。
例如:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
void Print(int n, int& rx, std::mutex& rmtx)
{
rmtx.lock();
for (int i = 0; i < n; i++)
{
++rx;
}
rmtx.unlock();
}
int main()
{
// std::ios::sync_with_stdio(false);
// std::cin.tie(nullptr);
int x = 0;
std::mutex mtx;
std::thread t1(Print, 1000000, std::ref(x), std::ref(mtx));
std::thread t2(Print, 2000000, std::ref(x), std::ref(mtx));
t1.join();
t2.join();
std::cout << x << std::endl;
return 0;
}
注意代码中我们没有直接用x和mtx当作形参传入,而是用std::ref()
这是为什么呢?
因为std::thread会默认把所有参数 拷贝 / 移动 一份, 不会直接用引用!如果直接传入x和mtx,它会 传值,而不是传引用,从而程序会 直接报错、编译失败。
也就是说我们写
std::thread t1(Print, 1000000, x, mtx);
结果会变成
Print(1000000, 拷贝(x), 拷贝(mtx));
而我们ref的作用是把变量包装成一个引用器对象
即:
std::ref(x) → 变成 int&
std::ref(mtx) → 变成 mutex&
所以std::thread拷贝的参数是引用类型,所以编译成功
在thread源码中,我们可通过这个来分析
template <class _Fn, class... _Args> void _Start(_Fn&& _Fx, _Args&&... _Ax) { using _Tuple = tuple<decay_t<_Fn>, decay_t<_Args>...>; auto _Decay_copied = _STD make_unique<_Tuple>(_STD forward<_Fn>(_Fx), _STD forward<_Args>(_Ax)...); constexpr auto _Invoker_proc = _Get_invoke<_Tuple>(make_index_sequence<1 + sizeof...(_Args)>{}); _Thr._Hnd = reinterpret_cast<void*>(_CSTD _beginthreadex(nullptr, 0, _Invoker_proc, _Decay_copied.get(), 0, &_Thr._Id)); if (_Thr._Hnd) { // ownership transferred to the thread (void) _Decay_copied.release(); } else { // failed to start thread _Thr._Id = 0; _Throw_Cpp_error(_RESOURCE_UNAVAILABLE_TRY_AGAIN); } }首先是tuple<decay_t<_Fn>, decay_t<_Args>...>
decay表示把复杂类型变成最简单的值类型,所以会导致传入的int &变成int以及mutex &变成mutex,然后tuple把参数全部打包。然后下面那个make_unique<>相当于把打包好的复制一份给线程
注意:我们的std::ref()是一个包装器返回的是对象,decay只会擦去原生的&,也就是内置类型,但是不会处理,也不认识你的类/对象
上述例子如果不想加std::ref的话我们可以利用lambda表达式
例如:
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
int main()
{
int x = 0;
std::mutex mtx;
// 改成使用lambda捕获外层的对象,也就可以不用传参数
auto Print = [&x, &mtx](size_t n)
{
mtx.lock();
for (size_t i = 0; i < n; i++)
{
++x;
}
mtx.unlock();
};
std::thread t1(Print, 1000000);
std::thread t2(Print, 2000000);
t1.join();
t2.join();
std::cout << x << std::endl;
return 0;
}
我们利用lambda表达式的特性,让其去捕捉参数就行
2.1.2 std::recursive_mutex:递归锁
这是适合递归函数用的锁
2.1.3 std::timed_mutex:带超时的锁
time_mutex跟mutex完全类似,只是额外提供try_lock_for和try_lock_untile的接口,这两个接口跟try_lock类似,只是他不会马上返回,而是直接进入阻塞,直到时间条件到了或者解锁了就会唤醒试图获取锁资源。
例如
// 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;
}
代码意思是创建10个线程抢锁,然后
while (!mtx.try_lock_for(std::chrono::milliseconds(200))) {
std::cout << "-";
}
该代码意思是我尝试抢锁,只等 200 毫秒。
- 抢到锁 → 返回
true- 200ms 都没抢到 → 返回
false
如果没抢到那就返回false一直打印那个"-",抢到了,则等待1s,打印"*",并释放锁,退出函数,让其他线程竞争
2.1.4 std::lock_guard<M>
lock_guard是C++11提供的支持RAII方式管理互斥锁资源的类,这样可以更有效的防止因为异常等 原因导致的死锁问题。
lock_guard的功能简单纯粹,仅仅支持RAII的方式管理锁对象。也可以在构造的时候通过传参 adopt_lock_t的adopt_lock对象管理已经lock的锁对象。其次lock_guard类不支持拷贝构造。
也就是说lock_guard支持RAII会在作用域结束后自己析构释放资源,不用我们自己手写unlock()函数
我们lock_guard有两个构造函数

第一个构造函数直接传入mutex锁就行
第二个我们还有一个参数adopt_lock_t tag表示:空标签类型,相当于告诉 lock_guard:“这个锁已经被我锁住了,你别再锁了,只帮我自动解锁就行 “,也就是说我们用第二个参数不用再用锁这个资源,锁资源已经在之前的代码中被lock了,我们只需要等lock_guard对象生命周期结束后,释放传入的锁就行了
例如:
void print_thread_id_1(int id) {
mtx.lock();
std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
std::cout << "thread #" << id << '\n';
}
void print_thread_id_2(int id) {
std::lock_guard<std::mutex> lck(mtx);
std::cout << "thread #" << id << '\n';
}
2.1.5 unique_lock
unique_lock也是C++11提供的支持RAII方式管理互斥锁资源的类,相比lock_guard他的功能支持更丰富复杂。
unique_lock首先在构造的时候传不同的tag,用以支持在构造的时候不同的方式处理锁对象。

unique_lock在构造的时候传时间段和时间点,用来管理time_mutex系统,构造时调用try_lock_for和try_lock_until

unique_lock不支持拷贝和赋值,支持移动构造和移动赋值。
2.1.6 lock和try_lock
lock是<mutex>文件提供的⼀个函数模板,可以支持对多个锁对象同时锁定,如果其中⼀个锁对象没有锁住,lock函数 会把已经锁定的对象解锁而进入阻塞,直到锁定所有的所有的对象。
try_lock也是<mutex>文件提供的⼀个函数模板,尝试对多个锁对象进行同时尝试锁定,如果全部锁对象都锁定了,返 回-1,如果某⼀个锁对象尝试锁定失败,把已经锁定成功的锁对象解锁,并则返回这个对象的下标 (第⼀个参数对象,下标从1开始算)。
template <class Mutex1, class Mutex2, class... Mutexes>
void lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
template <class Mutex1, class Mutex2, class... Mutexes>
int try_lock (Mutex1& a, Mutex2& b, Mutexes&... cde);
例如:我们想在同一个作用域用两次锁资源的时候,又怕死锁,我们就可以用lock()和try_lock()
lock()例子:
#include <iostream>
#include <thread>
#include <mutex>
// std::cout
// std::thread
// 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()
{
foo.lock();
std::thread th1(task_a);
std::thread th2(task_b);
std::cout << "xxxxxx" << std::endl;
bar.lock();
foo.unlock();
std::cout << "yyyyyy" << std::endl;
bar.unlock();
th1.join();
th2.join();
return 0;
}
try_lock()例子:
#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;
}
2.1.7 std::call_once
std::call_once是 C++11 并发支持库提供的线程安全一次性执行工具(本质是一个函数),专门解决多线程环境下,某段代码 / 函数必须执行、且只能执行一次的核心需求。
// 必须包含的头文件
#include <mutex>
// 函数原型
template<class Callable, class... Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
注意:once_flag:一个不可拷贝、不可移动的状态标志,用于标记目标函数是否执行完成
fun:需要仅执行一次的可调用对象(普通函数、lambda、成员函数均可)
args:传递给func的参数。
核心特性(必知)
- 仅执行一次:无论多少线程调用,目标函数只会成功执行一次;
- 绝对线程安全:无竞态条件,底层用原子操作实现,比手动加锁更可靠;
- 异常安全:如果函数执行时抛出异常,不算执行成功,
once_flag不会锁定,其他线程会继续尝试执行,直到有一个线程成功完成; - 阻塞等待:一个线程执行函数时,其他调用
call_once的线程会阻塞等待,直到函数执行完毕; - 轻量高效:性能远优于
mutex + bool手动实现方案。
例子:
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
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;
}
更多推荐

所有评论(0)