在做多线程编程时, 有两个场景我们都会遇到:
多线程访问共享资源, 需要用到锁;
多线程间的状态同步, 这个可用的机制很多, 条件变量是广泛使用的一种.
今天我用一个简单的例子来给大家介绍下锁和条件变量的使用.
代码使用 C++11
示例代码
- #include <iostream>
- #include <mutex>
- #include <thread>
- #include <condition_variable>
- std::mutex g_mutex; // 用到的全局锁
- std::condition_variable g_cond; // 用到的条件变量
- int g_i = 0;
- bool g_running = true;
- void ThreadFunc(int n) { // 线程执行函数
- for (int i = 0; i <n; ++i) {
- {
- std::lock_guard<std::mutex> lock(g_mutex); // 加锁, 离开 {} 作用域后锁释放
- ++g_i;
- std::cout <<"plus g_i by func thread" << std::this_thread::get_id() << std::endl;
- }
- }
- std::unique_lock<std::mutex> lock(g_mutex); // 加锁
- while (g_running) {
- std::cout <<"wait for exit" << std::endl;
- g_cond.wait(lock); // wait 调用后, 会先释放锁, 之后进入等待状态; 当其它进程调用通知激活后, 会再次加锁
- }
- std::cout << "func thread exit" << std::endl;
- }
- int main() {
- int n = 100;
- std::thread t1(ThreadFunc, n); // 创建 t1 线程(func thread),t1 会执行 `ThreadFunc` 中的指令
- for (int i = 0; i < n; ++i) {
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- ++g_i;
- std::cout <<"plus g_i by main thread" << std::this_thread::get_id() << std::endl;
- }
- }
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- g_running = false;
- g_cond.notify_one(); // 通知其它线程
- }
- t1.join(); // 等待线程 t1 结束
- std::cout <<"g_i =" << g_i << std::endl;
- }
程序运行后, 关键输出如下:
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by func thread 139921006847744
- plus g_i by func thread 139921006847744
- plus g_i by func thread 139921006847744
- plus g_i by func thread 139921006847744
- plus g_i by func thread 139921006847744
- wait for exit // func thread 等待 main thread 发来的退出信号
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- plus g_i by main thread 139921025066816
- func thread exit
- g_i = 200 // 锁机制保证了 g_i 的正确
可以看到:
- std::this_thread::get_id()
- g_i
加锁方法介绍
加锁相关的代码为:
- {
- std::lock_guard<std::mutex> lock(g_mutex);
- ......
- }
要点为:
首先, 这在一个局部作用域内, std::lock_guard 在构造时, 会调用 g_mutex->lock() 方法;
局部作用域代码结束后, std:;lock_guard 的析构函数会被调用, 函数中会调用 g_mutex->unlock() 方法.
这样就实现了加锁和解锁的过程, 为什么不直接调用加锁解锁方法呢?
我想, 这是因为如果加锁和解锁中间的代码出现了问题, 导致线程函数异常退出, 那么这个锁就一直无法得到释放, 其它线程处理的不好的话, 就会造成死锁了.
条件变量使用介绍
当线程调用 g_cond.wait(lock) 前要先手动调用 lock->lock() , 这里是通过 std::unique_lock 的构造方法实现的;
当线程调用 g_cond.wait(lock) 进入等待后, 会调用 lock->unlock() 方法, 所以这也是前面构造 lock 时使用了 std::unique_lock ;
通知使用的 g_cond.notify_one() , 这个可以通知一个线程, 另外还有 g_cond.notify_all() 用于通知所有线程;
线程收到通知的代码放在一个 while 循环中, 这是为了防止 APUE 中提到的虚假通知.
结束语
上面是我对 C++11 中多线程加锁和条件变量使用的基本认识, 有不当的地方, 还望指正.
参考
- cppreference:https://en.cppreference.com/w/cpp/thread
- APUE:https://book.douban.com/subject/1439495/
来源: http://www.tuicool.com/articles/Yzeu6nr