更多的阅读笔记,及示例代码见 Github:
https://github.com/anlongstory/C-_Concurrency_in_Action_reading_notes
Chapter 1 你好,C++的并发世界
1.1 并发:
-
最简单和最基本的并发,是指两个或更多独立的活动同时发生。
并发在生活中随处可见,我们可以一边走路一边说话,也可以两只手同时作不同的动作,还
有我们每个人都过着相互独立的生活——当我在游泳的时候,你可以看球赛,等等。
-
计算机中的并发: 当计算机只有单个处理单元(核),机器只能在某一时刻执行一个任务,常见的方式是“这个任务做一会,再切换到别的任务,再做一会儿”的方式,看起来像是并行执行的,现在叫做任务切换。 双核机器真正的并行称为 硬件并发
并发途径:
1.多进程并发
使用并发的第一种方法,是将应用程序分为多个独立的进程,它们在同一时刻运行,就像同时进行网页浏览和文字处理一样。独立的进程可以通过进程间常规的通信渠道
传递讯息(信号、套接字、文件、管道等等)
2.多线程并发
线程很像轻量级的进程:每个线程相互独立运行,且线程可以在不同的指令序列中运行。但是,进程中的所有线程都共享地址空间,并且所有线程访问到大部分数据———全局变量仍然是全局的,指针、对象的引用或数据可以在线程之间传递。
1.2 使用多线程的原因
-
分离关注点:
如,DVD播放界面,对DVD的读取和播放可以使用一个线程,对用户操作的响应(暂停,快进等)可以使用另一个线程,这样就不用将两个功能混在同一个代码中,然后在对DVD文件进行操作的时候定期检查用户的输入了。不过上述分成两个线程时,任务间需要人为的进行关联。 -
为了性能:
多处理器系统它们计算能力的提高不是源自使单一任务运行的更快,而是并行运行多个任务。有两种方式利用并发提高性能:
第一,将一个单个任务分成几部分,且各自并行运行,从而降低总运行时间,这是任务并行(task parallelism),这相当复杂,因为各个部分之间可能存在依赖。
第二,一个线程执行算法的一部分,而另一个线程执行算法的另一个部分——或是在数据方面——每个线程在不同的数据部分上执行相同的操作,这是数据并行(data parallelism)。
什么时候不使用并发
基本上,不使用并发的唯一原因就是,收益比不上成本。使用并发的代码在很多情况下难以理解,因此编写和维护的多线程代码就会产生直接的脑力成本,同时额外的复杂性也可能引起更多的错误。除非潜在的性能增益足够大或关注点分离地足够清晰,能抵消所需的额外的开发时间以及与维护多线程代码相关的额外成本(代码正确的前提下);否则,别用并发。
主要每一个线程调用和启动有固定的时间花销;线程也是有限资源,让运行太多可能会使整体操作系统运行更加缓慢;
1.3 C++ 中的并发和多线程
C++ 多线程历史
C++98(1998)标准不承认线程的存在,并且各种语言要素的操作效果都以顺序抽象机的形式编写。不仅如此,内存模型也没有正式定义,所以在C++98标准下,没办法在缺少编译器相关扩展的情况下编写多线程应用程序。
C++11(2011) 标准发布在标准库中加入了全新的线程感知内存模型,同时也有:管理线程、保护共享数据、线程间同步操作,以及低级原子操作的各种类。
开始入门
第一个多线程程序:
#include<iostream>
#include<thread> // 支持多线程的头文件
using namespace std;
void hello()
{
cout << " Hello Concurrent World\n" << endl;
}
int main()
{
// 每个线程必须有一个初始函数,所以这里把打印单独成一个函数;
// 新线程的执行从这里开始
thread t(hello); // 新线程名字为 t ,有 hello()作为初始函数
t.join();
system("pause");
return 0;
}
上面执行程序的初始线程始于 main(),而新线程始于 hello(),且在新的线程 t 启动后, 初始线程继续执行,直到运行到线程结束,而这是可能会出现主程序提前于 t 线程结束,这就是使用 t.join();
的原因。
可以看到,为了简单的打印一句话就需要做大量的工作,所以一般简单的工作不值得用多线程,尤其是初始线程并没有做什么。后面会通过实例展示哪些场景下使用多线程可以获得收益。