1 一些概念
1.1 并行与并发
并发concurrency
并发指计算机同时管理多个和任务的能力。这些任务并不要求在每时每刻同时执行,但是可能在一段时间内交错执行。并发的任务是处理多个用户输入,管理多个I/O任务,或者处理多个独立任务。并发适合依赖诸如文件和网络数据等外部资源的I/O约束型的操作。
并行parallelism
并行是并发的子集。并发要求多个任务每时每刻同时执行。并行的任务是提升计算效率和加速系统性能。并行需要利用多核CPU特性,适合CPU密集型任务。
concurrency in Python
并发类型
- processed-based concurrency:multiprocessing
- thread-based concurrency:threading
- coroutine-based concurrency:async/await关键字
补充:python提供concurent.future库来提供进程/线程并发
1.2 多处理器系统
多核系统:CPU芯片上集成多个核心,结构上可以是对称多处理SMP(所有核心地位相同)和非对称处理(核心有主从之分)。
集群系统:两个或者多个独立系统通过计算机网络组成。将应用程序的不同部分并行运行在集群的不同系统上的技术称为并行计算技术。集群系统结构上可以是对称或者非对称的。
1.3 CPU,物理核心,逻辑核心,超线程
CPU:中央处理器,一个CPU芯片可以集成多个CPU核心。
CPU物理核心:有独立的电路,L1,L2高速缓存,能独立执行指令。
CPU逻辑核心:应用程序将一个高速运转的CPU物理核心看做一个或者多个核心,应用程序视角下的核心就是逻辑核心。如果一个物理核心对应两个逻辑核心,意味着CPU采用了超线程技术。逻辑CPU数量=同时可执行的线程数。
超线程:超线程可以在一个逻辑核等待指令执行的间隔(等待从cache或内存中获取下一条指令),把时间片分配到另一个逻辑核。CPU在这两个逻辑核之间高速切换,让应用程序感知不到这个间隔,误认为自己是独占了一个核。实现超线程需要在CPU物理核心内设置冗余部件,才能让多个线程运行在一个物理核心上,从而实现一个物理核心对应多个逻辑核心。
# mac
# 查看CPU型号
sysctl machdep.cpu.brand_string
# 查看CPU物理核心数
sysctl hw.physicalcpu
# 查看CPU逻辑核心数
sysctl hw.logicalcpu
# linux
# 查看CPU型号
cat /proc/cpuinfo | grep "model name"
# 查看CPU物理核心数
~$ lscpu -p | egrep -v '^#' | wc -l
# 查看CPU逻辑核心数
~$ lscpu -p | egrep -v '^#' | sort -u -t, -k 2,4 | wc -l
2 Python多进程
python多线程无法利用多核优势,在执行并发任务需要使用多进程来完成
2.1 进程池
2.1.1 阻塞调用方式
调用进程会在执行p.map()时阻塞,直到所有任务执行完毕返回结果。p.map()返回的结果列表顺序和参数列表顺序一致。p.imap_unordered()返回的结果列表顺序和参数列表顺序不一致。
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
with Pool(5) as p:
print(p.map(f, [1, 2, 3])) # [1, 4, 9]
print(p.imap_unordered(f, range(10))) # arbitrary order
# 数据并行:将数据分成多个部分,分配给多个进程执行
2.1.2 非阻塞调用方式
方式一:使用apply_async
from multiprocessing import Pool, TimeoutError
import time
import os
def f(x):
return x*x
if __name__ == '__main__':
# start 4 worker processes
with Pool(processes=4) as pool:
# evaluate "f(20)" asynchronously
res = pool.apply_async(f, (20,)) # runs in *only* one process
print(res.get(timeout=1)) # prints "400"
# evaluate "os.getpid()" asynchronously
res = pool.apply_async(os.getpid, ()) # runs in *only* one process
print(res.get(timeout=1)) # prints the PID of that process
# launching multiple evaluations asynchronously *may* use more processes
multiple_results = [pool.apply_async(os.getpid, ()) for i in range(4)]
print([res.g