关于C++中线程和锁的介绍
一、线程类型(C++标准库)
C++标准库提供基础线程类型,用于创建和管理并发执行单元:
1. `std::thread(C++11)
-
核心功能:创建独立线程,执行可调用对象(函数、lambda、函数对象等)
-
关键特性:
- 不可复制,仅可移动(线程所有权唯一)
- 必须显式调用
join()(等待线程结束)或detach()(分离线程),否则析构时触发std::terminate() - 无内置取消机制,需手动实现线程退出逻辑
-
示例:
1
2
3
4
5
6
7
8
9
10
11
void task(int param) { /* 线程任务 */ }
int main() {
std::thread t1(task, 42); // 传递参数
std::thread t2([]{ /* lambda任务 */ });
t1.join(); // 等待t1完成
t2.detach(); // t2后台运行,脱离主线程控制
return 0;
}
2. std::jthread(C++20)
-
核心功能:
std::thread的增强版,自带RAII特性 -
关键特性:
- 析构时自动调用
join(),无需手动管理,避免资源泄漏 - 支持通过
std::stop_token和std::stop_source实现线程取消 - 兼容
std::thread的所有操作,可无缝替换
- 析构时自动调用
-
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
void cancelable_task(std::stop_token st) {
while (!st.stop_requested()) { // 检查取消请求
/* 循环任务 */
}
}
int main() {
std::jthread t(cancelable_task); // 自动join
// ... 其他操作 ...
t.request_stop(); // 请求线程停止
return 0; // t析构时自动join
}
说明:C++标准中无
shared_thread和timed_thread,线程的共享性通过同步机制实现,超时控制通过条件变量或带超时的锁操作实现。
二、互斥锁与RAII封装
互斥锁用于保护共享资源,RAII(资源获取即初始化)封装确保锁的安全管理:
1. 基础互斥锁类型
| 类型 | 特性 | 适用场景 |
|---|---|---|
std::mutex |
独占所有权,非递归 | 基本互斥场景 |
std::recursive_mutex |
允许同一线程多次锁定,需对应次数解锁 | 递归调用场景(谨慎使用,易隐藏设计问题) |
std::shared_mutex(C++17) |
支持共享锁(读)和独占锁(写) | 读多写少场景(如缓存、配置读取) |
std::timed_mutex |
支持超时锁定 | 需要超时控制的独占场景 |
std::recursive_timed_mutex |
递归+超时锁定 | 递归调用且需超时控制 |
2. RAII锁封装类(核心工具)
通过RAII机制自动管理锁的生命周期,避免手动lock()/unlock()导致的死锁。
(1)std::lock_guard(C++11)
-
特性:构造时锁定,析构时解锁,行为不可变
-
优势:轻量、高效,无额外开销
-
限制:不可手动解锁、不可转移所有权、不支持延迟锁定
-
示例:
1
2
3
4
5
6
7
8
std::mutex mtx;
int shared_value = 0;
void safe_increment() {
std::lock_guard<std::mutex> lock(mtx); // 自动锁定
shared_value++; // 临界区操作
} // 自动解锁,即使发生异常
(2)std::unique_lock(C++11)
-
特性:灵活控制锁状态,支持手动锁定/解锁、延迟锁定、超时锁定
-
优势:功能全面,可配合条件变量使用
-
开销:略高于
lock_guard(因需维护锁状态) -
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13void flexible_operation() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 延迟锁定
// 预处理(无需锁)
prepare_operation();
lock.lock(); // 手动锁定
perform_operation(); // 临界区
lock.unlock(); // 提前解锁(可选)
// 后续操作(无需锁)
cleanup_operation();
}
(3)std::shared_lock(C++14)
-
特性:配合
std::shared_mutex实现共享锁(读锁) -
优势:多个线程可同时持有共享锁,提高读操作并发度
-
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::shared_mutex smtx;
std::string config_data;
// 读操作(共享访问)
std::string read_config() {
std::shared_lock<std::shared_mutex> lock(smtx); // 共享锁
return config_data; // 多个读线程可同时执行
}
// 写操作(独占访问)
void update_config(const std::string& new_data) {
std::unique_lock<std::shared_mutex> lock(smtx); // 独占锁
config_data = new_data; // 写操作时阻塞所有读写
}
(4)std::scoped_lock(C++17)
-
特性:同时锁定多个互斥量,内部使用死锁避免算法
-
优势:解决多锁场景下的死锁问题(如按固定顺序锁定)
-
示例:
1
2
3
4
5
6
7
8std::mutex mtx1, mtx2;
int a = 0, b = 0;
void swap_values() {
// 同时锁定多个互斥量,避免死锁
std::scoped_lock lock(mtx1, mtx2); // 顺序不影响,内部自动处理
std::swap(a, b);
} // 自动解锁所有互斥量
三、锁操作标签(Tag Types)
通过标签控制锁的行为,实现特殊场景下的同步逻辑:
| 标签 | 作用 | 适用类 | 典型场景 |
|---|---|---|---|
std::adopt_lock |
接管已获取的锁,不重复锁定 | lock_guard/unique_lock |
手动锁定后交给RAII管理 |
std::try_to_lock |
尝试锁定,不阻塞,成功则持有 | unique_lock |
非阻塞获取锁,避免线程挂起 |
std::defer_lock |
延迟锁定,构造时不获取锁 | unique_lock |
灵活控制锁定时机(如条件判断后) |
1. std::adopt_lock 示例
1 | void manual_lock_management() { |
2. std::try_to_lock 示例
1 | void non_blocking_operation() { |
四、高级操作:超时与所有权转移
1. 超时锁定(unique_lock专属)
避免线程因无法获取锁而永久阻塞:
1 |
|
2. 锁的所有权转移
unique_lock支持移动语义,可在函数间传递锁的所有权:
1 | // 函数返回已锁定的锁 |
五、最佳实践与避坑指南
-
优先使用RAII封装:始终用
lock_guard/unique_lock等管理锁,避免手动lock()/unlock()(易因异常导致死锁)。 -
最小化临界区:锁的范围应尽可能小,仅包裹必要的共享资源操作,减少线程阻塞时间。
-
死锁预防:
- 多锁场景用
scoped_lock或按固定顺序加锁(如按地址排序)。 - 避免在持有锁时调用外部函数(可能隐式获取其他锁)。
- 结合
try_to_lock和超时机制,设置锁获取的最大等待时间。
- 多锁场景用
-
锁类型选择策略:
- 简单场景→
lock_guard(性能最优)。 - 需灵活控制(如条件变量、超时)→
unique_lock。 - 读多写少→
shared_mutex+shared_lock/unique_lock。 - 多锁同时锁定→
scoped_lock。
- 简单场景→
-
线程管理:优先使用
jthread(C++20+),避免detach()(难以控制生命周期)。
这些组件共同构成了C++多线程同步的完整体系,理解其特性与适用场景是编写安全、高效并发程序的基础。实际开发中,还需结合条件变量(std::condition_variable)、原子操作(std::atomic)等机制解决复杂同步问题。