我想了解multiprocessing优于threading的优势。我知道多处理绕过Global Interpreter Lock,但还有其他哪些优点,线程可以做同样的事情吗?
答案 0 :(得分:739)
以下是我提出的一些优点/缺点。
multiprocessing
模块包含有用的抽象,界面很像threading.Thread
Queue
模块),则手动使用同步原语成为必需(锁定粒度需要做出决定)答案 1 :(得分:599)
threading
模块使用线程,multiprocessing
模块使用进程。不同之处在于线程在相同的内存空间中运行,而进程具有单独的内存。这使得在具有多处理的进程之间共享对象变得有点困难。由于线程使用相同的内存,因此必须采取预防措施,否则两个线程将同时写入同一内存。这就是全局解释器锁的用途。
产生过程比产生线程慢一点。一旦它们运行,就没有太大区别了。
答案 2 :(得分:189)
线程的工作是使应用程序能够响应。假设您有数据库连接,并且需要响应用户输入。如果没有线程,如果数据库连接繁忙,应用程序将无法响应用户。通过将数据库连接拆分为单独的线程,可以使应用程序更具响应性。此外,由于两个线程都在同一个进程中,因此它们可以访问相同的数据结构 - 良好的性能以及灵活的软件设计。
请注意,由于GIL,应用程序实际上并没有同时执行两项操作,但我们所做的是将数据库上的资源锁定放到一个单独的线程中,以便可以在它和它之间切换CPU时间。用户互动。 CPU时间在线程之间得到限制。
多次处理适用于您确实希望在任何给定时间完成多项操作的时间。假设您的应用程序需要连接到6个数据库并对每个数据集执行复杂的矩阵转换。将每个作业放在一个单独的线程中可能会有所帮助,因为当一个连接空闲时,另一个可能会获得一些CPU时间,但是处理不会并行完成,因为GIL意味着您只使用一个CPU的资源。通过将每个作业置于多处理过程中,每个作业都可以在其自己的CPU上运行并以最高效率运行。
答案 3 :(得分:39)
关键优势是隔离。崩溃进程不会导致其他进程崩溃,而崩溃的进程可能会对其他进程造成严重破坏。
答案 4 :(得分:26)
另一件未提及的事情是,它取决于您在速度方面使用的操作系统。在Windows中,流程成本很高,因此在Windows中线程会更好,但是在unix进程中它们比它们的windows变体更快,因此在unix中使用进程更加安全,而且可以快速生成。
答案 5 :(得分:17)
其他答案更多地关注多线程与多处理方面,但在python Global Interpreter Lock( GIL )中必须考虑到。当创建更多数量(例如 k )的线程时,通常它们不会将性能提高 k 次,因为它仍将作为单线程应用程序运行。 GIL是一个全局锁,可以锁定所有内容,并且只允许单个线程执行,只使用一个内核。在使用Numpy,Network,I / O等C扩展的地方,性能确实会增加,在那里完成了大量后台工作并发布了GIL。
因此,当使用线程时,只有一个操作系统级线程,而python创建伪线程,这些线程完全由线程本身管理,但基本上作为单个进程运行。抢占发生在这些伪线程之间。如果CPU以最大容量运行,您可能希望切换到多处理
现在,如果是自包含的执行实例,您可以改为选择池。但是,如果数据重叠,您可能希望进程通信,则应使用multiprocessing.Process
。
答案 6 :(得分:10)
正如问题中提到的那样,Python中的 Multiprocessing 是实现真正的并行性的唯一真正方法。 多线程无法实现此目的,因为 GIL 阻止线程并行运行。
结果,线程化在Python中可能并不总是有用,实际上,根据您要实现的目标,甚至可能导致性能下降。例如,如果您正在执行 CPU绑定任务,例如解压缩gzip文件或3D渲染(任何占用CPU的资源),那么线程实际上可能会阻碍性能而不是帮助。在这种情况下,您将要使用 Multiprocessing ,因为只有这种方法实际上可以并行运行,并且有助于分配手头任务的重量。由于 Multiprocessing 涉及将脚本的内存复制到每个子进程中,这可能会导致大型应用程序出现问题。
但是,当您的任务受 IO约束时,多线程会很有用。例如,如果您的大部分任务都涉及等待 API调用,那么您将使用 Multithreading ,因为为什么在等待时不启动另一个线程中的另一个请求,而不是您的CPU无所事事。
TL; DR
答案 7 :(得分:8)
Python文档报价
我在What is the global interpreter lock (GIL) in CPython?
处突出了有关Process vs Threads和GIL的主要Python文档报价。进程与线程实验
我做了一些基准测试,以便更具体地显示差异。
在基准测试中,我为8 hyperthread CPU上各种线程的CPU和IO绑定工作计时。每个线程提供的工作始终是相同的,因此,更多线程意味着提供了更多的总工作。
结果是:
结论:
对于CPU限制的工作,多处理总是更快,这可能是由于GIL造成的。
用于IO绑定工作。两者的速度完全相同
线程只能扩展到大约4倍,而不是预期的8倍,因为我在8台超线程计算机上。
与C POSIX绑定的工作达到了预期的8倍加速的对比:What do 'real', 'user' and 'sys' mean in the output of time(1)?
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 upstream + plotting code on same directory。
在带有CPU的Lenovo ThinkPad P51笔记本电脑上的Ubuntu 18.10,Python 3.6.7上进行了测试:Intel Core i7-7820HQ CPU(4核/ 8线程),RAM:2x Samsung M471A2K43BB1-CRC(2x 16GiB),SSD:三星MZVLB512HAJQ-000L7(3,000 MB / s)。
答案 8 :(得分:2)
进程可能有多个线程。这些线程可以共享内存,并且是进程内的执行单元。
进程在CPU上运行,因此线程驻留在每个进程下。流程是独立运行的单个实体。如果要在每个流程之间共享数据或状态,可以使用内存存储工具,例如Cache(redis, memcache)
,Files
或Database
。
答案 9 :(得分:1)
多处理
- Python中的多处理库使用单独的内存空间,使用多个CPU内核,绕过CPython中的GIL限制,子进程是可终止的(例如程序中的函数调用)并且易于使用。
- 该模块的一些警告是较大的内存占用,而IPC则稍微复杂一些,而且开销更多。
多重阅读
- 多线程库是轻量级的,共享内存,负责响应UI,并且非常适合I / O绑定的应用程序。
- 该模块不可杀,并且要遵守GIL。
- 多个线程位于同一空间的同一进程中,每个线程将执行特定的任务,具有自己的代码,自己的堆栈内存,指令指针和共享堆内存。
- 如果某个线程发生内存泄漏,则可能会损坏其他线程和父进程。
使用Python的多线程和多处理示例
Python 3具有Launching parallel tasks的功能。这使我们的工作更加轻松。
它具有thread pooling和Process pooling的名称。
以下内容提供了一个见解:
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()
答案 10 :(得分:0)
在我上大学时,上面的大多数答案都是正确的。在不同平台上的PRACTICE(始终使用python)中,产生多个线程的结果类似于产生一个进程。区别在于多个内核共享负载,而不是只有1个内核以100%的速度处理所有内容。因此,如果您在4核PC上生成了10个线程,最终将只能获得25%的CPU功耗!而且,如果您生成10个进程,则最终将以100%的cpu处理(如果您没有其他限制)。我不是所有新技术的专家。我有自己的实际经验背景回答