我试图理解多处理相对于线程的优势。我知道多处理绕过了全局解释器锁,但是还有什么其他的优势,线程不能做同样的事情吗?
当前回答
以下是我想到的一些优点和缺点。
多处理
Pros
独立的内存空间 代码通常很简单 利用多个cpu和核 避免了cPython的GIL限制 消除了对同步原语的大部分需求,除非您使用共享内存(相反,它更像是IPC的通信模型) 子进程是可中断/可杀死的 Python多处理模块包含有用的抽象,其接口类似于线程。线程 必须使用cPython进行cpu绑定处理
Cons
IPC有点复杂,开销更大(通信模型vs.共享内存/对象) 更大的内存占用
线程
Pros
轻量级——低内存占用 共享内存-使访问状态从另一个上下文更容易 允许您轻松地创建响应式ui 正确释放GIL的cPython C扩展模块将并行运行 对于I/ o约束应用程序来说是一个很好的选择
Cons
cPython -服从GIL 不是可中断/ killable 如果不遵循命令队列/消息泵模型(使用queue模块),则必须手动使用同步原语(需要对锁定的粒度进行决策) 代码通常更难理解和正确编写——竞争条件的可能性急剧增加
其他回答
另一件没有提到的事情是,它取决于你使用的是什么操作系统。在Windows中,进程是昂贵的,所以线程在Windows中会更好,但在unix中,进程比它们的Windows变体更快,所以在unix中使用进程要安全得多,而且生成速度快。
以下是我想到的一些优点和缺点。
多处理
Pros
独立的内存空间 代码通常很简单 利用多个cpu和核 避免了cPython的GIL限制 消除了对同步原语的大部分需求,除非您使用共享内存(相反,它更像是IPC的通信模型) 子进程是可中断/可杀死的 Python多处理模块包含有用的抽象,其接口类似于线程。线程 必须使用cPython进行cpu绑定处理
Cons
IPC有点复杂,开销更大(通信模型vs.共享内存/对象) 更大的内存占用
线程
Pros
轻量级——低内存占用 共享内存-使访问状态从另一个上下文更容易 允许您轻松地创建响应式ui 正确释放GIL的cPython C扩展模块将并行运行 对于I/ o约束应用程序来说是一个很好的选择
Cons
cPython -服从GIL 不是可中断/ killable 如果不遵循命令队列/消息泵模型(使用queue模块),则必须手动使用同步原语(需要对锁定的粒度进行决策) 代码通常更难理解和正确编写——竞争条件的可能性急剧增加
多处理
Multiprocessing通过增加cpu来提高计算能力。 多个进程同时执行。 创建流程既耗时又耗费资源。 多处理可以是对称的也可以是非对称的。
Python中的多处理库使用独立的内存空间,多个CPU核心,绕过CPython中的GIL限制,子进程是可杀死的(例如程序中的函数调用),并且更容易使用。 该模块的一些注意事项是内存占用较大,IPC稍微复杂一些,开销更大。
多线程
多线程创建单个进程的多个线程,以提高计算能力。 一个进程的多个线程并发执行。 线程的创建在时间和资源上都是经济的。
多线程库是轻量级的,共享内存,负责响应式UI,并用于I/O绑定应用程序。 该模块不可杀死,并受GIL约束。 多个线程生活在同一个进程中的同一个空间中,每个线程将执行特定的任务,有自己的代码,自己的堆栈内存,指令指针,并共享堆内存。 如果一个线程有内存泄漏,它会损害其他线程和父进程。
使用Python的多线程和多处理示例
Python 3有启动并行任务的功能。这使我们的工作更容易。
它有线程池和进程池。
下面让我们来了解一下:
ThreadPoolExecutor例子
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
ProcessPoolExecutor
import concurrent.futures
import math
PRIMES = [
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419]
def is_prime(n):
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
Python文档引用
这个答案的规范版本现在是双重问题:线程模块和多处理模块之间有什么区别?
我已经突出显示了Python文档中关于进程vs线程和GIL的关键引用:什么是CPython中的全局解释器锁(GIL) ?
进程与线程实验
为了更具体地展示差异,我做了一些基准测试。
在基准测试中,我对8超线程CPU上不同数量的线程进行了CPU和IO限制。每个线程提供的功总是相同的,因此线程越多,提供的总功就越多。
结果如下:
图数据。
结论:
对于CPU约束的工作,多处理总是更快,可能是由于GIL IO绑定工作。两者的速度完全相同 线程只能扩展到大约4倍,而不是预期的8倍,因为我使用的是8超线程机器。 与此相比,C POSIX cpu绑定的工作达到了预期的8倍加速:'real', 'user'和'sys'在time(1)的输出中是什么意思? 我不知道这是什么原因,肯定有其他Python的低效率因素在起作用。
测试代码:
#!/usr/bin/env python3
import multiprocessing
import threading
import time
import sys
def cpu_func(result, niters):
'''
A useless CPU bound function.
'''
for i in range(niters):
result = (result * result * i + 2 * result * i * i + 3) % 10000000
return result
class CpuThread(threading.Thread):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class CpuProcess(multiprocessing.Process):
def __init__(self, niters):
super().__init__()
self.niters = niters
self.result = 1
def run(self):
self.result = cpu_func(self.result, self.niters)
class IoThread(threading.Thread):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
class IoProcess(multiprocessing.Process):
def __init__(self, sleep):
super().__init__()
self.sleep = sleep
self.result = self.sleep
def run(self):
time.sleep(self.sleep)
if __name__ == '__main__':
cpu_n_iters = int(sys.argv[1])
sleep = 1
cpu_count = multiprocessing.cpu_count()
input_params = [
(CpuThread, cpu_n_iters),
(CpuProcess, cpu_n_iters),
(IoThread, sleep),
(IoProcess, sleep),
]
header = ['nthreads']
for thread_class, _ in input_params:
header.append(thread_class.__name__)
print(' '.join(header))
for nthreads in range(1, 2 * cpu_count):
results = [nthreads]
for thread_class, work_size in input_params:
start_time = time.time()
threads = []
for i in range(nthreads):
thread = thread_class(work_size)
threads.append(thread)
thread.start()
for i, thread in enumerate(threads):
thread.join()
results.append(time.time() - start_time)
print(' '.join('{:.6e}'.format(result) for result in results))
相同目录上的GitHub上游+绘图代码。
在Ubuntu 18.10, Python 3.6.7,联想ThinkPad P51笔记本电脑上测试,CPU:英特尔酷睿i7-7820HQ CPU(4核/ 8线程),RAM: 2倍三星M471A2K43BB1-CRC(2倍16GiB), SSD:三星MZVLB512HAJQ-000L7 (3000 MB/s)。
可视化给定时间哪些线程正在运行
这篇文章https://rohanvarma.me/GIL/告诉我,你可以运行一个回调每当线程调度与目标=参数的线程。线程和multiprocessing.Process。
这允许我们准确地查看每次运行的线程。当这完成后,我们会看到(我制作了这张特殊的图表):
+--------------------------------------+
+ Active threads / processes +
+-----------+--------------------------------------+
|Thread 1 |******** ************ |
| 2 | ***** *************|
+-----------+--------------------------------------+
|Process 1 |*** ************** ****** **** |
| 2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
+ Time --> +
+--------------------------------------+
这将表明:
线程由GIL完全序列化 进程可以并行运行
线程模块使用线程,多处理模块使用进程。不同之处在于线程在相同的内存空间中运行,而进程有单独的内存。这使得在多进程之间共享对象变得有点困难。由于线程使用相同的内存,必须采取预防措施,否则两个线程将同时写入同一内存。这就是全局解释器锁的作用。
生成进程比生成线程要慢一些。
推荐文章
- 证书验证失败:无法获得本地颁发者证书
- 当使用pip3安装包时,“Python中的ssl模块不可用”
- 无法切换Python与pyenv
- Python if not == vs if !=
- 如何从scikit-learn决策树中提取决策规则?
- 为什么在Mac OS X v10.9 (Mavericks)的终端中apt-get功能不起作用?
- 将旋转的xtick标签与各自的xtick对齐
- 为什么元组可以包含可变项?
- 如何合并字典的字典?
- 如何创建类属性?
- 不区分大小写的“in”
- 在Python中获取迭代器中的元素个数
- 自动化invokerrequired代码模式
- 解析日期字符串并更改格式
- 使用try和。Python中的if