C++ 多线程编程之一

生产者 - 消费者模型

重点知识

unique_lock 的作用

unique_lock 是 C++ 标准库 mutex 的 RAII(资源获取即初始化)封装,用于自动管理互斥锁。当执行 unique_lock<mutex> lock(_mutex); 时:

  • 当前线程尝试获取 mutex 锁(如果其他线程已经持有锁,则当前线程会阻塞等待,直到锁被释放)。
  • 一旦成功获取锁,在 lock 对象的生命周期内,当前线程独占访问受保护的资源。
  • lock 对象销毁时(如作用域结束),mutex 会自动解锁,从而可以避免死锁或资源泄露。

unique_lock 与 lock_guard 的区别

  • unique_lock

    • 使用语法(unique_lock<mutex> lock(_mutex);
    • 可以手动解锁(lock.unlock())。
    • 可以延迟加锁(unique_lock<mutex> lock(_mutex, defer_lock);)。
    • 可以移动赋值(unique_lock 可被转移,但 lock_guard 不能)。
    • 通常与 condition_variable 搭配使用,因为 condition_variable::wait() 函数需要传入 unique_lock 参数(不能使用 lock_guard)。
  • lock_guard

    • 使用语法(lock_guard<mutex> lock(_mutex);
    • 作用域结束时自动解锁,不可以手动解锁。
    • unique_lock 更轻量级,性能更好(因为没有 unlock () 之类的额外操作)。

总结

  • 如果只需要简单加锁 / 解锁,建议使用 lock_guard,效率更高。
  • 如果需要手动控制解锁,建议使用 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <chrono>
#include <atomic>
#include <condition_variable>

using namespace std;

mutex _mutex; // 互斥锁
queue<int> _queue; // 共享数据队列
condition_variable _cv; // 条件变量
atomic_bool _finish(false); // 控制标志(用于终止程序)

void produce() {
while (!_finish) {
// 获取锁
unique_lock<mutex> lock(_mutex);

// 阻塞等待,直到满足条件才继续往下执行(可以避免虚假唤醒)
_cv.wait(lock, [] { return _queue.empty(); });

// 模拟生产时间
this_thread::sleep_for(chrono::milliseconds(500));

int item = rand() % 1000 + 1;
_queue.push(item);
cout << "生产: " << item << endl;

// 通知消费者
_cv.notify_all();
}
}

void consume() {
while (!_finish) {
// 获取锁
unique_lock<mutex> lock(_mutex);

// 阻塞等待,直到满足条件才继续往下执行(可以避免虚假唤醒)
_cv.wait(lock, [] { return !_queue.empty(); });

// 模拟消费时间
this_thread::sleep_for(chrono::milliseconds(500));

int item = _queue.front();
_queue.pop();
cout << "消费: " << item << endl;

// 通知生产者
_cv.notify_all();
}
}

int main() {
// 设置随机种子
srand(time(nullptr));

// 启动生产者和消费者线程
thread producer(produce);
thread consumer(consume);

// 主线程运行 60 秒
this_thread::sleep_for(chrono::seconds(60));

// 设置终止标志
_finish = true;

// 唤醒所有线程,确保它们能结束
_cv.notify_all();

// 主线程等待生产者和消费者线程结束
producer.join();
consumer.join();

return 0;
}

程序运行的输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
生产: 98
消费: 98
生产: 635
消费: 635
生产: 318
消费: 318
生产: 513
消费: 513
生产: 413
消费: 413
......