目录
1、概念
并发(concurrency):两个或多个独立进行的活动。每秒在多个任务间多次切换,看起来所有的任务都在同时执行。任务切换对使用者和软件自身都制造出并发的表象。
硬件并发(hard concurrency):多核处理器真正能同时并行运行多个任务。
单核机交替执行任务:每当切换时就必须完成一次上下文切换(context switch),耗费了时间,操作系统需要保存当前任务的CPU状态和指令指针,判定需要切换到哪个任务,为之重新加载CPU状态。接着,CPU有可能需要将新任务的 指令和数据从内存加载到缓存。
尽管多核处理器或多核系统更适合硬件并发,如今的处理器能在单核上执行多线程,即使硬件支持并发,任务数量往往也会超过硬件本身可以并行处理的数量,故单核的任务切换(并发)仍然有用。
2、多进程并发
将一个应用软件拆分成多个独立的进程同时运行,每个进程都只包含单一的线程,这些独立的进程可以通过常规的进程间通信途径相互传递信息(信号、套接字、文件、管道等)。
缺点:
1.操作系统需要在进程之间提供大量防护措施,以免某一进程改变另一进程的数据,导致设置复杂、速度慢。
2.运行多个进程固定开销大,进程需要启动时间,系统需要调配内部资源来管控进程。
优点:
1.操作系统在进程间提供额外的保护和高级通信机制,因此比起线程,更容易编写安全的并发代码。某些环境以进程作为基本构建单元。(如Erlang语言的环境)
2.通过网络连接,独立的进程能够在不同的计算机上运行。
3、多线程并发
单一进程内运行多线程。每个线程都独立运行,并各自执行不同的指令序列。同一进程内的所有线程都公用相同的地址空间(共享内存),所有线程都能直接访问大部分数据,即全局变量全局可见,指向对象或数据的指针和引用能在线程间传递。
缺点:
同一数据地址在不同进程中不一定相同,难以驾驭。
优点:
开销低,主流语言青睐以这种方式实现并发。
4、并发与并行(parallel)
都指使用可调配的硬件资源同时运行多个任务。关注点不同:并行更注重大规模数据处理的性能。并发更关注分离关注点或响应能力。(也有说并行是任务同时执行,并发是任务间切换。)下面是并发的两个主要作用:
分离关注点(separation of concerns)
归类相关代码,隔离不同领域的操作代码,使代码更清楚明晰。如视频播放软件在播放的同时要实时监测用户输入指令,结果可能会混杂用户界面的代码和播放视频的代码。因此,分离关注点可大幅简化各线程业务的内部逻辑,线程间交互集中在特定代码中可明确辨识的切入点,无须将不同任务的逻辑交错散置。
提升性能
增强性能的并发方式:
1.易并行(pleasingly parallel,也被成为尴尬并行、天然并行、方便并行)将单一任务分解成多个部分,各自并行运作。任务之间的复杂依赖关系可能导致复杂的处理过程。任务并行:某线程运行算法的一个部分,另一线程运行算法的其它部分。数据并行:线程对数据的不同部分执行相同的操作。
优点:可按规模伸缩——随着硬件支持的线程数增加,算法的并行程度自然提升。
2.利用并行资源处理大规模问题。每次同时处理多个数据(数量固定,无法按规模伸缩)。
优点:数据吞吐量会增加。
5、权衡收益与代价
采用并发技术的代码更难理解,编写和维护成本也会提高。
性能增幅可能达不到预期,线程启动有固定开销;线程间的上下文切换需要一定的时间,过于频繁的切换反而会降低软件整体性能。设计过程可以以硬件并发资源为依据调整线程数量。
线程占用有限的资源,线程太多会令系统整体变慢,系统分配给进程的资源有上限,若线程过多,可能会耗尽进程的全部地址空间。
C++11标准基于Boost线程库设计了C++并发库,C++14引入保护共享数据的新互斥,C++17简化了多线程代码的编写。
6、Hello World
#include<iostream>
#include<thread>
void hello() {
std::cout << "Hello World!" << std::endl;
}
int main()
{
std::thread t(hello);
t.join();
}
<thread>包含支持多线程的相关声明,包括管控线程的函数和类的声明。每个线程都需要一个起始函数,需要在std::thread对象的构造函数中声明。本例中,以hello()函数作为线程t的起始函数。
join()的调用会另主线程等待子线程执行完毕再继续执行。