何谓并发(concurrency)?
计算机中的并发是指单个系统在同时执行多个独立的任务。
当计算机只有一个处理器的时候,具有单个处理单元(processing uint)或者核心(core),这种机器在处理多任务的时候是在不停地进行任务切换(task switching),这样仍然可以称之为并发。
现在有了单芯多核的计算机可以真正的并行处理多个任务,我们称之为硬件并发(hardware concurrency)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fT8Yn1o7-1646023114780)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/aa3e3845-5af8-439a-93b2-72658ee05cef/Screen_Shot_2022-02-27_at_21.48.44.png)]
有些处理器可以在一个核心上执行多个线程,但是硬件并发在多处理器或者多核系统上的效果更加显著。硬件线程(hardware threads)最重要的因素是数量,也就是硬件上可以并发运行多少独立的任务
并发的途径
方法一:多进程并发
将应用程序分为多个独立的进程,在同一时刻运行,独立的进程之间可以通过常规的信息渠道传递讯息。
缺点:进程之间的通信通常设置复杂,速度慢(因为OS会在进程间设置保护措施,避免一个进程回去修改另一个进程),还有一个缺点就是运行多个进程所需的固定开销:需要时间启动进程,操作系统需要内部资源来管理进程。
优点:OS在进程见提供的附加保护操作和更高级别的通信机制,意味着可以编写更加safe的并发代码。另外一个有时就是可以使用远程连接的方式,在不同的机器上运行独立的进程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ptxhWEEj-1646023114781)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f51d92c8-2fed-4e12-9621-8333c41c5d37/Screen_Shot_2022-02-28_at_10.18.30.png)]
方法二:多线程并发
在单个进程中运行多个线程,每个线程之间相互独立,可以在不同的指令序列中运行。
缺点:进程中所有线程都共享地址空间,所欧线程访问到大部分数据,全局变量仍然是全聚德,指针、对象的引用或者数据都可以在线程之间传递。虽然进程之间也能共享内存,但是这种贡献通常是难以建立的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lTcin8a0-1646023114781)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/34506210-bd20-4611-a916-be0964f0081d/Screen_Shot_2022-02-28_at_10.25.13.png)]
地址空间的共享,以及缺少线程之间的数据保护,使得OS的工作量变小,开销就比多进程小的多,但是这些优点都是有代价的:如果数据要被多个线程访问,程序员就要保证每个线程所访问到的数据是一致的
为什么并发
为了分离关注点
将相关代码和无关代码进行分离,使程序可以能加容易理解和测试
为了性能
1⃣️:单个任务分成几个部分,并且各自并行运行,降低总运行时间:任务并行(task parallelism)
2⃣️:每个线程在 不同的数据部分上执行相同的操作:数据并行(data parallelism)
什么时候不能并发
收益比不上成本:除非潜在的性能增益足够 大或关注点分离地足够清晰,能抵消所需的额外的开发时间以及与维护多线程代码相关的额 外成本(代码正确的前提下);否则,别用并发。
C++中使用的并发和多线程
C++11开始编写不以来平台拓展的多线程代码
历史
c++98不承认线程的存在
开始入门
#include <iostream>
// 多线程头文件,管理线程的函数和类声明在<thread>中,
// 保护共享数据的函数和类在其他头文件中声明
#include <thread>
using namespace std;
// 写信息的代码被移动到了一个独立的函数中,因为每个线程必须具有一个初始函数
// 新线程的执行在这里开始,对于应用程序来说,初始线程是main(),对于替他线程,可以在std::thread中指定
// 在本例中,命名为t的thread对象拥有函数hello()作为其初始函数
void hello(){
cout << "hello mutlithread world" <<endl;
}
// 与直接写入标准输出或是从main()调用hello()不同,
// 该程序启动了一个全新的线程来实现,将线程数量一分为二——初始线程始于main(),而新线程始于hello()。
int main(){
// 新线程启动后,初始线程继续执行初始线程继续执行。如果它不等待新线程结束,
// 它就将自顾自地继续运行到main()的结束,从而结束程序——有可能发生在新线程运行之前,
// 所以会用到 join()
std::thread t(hello);
t.join();
}