现代 C++ 编程实战
吴咏炜
前 Intel 资深软件架构师
34196 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
加餐 (1讲)
现代 C++ 编程实战
15
15
1.0x
00:00/00:00
登录|注册

19 | thread和future:领略异步中的未来

get_future
构造函数
get_future
set_value
shared_future
get
async
shared_mutex
timed_mutex
recursive_mutex
unlock
try_lock
lock
scoped_thread类
sleep_for
join和detach
构造函数
packaged_task
promise
future
mutex
thread类
信号量
条件变量
无锁算法
互斥锁和死锁问题
原子操作和数据竞争
编译器和处理器的重排问题
并发思维模式
多核处理器的普及
摩尔定律结束
多线程开发
并发编程挑战
原因
并发编程

该思维导图由 AI 生成,仅供参考

你好,我是吴咏炜。
编译期的烧脑我们先告个段落,今天我们开始讲一个全新的话题——并发(concurrency)。

为什么要使用并发编程?

在本世纪初之前,大部分开发人员不常需要关心并发编程;用到的时候,也多半只是在单处理器上执行一些后台任务而已。只有少数为昂贵的工作站或服务器进行开发的程序员,才会需要为并发性能而烦恼。原因无他,程序员们享受着摩尔定律带来的免费性能提升,而高速的 Intel 单 CPU 是性价比最高的系统架构,可到了 2003 年左右,大家骤然发现,“免费午餐”已经结束了 [1]。主频的提升停滞了:在 2001 年,Intel 已经有了主频 2.0 GHz 的 CPU,而 18 年后,我现在正在使用的电脑,主频也仍然只是 2.5 GHz,虽然从单核变成了四核。服务器、台式机、笔记本、移动设备的处理器都转向了多核,计算要求则从单线程变成了多线程甚至异构——不仅要使用 CPU,还得使用 GPU。
如果你不熟悉进程和线程的话,我们就先来简单介绍一下它们的关系。我们编译完执行的 C++ 程序,那在操作系统看来就是一个进程了。而每个进程里可以有一个或多个线程:
每个进程有自己的独立地址空间,不与其他进程分享;一个进程里可以有多个线程,彼此共享同一个地址空间。
堆内存、文件、套接字等资源都归进程管理,同一个进程里的多个线程可以共享使用。每个进程占用的内存和其他资源,会在进程退出或被杀死时返回给操作系统。
并发应用开发可以用多进程或多线程的方式。多线程由于可以共享资源,效率较高;反之,多进程(默认)不共享地址空间和资源,开发较为麻烦,在需要共享数据时效率也较低。但多进程安全性较好,在某一个进程出问题时,其他进程一般不受影响;而在多线程的情况下,一个线程执行了非法操作会导致整个进程退出。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了并发编程的基本概念和相关技术,包括传统的多线程开发和高层抽象future的用法。文章首先解释了并发编程的必要性,随着计算需求的增加,程序员们需要面对并发性能的挑战。接着介绍了基于thread的多线程开发,展示了使用thread类创建线程的方法,并讨论了互斥量的基本语义以及C++标准库中提供的不同类型的互斥量。文章还提到了scoped_thread类的实现,以及建议使用lock_guard来避免手动加锁、解锁的麻烦。随后,文章引入了更高层的抽象future,通过示例展示了如何使用async函数和promise来简化并发编程代码。此外,还介绍了packaged_task的用法,展示了如何使用它来执行任务并获取结果。总的来说,本文全面介绍了并发编程的基本概念和相关技术,对于想要了解并发编程的读者具有很好的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《现代 C++ 编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • 李公子胜治
    作者大大,你好,我在effective modern c++这本书上面看到,作者告诫我们平时写代码时,首先基于任务而不是线程,但是如果我们使用async时,实际上async还是为我们创建了一个新线程,还是没有体会到async比thread的优越性,难道仅仅是可以调用get(),获取async后的执行结果吗?

    作者回复: 少写这么多代码,还没有优越性? 新功能很多是用来提高程序员的工作效率的。而且,脑子摆脱了底层细节,就更有空去思考更高层的抽象了。否则开发里到处是羁绊,只看到这个不能做,那个很麻烦。

    2020-01-08
    24
  • 风临
    老师教得很棒,学之前一头雾水,学之后醍醐灌顶,真是很享受。其实网上的教程很多,但是鱼龙混杂,自身没有经验的话,很难去分清多种方法的优劣,很可能片面甚至误导。所以跟着老师说真的很安心,因为质量很高,既全面又能让人看清楚趋势和方向

    作者回复: 极客时间当然是会挑老师的啦😄。我和极客时间都很爱惜自己的名声,不好好写对不起读者,也对不起自己。

    2020-04-14
    9
  • YouCompleteMe
    当时看<The C++ Programming>下册关于多线程的时候,还写了一些demo,现在看到future/async这些类,一点想不起来怎么用的-_-

    作者回复: 一定是要多用,形成“肌肉记忆”才行。光读不用是真会忘的。

    2020-01-08
    2
    9
  • 刘丹
    future执行get函数的时候,如果此时还没生成结果,是否get就阻塞了,直到有返回值为止呢?

    作者回复: 是的,那时就阻塞等待了。

    2020-03-05
    5
  • 皮皮侠
    模板部分终于看完了,玩过lamda和一些traits。跟着老师学了好多新东西,新思维,尤其是对编译期计算。对一些以前模糊的理论有了新的认识。这几篇以后肯定要回头继续把玩的。另外,我想老师花了这么多心血来写一些14、17、20的新特性,应该是希望让C++既能写出性能高的代码,也易于使用,简练,更适合上层业务逻辑,用心良苦。 蟹蟹老师的分享;)

    作者回复: 理解满分!😁

    2020-02-29
    3
  • even
    吴老师的课适合几度回味,多看几次,跟着敲代码,试着运行,慢慢的领会C++新特性,希望以后能够用上

    作者回复: 对的,练习非常重要。要能够自然地自己写出现代C++代码了,才算学会了。

    2020-04-08
    2
    2
  • 怪兽
    老师你好,想请问下shared_future,原文说“要么调用 future 的 share 方法来生成一个 shared_future,结果就可以在多个线程里用了——当然,每个 shared_future 上仍然还是只能调用一次 get 函数”。 意思是,async 返回的 future 作为共享资源被多个线程使用时,每个线程通过 future 的 share 方法获取到 shared_future,然后每个线程就可以调用get函数了,是这样吗? 另外,我在 MSVC 下测试,async 返回的 future,future.share() 得到 shared_future,shared_future 多次调用它的 get 方法也没有问题.

    作者回复: 盲生,你发现了一个华点。 这个问题提得非常好。这里是我搞错了。future 和 shared_future 具有不同的接口。对于值对象类型,future<Obj>::get() 的返回值就是 Obj,是移动出来的。而 shared_future<Obj>::get() 的返回值是 const Obj&,就是可以多次读取的…… 我回头看看文本怎么修正一下。谢谢🙏。

    2022-11-14归属地:江苏
    1
  • 王大为
    最近用google的cpplint工具扫描了我的代码,但cpplint报告说不允许包含c++11的thread头文件,请问这个是出于什么目的呢? cpplint. py --verbose=5 my_cpp_file output : <thread> is an unapproved c++11 header 我看了一下cpplint脚本,里面确实对mutex thread chrono等头文件做了限制。

    作者回复: 那是Google的偏好。除非你为Google的项目贡献代码,理它干嘛?

    2020-01-09
    3
    1
  • 当初莫相识
    每次看线程都有新的收获,也有新的疑问。scoped_thread在析构函数时join,析构函数会等join完毕后销毁thread成员变量,对吗? scoped_thread th{work, ref(cv), ref(result)}; // 干一些其他事 cout << "I am waiting now\n"; unique_lock lock{cv_mut}; cv.wait(lock); cout << "Answer: " << result << '\n'; } main()函数里,我理解为scoped_thread直到}才会执行析构启动线程,而cv.wait又一直在阻塞,所以不会运行到}。虽然知道程序能得到预期结果,但逻辑上不理解,希望老师能解答我的困惑

    作者回复: 析构时join,然后销毁thread成员变量是对的。 但不是析构时启动线程。thread在构造时就启动线程了。

    2022-09-01归属地:上海
  • 青鸟飞鱼
    老师,你好,关于条件变量应该如何退出呢? 比如说: void f() {     while(m_isRun)     {         std::unique_lock<std::mutex> dataLock(m_mtx);        while(m_list.empty())        {            m_cond. wait(dataLock);         }         ……     } } 线程函数如上面所写,这个线程如何安全退出呢?

    作者回复: 没看出具体意义。如果你希望在wait那句后有可能退出,在那儿检查 m_isRun 不就行了?

    2020-08-05
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部