并发性和并行性都用于多线程程序,但它们之间的相似性和差异存在很多混淆。 在这方面的一个大问题是:并发是不是就是并行? 虽然这两个术语看起来很相似,但对上述问题的答案是否定的,并发性和并行性并不相同。 现在,如果它们不一样,它们之间的基本区别是什么?
简而言之,并发处理的是处理来自不同线程的共享状态访问,而并行处理利用多个CPU或其内核来提高硬件性能。
并发简介
并发是两个任务在执行过程中重叠的时候。 这可能是一个应用程序正在同时处理多个任务的情况。 我们可以用图解理解它; 多项任务正在同时取得进展,如下所示 -
并发级别
在本节中,我们将讨论编程方面的三个重要级别的并发性 -
1. 低级并发
在这种并发级别中,显式使用了原子操作。 我们不能在构建应用程序时使用这种并发性,因为它非常容易出错并且很难调试。 即使Python不支持这种并发性。
2. 中级并发
在这种并发中,没有使用显式的原子操作。 它使用显式锁。 Python和其他编程语言支持这种并发性。 大多数应用程序员使用这种并发性。
3. 高级并发
在这种并发中,不使用显式原子操作也不使用显式锁。 Python有concurrent.futures
模块来支持这种并发。
并发系统的性质
要使程序或并发系统正确,一些属性必须由它来满足。 与终止系统相关的属性如下 -
正确性属性
正确性属性意味着程序或系统必须提供所需的正确答案。 为了简单起见,可以说系统必须正确地将启动程序状态映射到最终状态。
安全属性
安全属性意味着程序或系统必须保持“良好”或“安全”状态,并且从不做任何“坏”的事情。
活跃度属性
这个属性意味着一个程序或系统必须“取得进展”,并且会达到一个理想的状态。
并发系统的行为者
这是并发系统的一个常见属性,其中可以有多个进程和线程,它们同时运行以在他们自己的任务上取得进展。 这些进程和线程称为并发系统的角色。
并发系统的资源
行为者必须利用内存,磁盘,打印机等资源来执行任务。
某些规则集
每个并发系统必须拥有一套规则来定义执行者要执行的任务类型和每个任务的执行时间。 任务可能是获取锁,共享内存,修改状态等。
并发系统的障碍
在实现并发系统时,程序员必须考虑以下两个重要问题,这可能是并发系统的障碍 -
共享数据
实现并发系统时的一个重要问题是在多个线程或进程间共享数据。 实际上,程序员必须确保锁保护共享数据,以便所有对它的访问都被序列化,并且一次只有一个线程或进程可以访问共享数据。 如果多个线程或进程都试图访问相同的共享数据,那么除了其中至少一个以外,其他所有进程都将被阻塞并保持空闲状态。 换句话说,在锁定生效时,我们只能使用一个进程或线程。 可以有一些简单的解决方案来消除上述障碍 -
数据共享限制
最简单的解决方案是不共享任何可变数据。 在这种情况下,我们不需要使用显式锁定,并且可以解决由于相互数据而导致的并发障碍。
数据结构协助
很多时候并发进程需要同时访问相同的数据。 与使用显式锁相比,另一种解决方案是使用支持并发访问的数据结构。 例如,可以使用提供线程安全队列的队列模块。 也可以使用multiprocessing.JoinableQueue
类来实现基于多处理的并发。
不可变的数据传输
有时,我们使用的数据结构(比如说并发队列)不适合,那么可以传递不可变数据而不锁定它。
可变数据传输
继续上面的解决方案,假设如果它只需要传递可变数据而不是不可变数据,那么可以传递只读的可变数据。
共享I/O资源
实现并发系统的另一个重要问题是线程或进程使用I/O资源。 当一个线程或进程使用I/O很长时间而另一线程或进程闲置时会出现问题。 在处理I/O大量应用程序时,我们可以看到这种障碍。 可以通过一个例子来理解,从Web浏览器请求页面。 这是一个沉重的应用程序。 在这里,如果数据请求的速率比它消耗的速率慢,那么在并发系统中就会有I/O障碍。
以下Python脚本用于请求网页并获取网络用于获取请求页面的时间 -
import urllib.request
import time
ts = time.time()
req = urllib.request.urlopen('http://www.yiibai.com')
pageHtml = req.read()
te = time.time()
print("Page Fetching Time : {} Seconds".format (te-ts))
执行上述脚本后,可以获取页面获取时间,如下所示。
Page Fetching Time: 0.999139881798332 Seconds
可以看到,获取该页面的时间差不多是一秒钟。 现在,如果我们想要访问数千个不同的网页,您可以大概知道访问网络需要多少时间。
什么是并行性?
并行可定义为将任务分解为可同时处理的子任务的技术。 如上所述,它与并发性相反,其中两个或更多事件同时发生。 我们可以用图解理解它; 一个任务被分解成可以并行处理的多个子任务,如下所示 -
并行但不平行
应用程序可以是并行的,但不是并行的,意味着它可以同时处理多个任务,但任务不会分解为子任务。
并行但不并发
一个应用程序可以是并行的,但不是并行的,意味着它一次只能在一个任务上工作,并且分解为子任务的任务可以并行处理。
既不平行也不并发
应用程序既不能并行也不能并发。 这意味着它一次只能处理一项任务,并且任务不会被分解为子任务。
并行和并发
应用程序既可以是并行的,也可以是并行的,这意味着它既可以同时在多个任务上工作,也可以将任务分解为子任务并行执行。
并行的必要性
我们可以通过在单CPU的不同内核之间或网络内连接的多台计算机之间分配子任务来实现并行。
考虑以下要点来理解为什么有必要实现并行性 -
有效的代码执行
借助并行性,我们可以高效地运行代码。 它将节省时间,因为部分中的相同代码并行运行。
比顺序计算更快速
顺序计算受到物理和实际因素的限制,因此无法获得更快的计算结果。 另一方面,这个问题可以通过并行计算来解决,并且比顺序计算提供更快的计算结果。
执行时间更短
并行处理减少了程序代码的执行时间。
如果要谈论真实生活中并行性的例子,我们计算机的图形卡就是一个例子,它强调了并行处理的真正能力,因为它拥有数百个独立工作的独立处理内核,并且可以同时执行。 由于这个原因,我们也能够运行高端应用程序和游戏。
理解处理器的实现
我们知道并发性,并行性以及它们之间的差异,但是它将如何实现。 理解将要实施的系统是非常必要的,因为它使我们在设计软件时能够做出明智的决定。有以下两种处理器 -
单核处理器
单核处理器能够在任何给定时间执行一个线程。 这些处理器使用上下文切换来在特定时间存储线程的所有必要信息,然后再恢复信息。 上下文切换机制有助于我们在给定秒内的多个线程上取得进展,并且看起来好像系统正在处理多种事情。
单核处理器具有许多优点。 这些处理器需要更少的功率,并且多个内核之间没有复杂的通信协议。 另一方面,单核处理器的速度有限,不适合更大的应用。
多核处理器
多核处理器具有多个独立处理单元,也称为核心。
这种处理器不需要上下文切换机制,因为每个核心都包含执行一系列存储指令所需的所有内容。
读取 - 解码 - 执行的周期
多核处理器的内核遵循一个执行周期。 这个周期被称为读取 - 解码 - 执行周期。 它涉及以下步骤 -
读取
这是循环的第一步,它涉及从程序存储器读取指令。
解码
最近读取的指令将被转换为一系列触发CPU其他部分的信号。
执行
这是获取和解码指令将被执行的最后一步。 执行结果将存储在CPU寄存器中。
这里的一个优势是多核处理器的执行速度比单核处理器的执行速度快。 它适用于更大的应用程序。 另一方面,多核之间的复杂通信协议是一个问题。 多核需要比单核处理器需要更多的功率。