【C++并发编程】【1】【Hello, world of concurrency in C++!】What is concurrency?

并发,多进程并发,多线程并发,并行

Posted by x-jeff on September 30, 2024

【C++并发编程】系列博客为参考《C++ Concurrency IN ACTION (SECOND EDITION)》一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.What is concurrency?

从最简单、最基本的层面上,并发(concurrency)就是指两个或更多的独立活动同时进行。

2.Concurrency in computer systems

计算机领域的并发指的是单个系统并行执行多个独立活动,而不是依次或一个接一个的执行。

以前,大多数计算机只有一个处理器(processor),配有一个处理单元(processing unit)或核心(core),今天的许多计算机仍然如此。这类计算机一次只能执行一个任务,但它可以每秒在多个任务之间切换多次。这个任务做一会,然后切换到别的任务再做一会,给人一种任务是同时执行的假象。我们将这种机制称为任务切换(task switching)。但这和真正的并发仍有细微差别。

无论是拥有多个处理器,还是在一个处理器内有多个核心(或者两者兼有),这些计算机都能够真正并行运行多个任务,我们将这种现象称为硬件并发(hardware concurrency)。

如Fig1.1所示的理想化场景,假设计算机有两个任务要执行,每个任务被分成10个大小相等的块。在双核计算机(即拥有两个处理核心)上,每个任务可以在各自的核心上执行。而在单核计算机上执行任务切换时,这些任务的块是交错排列的,并且块之间有较粗的灰条间隔,这表示系统每次从一个任务切换到另一个时都必须执行上下文切换(context switch),而这需要时间。为了执行上下文切换,操作系统必须保存当前正在运行任务的CPU状态和指令指针,计算出要切换到哪个任务,并重新加载要切换的任务的CPU状态。然后,CPU可能需要将新任务的指令和数据加载到缓存中,这会阻止CPU执行任何指令,导致进一步的延迟。

Fig1.2展示了4个任务在双核计算机上的任务切换,同样是一个理想化的场景,任务被整齐地划分为大小相等的块。实际上,很多因素会导致块分割不均,调度也会不规则。

3.Approaches to concurrency

第一种方式是多个进程,每个进程只有一个线程。第二种方式是多个线程在一个进程中运行。此外,也可以以任意方式组合这些方法,即可以有多个进程,其中有些是多线程的,有些是单线程的。

3.1.CONCURRENCY WITH MULTIPLE PROCESSES

在应用程序中使用并发的第一种方式是将应用程序分成多个独立的单线程进程,这些进程同时运行,比如同时运行网页浏览器和文字处理器。这些独立的进程可以通过所有常见的进程间通信通道(如信号、套接字、文件、管道等)相互传递消息,如Fig1.3所示。一个缺点是,这种进程之间的通信通常要么设置复杂,要么速度慢,或者两者兼有,因为操作系统通常为进程之间提供了大量保护,以防止一个进程意外修改属于另一个进程的数据。另一个缺点是运行多个进程本身的开销,比如启动进程的时间、操作系统必须分配内部资源来管理进程等。

这种方式也不全是缺点,操作系统通常在进程之间提供额外保护以及更高级别的通信机制,这意味着使用进程而不是线程编写安全的并发代码更容易。

使用独立进程来实现并发还有一个额外的优势——我们可以在通过网络连接的不同机器上运行这些独立的进程。虽然这增加了通信成本,但在精心设计的系统中,这可能是一种提高并行性和性能的成本效益较高的方法。

3.2.CONCURRENCY WITH MULTIPLE THREADS

另一种并发方式是在单个进程中运行多个线程。线程很像轻量级的进程,每个线程独立运行。一个进程中的所有线程共享相同的地址空间。虽然在进程之间共享内存通常也是可能的,但设置起来很复杂且难以管理,因为在不同的进程中,同一数据的内存地址不一定相同。Fig1.4展示了在一个进程中通过共享内存进行通信的两个线程。

线程之间共享地址空间,并且缺乏对数据的保护,使得使用多线程的开销比使用多进程的开销小得多,因为操作系统的管理任务减少了。但共享内存的灵活性也有代价:如果多个线程访问同一数据,应用程序开发者必须确保每个线程访问数据时所看到的数据是一致的。

尽管共享内存可能会带来一些潜在的问题,但相比多个单线程进程,在单个进程中运行多个线程的开销较低,使之成为了主流语言(包括C++)中实现并发的首选方法。此外,C++标准并不提供进程间通信的本质支持,因此使用多个进程的应用程序必须依赖平台特定的API来实现。因此,本系列博客提到的并发默认指的是多线程实现的并发。

4.Concurrency vs. parallelism

在多线程代码中,并发(concurrency)和并行(parallelism)的含义在很大程度上是重叠的。区别主要在于二者的侧重点有一些细微的差别。