C++ 并发编程学习01
为什么需要并发编程?
1. 分离关注点
将相关的代码与无关的代码分离,可以使程序更容易理解和测试,从而减少出错的可能性。使用并发分离出不同的功能区域。例如,独立的线程通常用来执行那些必须在后台持续运行的任务,如,桌面搜索程序中监视文件系统变化的任务。
2. 为了性能
两种方式:
任务并行: 将一个单一任务分成几部分,且各自并行运行,从而降低总运行时间,每个线程执行不同部分的算法。也被称为 易并行算法
数据并行: 每个线程在不同的数据部分上执行相同的操作。
3. 什么时候不适用并发?
收益比不上成本,操作系统需要分配内核相关资源和堆栈空间,所以在启动线程时需要开销,然后才能把新线程加入到 调度器 中,这都需要时间。
线程的资源有限。如果太多的线程同时运行,则会消耗很多 操作系统资源 ,从而使得操作系统整体上运行得更加缓慢
每个线程都需要一个 独立的堆栈空间,所以运行太多的线程也会耗尽进程的 可用内存或地址空间。
运行越多的线程,操作系统 就需要越多的 上下文切换,每一次切换都需要耗费本可以花在有价值工作上的时间。
并发和多线程
新标准支持并发
Boost线程库作为新类库的主要模型,很多类与Boost库中的相关类有着相同名称和结构。随着C++标准的进步,Boost线程库也配合着C++标准在许多方面做出改变,因此之前使用Boost的用户将会发现自己非常熟悉C++11的线程库。
C++线程库的效率
为了效率,C++类整合了一些底层工具。这样就需要了解相关使用 高级工具 和使用 低级工具 的开销差,这个开销差就是抽象代价(abstraction penalty)。
C++为了提供足够多的底层工具,伴随着新的内存模型,形成了一个综合的原子操作库,可用于直接控制单个位、字节、内部线程同步,以及对所有变化的可见性。
Hello World
1 |
|
打印信息的代码被移动到了一个独立的函数中(_2_) 。因为每个线程都必须具有一个初始函数(initial function),新线程的执行从这里开始。
对于应用程序来说,初始线程是main()
,但是对于其他线程,可以在std::thread
对象的 构造函数 中指定。本例中,被命名为t
(_3_)的std::thread
对象拥有新函数hello()
作为其初始函数。
所以程序启动时,将线程一分为二,初始线程始于 main()
,而新线程始于hello()
初始线程继续执行。如果它不等待新线程结束,它就将自顾自地继续运行到main()
的结束,从而结束程序——有可能发生在新线程运行之前。这就是为什么在(_4_)这里调用join()
的原因——这会导致调用线程(在main()
中)等待与std::thread
对象相关联的线程,即这个例子中的t
。