多处理与线程 Python [重复]
- 2024-11-21 08:34:00
- admin 原创
- 5
问题描述:
我试图了解多处理相对于线程的优势。我知道多处理可以绕过全局解释器锁,但是还有什么其他优势,线程不能做同样的事情吗?
解决方案 1:
以下是我认为的一些优点/缺点。
多处理
优点
独立内存空间
代码通常很简单
充分利用多个 CPU 和核心
避免 cPython 的 GIL 限制
除非使用共享内存,否则消除了对同步原语的大多数需求(相反,它更像是 IPC 的通信模型)
子进程可中断/可终止
Python
multiprocessing
模块包含一些有用的抽象,其接口类似于threading.Thread
必须使用 cPython 进行 CPU 密集型处理
缺点
IPC 稍微复杂一些,开销也更大(通信模型与共享内存/对象)
更大的内存占用
线程
优点
轻量级——低内存占用
共享内存——使从另一个上下文访问状态更加容易
让您轻松制作响应式 UI
正确释放 GIL 的 cPython C 扩展模块将并行运行
I/O 受限应用程序的绝佳选择
缺点
cPython - 受 GIL 约束
不可中断/不可终止
如果不遵循命令队列/消息泵模型(使用
Queue
模块),则手动使用同步原语将成为必要(需要确定锁定的粒度)代码通常更难理解和正确——竞争条件的可能性急剧增加
解决方案 2:
模块threading
使用线程,multiprocessing
模块使用进程。区别在于线程在相同的内存空间中运行,而进程拥有单独的内存。这使得在多处理进程之间共享对象变得有点困难。由于线程使用相同的内存,因此必须采取预防措施,否则两个线程将同时写入同一内存。这就是全局解释器锁的用途。
生成进程比生成线程要慢一点。
解决方案 3:
线程的作用是使应用程序具有响应能力。假设您有一个数据库连接,并且需要响应用户输入。如果没有线程,如果数据库连接繁忙,应用程序将无法响应用户。通过将数据库连接拆分为单独的线程,您可以使应用程序更具响应能力。此外,由于两个线程都在同一个进程中,它们可以访问相同的数据结构 - 性能良好,软件设计灵活。
请注意,由于 GIL,应用程序实际上不会同时执行两件事,但我们所做的是将数据库上的资源锁放入单独的线程中,以便 CPU 时间可以在它和用户交互之间切换。CPU 时间在线程之间分配。
多处理适用于您确实希望在任意给定时间完成多件事的情况。假设您的应用程序需要连接到 6 个数据库并对每个数据集执行复杂的矩阵转换。将每个作业放在单独的线程中可能会有所帮助,因为当一个连接空闲时,另一个连接可以获得一些 CPU 时间,但处理不会并行完成,因为 GIL 意味着您只使用一个 CPU 的资源。通过将每个作业放在多处理进程中,每个作业都可以在自己的 CPU 上运行并以最高效率运行。
解决方案 4:
Python 文档引述
这个答案的规范版本现在是重复问题:线程和多处理模块之间有什么区别?
我已经在以下位置突出显示了有关进程、线程和 GIL 的关键 Python 文档引述:CPython 中的全局解释器锁 (GIL) 是什么?
进程与线程实验
我做了一些基准测试,以便更具体地显示差异。
在基准测试中,我为8 个超线程CPU 上不同数量的线程计时了 CPU 和 IO 绑定工作。每个线程提供的工作始终相同,因此线程越多意味着提供的总工作量越多。
结果是:
绘制数据。
结论:
对于 CPU 密集型工作,多处理总是更快,大概是因为 GIL
对于 IO 绑定工作。两者的速度完全相同
由于我使用的是 8 超线程机器,因此线程只能扩大到大约 4 倍而不是预期的 8 倍。
与达到预期 8 倍加速的 C POSIX CPU 绑定工作进行对比:time(1) 输出中的“real”、“user”和“sys”是什么意思?
TODO:我不知道这个原因,肯定有其他 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:Intel Core i7-7820HQ CPU(4 核/8 线程),RAM:2x Samsung M471A2K43BB1-CRC(2x 16GiB),SSD:Samsung MZVLB512HAJQ-000L7(3,000 MB/s)。
可视化在给定时间内正在运行的线程
这篇文章https://rohanvarma.me/GIL/告诉我,每当一个线程被调度时,你都可以运行一个回调,并且target=
参数threading.Thread
也是一样multiprocessing.Process
。
这样我们就可以准确查看每次运行的线程。完成后,我们会看到类似这样的内容(我制作了这个特定的图表):
+--------------------------------------+
+ Active threads / processes +
+-----------+--------------------------------------+
|Thread 1 |******** ************ |
| 2 | ***** *************|
+-----------+--------------------------------------+
|Process 1 |*** ************** ****** **** |
| 2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
+ Time --> +
+--------------------------------------+
这表明:
线程完全由 GIL 序列化
进程可以并行运行
解决方案 5:
关键优势在于隔离。崩溃的进程不会导致其他进程崩溃,而崩溃的线程可能会对其他线程造成严重破坏。
解决方案 6:
正如问题中提到的, Python 中的多处理是实现真正并行的唯一真正方法。多线程无法实现这一点,因为GIL阻止线程并行运行。
因此,线程在 Python 中可能并不总是有用,事实上,根据您想要实现的目标,甚至可能导致更差的性能。例如,如果您正在执行CPU 密集型任务,如解压缩 gzip 文件或 3D 渲染(任何 CPU 密集型任务),那么线程实际上可能会阻碍您的性能而不是帮助。在这种情况下,您可能希望使用多处理,因为只有这种方法才能真正并行运行并有助于分配手头任务的权重。这可能会有一些开销,因为多处理涉及将脚本的内存复制到每个子进程中,这可能会给较大尺寸的应用程序带来问题。
但是,当您的任务是IO 密集型时,多线程就变得有用了。例如,如果您的大多数任务涉及等待API 调用,那么您将使用多线程,因为为什么不在等待时在另一个线程中启动另一个请求,而不是让您的 CPU 闲置。
总结
多线程是并发的,用于IO 密集型任务
多处理实现真正的并行,用于CPU 密集型任务
解决方案 7:
另一件未提及的事情是,速度取决于您使用的操作系统。在 Windows 中,进程成本高昂,因此在 Windows 中使用线程会更好,但在 Unix 中,进程比 Windows 变体更快,因此在 Unix 中使用进程更安全,而且生成速度更快。
解决方案 8:
其他答案更多地关注多线程与多处理方面,但在 python 中,必须考虑全局解释器锁 ( GIL )。当创建更多 (例如k ) 个线程时,它们通常不会将性能提高k倍,因为它仍将作为单线程应用程序运行。 GIL 是一个全局锁,它将所有内容锁定在外,只允许使用单个核心的单线程执行。在使用 C 扩展(如 numpy、Network、I/O)的地方,性能确实会提高,在这些地方,完成了大量后台工作并释放了 GIL。
因此,当使用线程时,只有一个操作系统级线程,而 python 会创建伪线程,这些伪线程完全由线程本身管理,但本质上作为单个进程运行。抢占发生在这些伪线程之间。如果 CPU 以最大容量运行,您可能需要切换到多处理。
现在,在自包含执行实例的情况下,您可以选择池。但在数据重叠的情况下,您可能希望进程进行通信,您应该使用multiprocessing.Process
。
解决方案 9:
多处理
多处理增加 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)))
进程池执行器
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()
解决方案 10:
线程共享相同的内存空间,以保证两个线程不共享相同的内存位置,因此必须采取特殊的预防措施,CPython 解释器使用一种称为的机制GIL
或全局解释器锁来处理这个问题
什么是 GIL(我只是想澄清一下GIL,它上面重复了)?
在 CPython 中,全局解释器锁(GIL)是一个互斥锁,用于保护对 Python 对象的访问,防止多个线程同时执行 Python 字节码。此锁是必需的,主要是因为 CPython 的内存管理不是线程安全的。
对于主要问题,我们可以使用用例进行比较,如何比较?
1-线程用例:对于 GUI 程序,线程可用于使应用程序响应迅速。例如,在文本编辑程序中,一个线程可以负责记录用户输入,另一个线程可以负责显示文本,第三个线程可以进行拼写检查,等等。此时,程序必须等待用户交互。这是最大的瓶颈。线程的另一个用例是 IO 受限或网络受限的程序,例如网络爬虫。
2-多处理的用例:当程序是 CPU 密集型的并且不需要进行任何 IO 或用户交互时,多处理比线程更出色。
有关更多详细信息,请访问此链接和链接,或者如果您需要深入了解线程,请访问此处,有关多处理,请访问此处
解决方案 11:
进程可能有多个线程。这些线程可以共享内存,并且是进程内的执行单位。
进程在 CPU 上运行,因此线程驻留在每个进程下。进程是独立运行的单独实体。如果您想在每个进程之间共享数据或状态,您可以使用内存存储工具,例如Cache(redis, memcache)
、Files
或Database
。
解决方案 12:
正如我在大学学到的,上面的大多数答案都是正确的。在不同平台上的实践中(总是使用 python),生成多个线程最终就像生成一个进程一样。不同之处在于多个核心共享负载,而不是只有 1 个核心以 100% 的速度处理所有事情。因此,如果您在 4 核 PC 上生成例如 10 个线程,您最终将只获得 25% 的 CPU 功率!!如果您生成 10 个进程,那么您最终将以 100% 的速度处理 CPU(如果您没有其他限制)。我并不是所有新技术的专家。我是根据自己的实际经验背景来回答的
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件