- C++ 开发 Web 服务框架
- 1 小时入门增强现实技术
- C++ 实现高性能内存池
- GDB 简明教程
- C++ 实现太阳系行星系统
- C++11/14 高速上手教程
- C 语言实现 Linux Shell 命令解释器
- C++ 打造 Markdown 解析器
- C 语言实现文件类型统计程序
- C 语言实现 Linux touch 命令
- C 语言入门教程
- C 语言实现多线程排序
- 多线程生产者消费者模型仿真停车场
- C++实现运动目标的追踪
- C 语言实现 Linux 网络嗅探器
- 100 行 C++ 代码实现线程池
- C 语言实现聊天室软件
- C 语言实现 Linux who 命令
- C 语言实现 Linux cp 命令
- C++实现第一人称射击游戏
- C++ 实现银行排队服务模拟
- 数据结构(新版)
- 软件工程(C 编码实践篇)
- C 语言制作简单计算器
- C 语言版 flappy_bird
- C 语言编写万年历
- C 语言版扫雷游戏
- C 语言实现一个支持 PHP 的简易 WEB 服务器
- C 语言制作 2048
- C 语言模拟 ATM 自动取款机系统
- Linux 系统编程
- C 语言利用 epoll 实现高并发聊天室
- C 语言快速实现五子棋
- C 语言实现 ping 程序
- 简单词法分析器(C++语言)
第 7 节 C++ 11/14 高速上手教程 - 语言级线程支持
一、本节内容
本节内容包括:
- 对标准库的扩充: 语言级线程支持
- std::thread
- std::mutex/std::uniquelock
- std::future/std::packagedtask
- std::condition_variable
> 提示:本节代码编译需要使用 -pthread
选项,例如: > > > g++ main.cpp -std=c++14 -pthread >
二、std::thread
std::thread
用于创建一个执行的线程实例,所以它是一切并发编程的基础,使用时需要包含<thread>头文件,它提供了很多基本的线程操作,例如 get_id()
来获取所创建线程的线程 ID,例如使用 join()
来加入一个线程等等,例如:
#include <iostream>
#include <thread>
void foo() {
std::cout << "hello world" << std::endl;
}
int main() {
std::thread t(foo);
t.join();
return 0;
}
三、 std::mutex, std::unique_lock
我们在操作系统的相关知识中已经了解过了有关并发技术的基本知识,mutex 就是其中的核心之一。C++11 引入了 mutex 相关的类,其所有相关的函数都放在 <mutex>
头文件中。
std::mutex
是 C++11 中最基本的 mutex
类,通过实例化 std::mutex
可以创建互斥量,而通过其成员函数 lock()
可以仅此能上锁, unlock()
可以进行解锁。但是在在实际编写代码的过程中,最好不去直接调用成员函数,因为调用成员函数就需要在每个临界区的出口处调用 unlock()
,当然,还包括异常。这时候 C++11 还为互斥量提供了一个 RAII 语法的模板类 std::lock_gurad
。RAII 在不失代码简洁性的同时,很好的保证了代码的异常安全性。
在 RAII 用法下,对于临界区的互斥量的创建只需要在作用域的开始部分,例如:
void some_operation(const std::string &message) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
// ...操作
// 当离开这个作用域的时候,互斥锁会被析构,同时 unlock 互斥锁
// 因此这个函数内部的可以认为是临界区
}
由于 C++保证了所有栈对象在声明周期结束时会被销毁,所以这样的代码也是异常安全的。无论 some_operation()
正常返回、还是在中途抛出异常,都会引发堆栈回退,也就自动调用了 unlock()
。
而 std::unique_lock
则相对于 std::lock_guard
出现的, std::unique_lock
更加灵活, std::unique_lock
的对象会以独占所有权(没有其他的 unique_lock
对象同时拥有某个 mutex
对象的所有权)的方式管理 mutex
对象上的上锁和解锁的操作。所以在并发编程中,推荐使用 std::unique_lock
。例如:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void block_area() {
std::unique_lock<std::mutex> lock(mtx);
//...临界区
}
int main() {
std::thread thd1(block_area);
thd1.join();
return 0;
}
std::future, std::packaged_task
std::future
则是提供了一个访问异步操作结果的途径,这句话很不好理解。为了理解这个特性,我们需要先理解一下在 C++11 之前的多线程行为。
试想,如果我们的主线程 A 希望新开辟一个线程 B 去执行某个我们预期的任务,并返回我一个结果。而这时候,线程 A 可能正在忙其他的事情,无暇顾及 B 的记过,所以我们会很自然的希望能够在某个特定的时间获得线程 B 的结果。
在 C++11 的 std::future
被引入之前,通常的做法是:创建一个线程 A,在线程 A 里启动任务 B,当准备完毕后发送一个事件,并将结果保存在全局变量中。而主函数线程 A 里正在做其他的事情,当需要结果的时候,调用一个线程等待函数来获得执行的结果。
而 C++11 提供的 std::future
简化了这个流程,可以用来获取异步任务的结果。自然地,我们很容易能够想象到把它作为一种简单的线程同步手段。
此外, std::packaged_task
可以用来封装任何可以调用的目标,从而用于实现异步的调用。例如:
#include <iostream>
#include <future>
#include <thread>
int main() {
// 将一个返回值为 7 的 lambda 表达式封装到 task 中
// std::packaged_task 的模板参数为要封装函数的类型
std::packaged_task<int()> task([](){return 7;});
// 获得 task 的 future
std::future<int> result = task.get_future(); // 在一个线程中执行 task
std::thread(std::move(task)).detach(); std::cout << "Waiting...";
result.wait();
// 输出执行结果
std::cout << "Done!" << std:: endl << "Result is " << result.get() << '\n';
}
在封装好要调用的目标后,可以使用 get_future()
来获得一个 std::future
对象,以便之后事实线程同步。
std::condition_variable
std::condition_variable
是为了解决死锁而生的。当互斥操作不够用而引入的。比如,线程可能需要等待某个条件为真才能继续执行,而一个忙等待循环中可能会导致所有其他线程都无法进入临界区使得条件为真时,就会发生死锁。所以, condition_variable
实例被创建出现主要就是用于唤醒等待线程从而避免死锁。 std::condition_variable
的 notify_one()
用于唤醒一个线程; notify_all()
则是通知所有线程。下面是一个生产者和消费者模型的例子:
#include <condition_variable>
#include <mutex>
#include <thread>
#include <iostream>
#include <queue>
#include <chrono>
int main()
{
// 生产者数量
std::queue<int> produced_nums;
// 互斥锁
std::mutex m;
// 条件变量
std::condition_variable cond_var;
// 结束标志
bool done = false;
// 通知标志
bool notified = false;
// 生产者线程
std::thread producer([&]() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
// 创建互斥锁
std::unique_lock<std::mutex> lock(m);
std::cout << "producing " << i << '\n';
produced_nums.push(i);
notified = true;
// 通知一个线程
cond_var.notify_one();
}
done = true;
cond_var.notify_one();
});
// 消费者线程
std::thread consumer([&]() {
std::unique_lock<std::mutex> lock(m);
while (!done) {
while (!notified) { // 循环避免虚假唤醒
cond_var.wait(lock);
}
while (!produced_nums.empty()) {
std::cout << "consuming " << produced_nums.front() << '\n';
produced_nums.pop();
}
notified = false;
}
});
producer.join();
consumer.join();
}
总结
C++11 语言层提供了并发编程的相关支持,本节简单的介绍了 std::thread
/ std::mutex
/ std::future
这些并发编程中不可回避的重要工具。
> 本节提到的内容足以让我们使用不超过 100 行代码编写一个简单的线程池库
> 关于这方面的使用技巧,可以通过项目课: 100 行 C++ 代码实现线程池 进行进一步巩固学习。
本节代码:
http://labfile.oss.aliyuncs.com/courses/605/7.zip
进一步阅读的参考资料
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论