C++新特性梳理03-锁合集

鸽了好久, 终于想起来还有个博客主题没写…

1. 基础锁管理器:lock_guard

std::lock_guard 是最基础的 RAII 风格锁管理器,使用简单且安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// C++11 标准库提供了 std::lock_guard 用于自动管理互斥锁的锁定和解锁。
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_thread_id(int id) {
// std::lock_guard 是一个 RAII(Resource Acquisition Is
// Initialization)风格的锁管理器,
// 它在构造时自动锁定互斥锁,在析构时自动解锁。
std::lock_guard<std::mutex> lock(mtx); // 自动管理锁的生命周期
std::cout << "Thread ID: " << id << std::endl;
}

int main() {
std::thread t1(print_thread_id, 1);
std::thread t2(print_thread_id, 2);

t1.join();
t2.join();

return 0;
}

特点:

  • 构造时自动加锁
  • 析构时自动解锁
  • 不能手动解锁
  • 不能复制或移动

2. 灵活的锁管理器:unique_lock

std::unique_lock 提供了更灵活的锁管理方式,支持多种加锁策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// C++11 标准库提供了 std::unique_lock 用于灵活管理互斥锁的锁定和解锁。
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_thread_id(int id) {
// 1. defer_lock: 创建时不锁定互斥量
{
std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);
lock1.lock(); // 手动锁定
std::cout << "defer_lock - Thread " << id << std::endl;
lock1.unlock(); // 手动解锁
}

// 2. try_to_lock: 尝试锁定,如果无法获得锁则返回 false
{
std::unique_lock<std::mutex> lock2(mtx, std::try_to_lock);
if (lock2.owns_lock()) {
std::cout << "try_to_lock succeeded - Thread " << id << std::endl;
} else {
std::cout << "try_to_lock failed - Thread " << id << std::endl;
}
}

// 3. adopt_lock: 假设当前线程已经拥有互斥量的所有权
// 作用:将原生的锁的生命周期托管给了 unique_lock 管理
{
mtx.lock(); // 必须在之前手动锁定,否则是 Undefined Behavior
std::unique_lock<std::mutex> lock3(mtx, std::adopt_lock);
std::cout << "adopt_lock - Thread " << id << std::endl;
} // 自动解锁

// 4. 默认构造:立即锁定(最常用)
{
std::unique_lock<std::mutex> lock4(mtx); // 等同于 lock_guard
std::cout << "default lock - Thread " << id << std::endl;
}
}

int main() {
std::thread t1(print_thread_id, 1);
std::thread t2(print_thread_id, 2);

t1.join();
t2.join();

return 0;
}

特点:

  • 支持手动加锁/解锁
  • 可以检查锁状态
  • 支持锁的所有权转移
  • lock_guard 更灵活但开销略大

3. 超时锁:timed_mutex

std::timed_mutex 支持带超时的锁定操作,适用于需要避免长时间等待的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>

std::timed_mutex mtx;
// std::timed_mutex 支持带超时的锁定操作。

bool try_lock_with_timeout() {
// try_lock_for 语义:
// 1. 在指定时间内持续尝试获取锁,而不是等待 2 秒后再尝试
// 2. 如果锁立即可用,立即获取并返回 true
// 3. 如果锁不可用,会在 2 秒内持续尝试
// 4. 2 秒后仍未获取到锁,返回 false
if (mtx.try_lock_for(std::chrono::seconds(2))) {
// 成功锁定
std::cout << "Mutex locked" << std::endl;

// 注意:获取到锁后必须记得解锁
// 建议使用 RAII 方式管理锁的生命周期,比如:
// std::unique_lock<std::timed_mutex> lock(mtx, std::chrono::seconds(2));
mtx.unlock();
return true;
} else {
// 锁定失败
std::cout << "Failed to lock mutex within 2 seconds" << std::endl;
return false;
}
}

// 另一种写法:使用 try_lock_until 指定截止时间点
bool try_lock_until_deadline() {
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
if (mtx.try_lock_until(deadline)) {
std::cout << "Locked before deadline" << std::endl;
mtx.unlock();
return true;
}
return false;
}

int main() {
// 易错点:
// 1. 不要在持有锁时调用可能抛出异常的代码,除非使用 RAII 管理锁
// 2. 记得及时解锁,避免死锁
// 3. 超时时间设置要合理,太短可能导致频繁失败,太长可能影响性能
std::thread t1(try_lock_with_timeout);
std::thread t2(try_lock_with_timeout);

t1.join();
t2.join();

return 0;
}

特点:

  • 支持 try_lock_for:指定等待时长
  • 支持 try_lock_until:指定截止时间点
  • 避免无限等待

4. 多锁管理:scoped_lock

std::scoped_lock(C++17)用于同时管理多个互斥锁,避免死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// C++17 标准库提供了 std::scoped_lock 用于简化同时锁定多个互斥锁的操作。
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx1, mtx2;

void perform_operation_with_1st_mutex() {
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_1st_mutex start" << std::endl;
std::unique_lock<std::mutex> lock(mtx1);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_1st_mutex done" << std::endl;
}

void perform_operation_with_2nd_mutex() {
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_2nd_mutex start" << std::endl;
std::unique_lock<std::mutex> lock(mtx2);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_2nd_mutex done" << std::endl;
}

void perform_operation_with_2_mutex() {
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_2_mutex start" << std::endl;
std::scoped_lock lock(mtx1, mtx2); // 同时锁定多个互斥锁
// 执行操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id()
<< " perform_operation_with_2_mutex done" << std::endl;
}

int main() {
std::thread t1(perform_operation_with_2_mutex);
std::thread t2(perform_operation_with_1st_mutex);
std::thread t3(perform_operation_with_2nd_mutex);

t1.join();
t2.join();
t3.join();
return 0;
}

特点:

  • 可以同时锁定多个互斥量
  • 自动避免死锁
  • RAII 风格管理

5. 读写锁:shared_mutex

std::shared_mutex(C++17)支持读写分离的场景,允许多个读操作同时进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// C++17 标准库提供了 std::shared_mutex 用于支持共享和独占访问的互斥锁。
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <thread>

std::shared_mutex mtx;

void read_data() {
std::shared_lock<std::shared_mutex> lock(mtx); // 共享锁,shared_lock 由 C++14 提供
std::cout << "Reading data..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id() << " Reading data done"
<< std::endl;
}

void write_data() {
std::unique_lock<std::shared_mutex> lock(mtx); // 排他锁
std::cout << "Writing data..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Thread " << std::this_thread::get_id() << " Writing data done"
<< std::endl;
}

int main() {
std::thread t1(read_data);
std::thread t2(read_data);
std::thread t3(write_data);

t1.join();
t2.join();
t3.join();

return 0;
}

特点:

  • 支持共享(读)和独占(写)两种访问模式
  • 多个读操作可以并发
  • 写操作需要独占访问

6. 尝试锁定:try_lock 和 try_to_lock_t

两种不同的尝试锁定方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

// 使用 try_lock() 函数方式
bool try_lock_mutex() {
// std::try_lock 是一个函数,用于尝试锁定互斥锁
// 立即返回结果:成功返回 true,失败返回 false
if (mtx.try_lock()) {
std::cout << "Mutex locked using try_lock()" << std::endl;
mtx.unlock();
return true;
} else {
std::cout << "Failed to lock mutex using try_lock()" << std::endl;
return false;
}
}

// 使用 try_to_lock_t 标签类型方式
bool try_lock_with_unique_lock() {
// std::try_to_lock_t 是一个标签类型,用于 RAII 方式的锁管理
// 通过 std::try_to_lock 传递给 unique_lock 构造函数
std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);

if (lock.owns_lock()) {
std::cout << "Mutex locked using unique_lock with try_to_lock" << std::endl;
return true;
} else {
std::cout << "Failed to lock mutex using unique_lock with try_to_lock"
<< std::endl;
return false;
}
// unique_lock 析构时自动释放锁
}

int main() {
// 测试两种方式
std::cout << "Testing try_lock():" << std::endl;
std::thread t1(try_lock_mutex);
std::thread t2(try_lock_mutex);
t1.join();
t2.join();

std::cout << "\nTesting try_to_lock_t:" << std::endl;
std::thread t3(try_lock_with_unique_lock);
std::thread t4(try_lock_with_unique_lock);
t3.join();
t4.join();

return 0;
}

特点:

  • try_lock():函数调用方式
  • try_to_lock_t:用于 RAII 方式的锁管理

总结

C++ 提供了丰富的锁机制,适应不同的场景需求:

  1. 简单场景使用 lock_guard
  2. 需要灵活控制时使用 unique_lock
  3. 需要超时机制时使用 timed_mutex
  4. 多锁场景使用 scoped_lock
  5. 读写分离场景使用 shared_mutex

选择合适的锁机制对于编写高效的多线程程序至关重要。建议优先使用高级的 RAII 风格锁管理器,避免手动管理锁的状态。