我试图了解多处理优于线程的优势。 我知道多处理可以解决Global Interpreter Lock问题,但是还有什么其他优点,并且线程不能做同样的事情?
#1楼
关键优势是隔离。 崩溃进程不会导致其他进程崩溃,而崩溃的进程可能会对其他线程造成严重破坏。
#2楼
threading
模块使用线程, multiprocessing
模块使用进程。 不同之处在于线程在相同的内存空间中运行,而进程具有单独的内存。 这使得在具有多处理的进程之间共享对象变得有点困难。 由于线程使用相同的内存,因此必须采取预防措施,否则两个线程将同时写入同一内存。 这就是全局解释器锁的用途。
产卵过程比产生线程慢一点。 一旦它们运行,就没有太大区别。
#3楼
另一件未提及的事情是它取决于您在速度方面使用的操作系统。 在Windows中,进程成本很高,因此在Windows中线程会更好,但是在unix进程中它们比它们的windows变体更快,因此在unix中使用进程更加安全,而且可以快速生成。
#4楼
线程的工作是使应用程序能够响应。 假设您有数据库连接,并且需要响应用户输入。 如果没有线程,如果数据库连接繁忙,应用程序将无法响应用户。 通过将数据库连接拆分为单独的线程,可以使应用程序更具响应性。 此外,由于两个线程都在同一个进程中,因此它们可以访问相同的数据结构 - 良好的性能以及灵活的软件设计。
请注意,由于GIL,应用程序实际上并没有同时执行两项操作,但我们所做的是将数据库上的资源锁定放入一个单独的线程中,以便可以在它与用户交互之间切换CPU时间。 CPU时间在线程之间得到限制。
多处理是指您确实希望在任何给定时间完成多项操作的时间。 假设您的应用程序需要连接到6个数据库并对每个数据集执行复杂的矩阵转换。 将每个作业放在一个单独的线程中可能会有所帮助,因为当一个连接空闲时,另一个可能会获得一些CPU时间,但是处理不会并行完成,因为GIL意味着您只使用一个CPU的资源。 通过将每个作业置于多处理过程中,每个作业都可以在其自己的CPU上运行并以最高效率运行。
#5楼
以下是我想出的一些优点/缺点。
多
优点
- 单独的内存空间
- 代码通常很简单
- 利用多个CPU和核心
- 避免cPython的GIL限制
- 消除对同步原语的大多数需求,除非您使用共享内存(相反,它更像是IPC的通信模型)
- 子进程是可中断/可杀死的
- Python
multiprocessing
模块包含有用的抽象,其界面非常类似于threading.Thread
- 必须使用cPython进行CPU绑定处理
缺点
- IPC更复杂,开销更大(通信模型与共享内存/对象)
- 更大的内存占用
穿线
优点
- 轻量级 - 内存占用少
- 共享内存 - 更容易从另一个上下文访问状态
- 允许您轻松制作响应式用户界面
- 正确释放GIL的cPython C扩展模块将并行运行
- I / O绑定应用程序的绝佳选择
缺点
- cPython - 受GIL限制
- 不可中断/可杀
- 如果不遵循命令队列/消息泵模型(使用
Queue
模块),则手动使用同步原语成为必需(锁定粒度需要决策) - 代码通常难以理解并且正确 - 竞争条件的可能性会急剧增加
#6楼
) has to be taken into account. 其他答案更多地关注多线程与多处理方面,但在python中必须考虑全局解释器锁( )。 ) of threads are created, generally they will not increase the performance by times, as it will still be running as a single threaded application. 当创建更多数量(比如 )的线程时,通常它们不会将性能提高倍,因为它仍将作为单线程应用程序运行。 GIL是一个全局锁,可以锁定所有内容,并且只允许单个线程执行,只使用一个内核。 在使用Numpy,Network,I / O等C扩展的地方,性能确实会增加,在那里完成了大量后台工作并发布了GIL。
is used, there is only a single operating system level thread while python creates pseudo-threads which are completely managed by threading itself but are essentially running as a single process. 因此,当使用时,只有一个操作系统级线程,而python创建伪线程,这些线程完全由线程本身管理,但基本上作为单个进程运行。 抢占发生在这些伪线程之间。 如果CPU以最大容量运行,您可能希望切换到多处理。
现在,如果是自包含的执行实例,您可以改为选择池。 但是在数据重叠的情况下,您可能希望进程通信,您应该使用multiprocessing.Process
。
#7楼
进程可能有多个线程。 这些线程可以共享内存,并且是进程内的执行单元。
进程在CPU上运行,因此线程驻留在每个进程下。 流程是独立运行的单个实体。 如果要在每个进程之间共享数据或状态,可以使用内存存储工具,如Cache(redis, memcache)
, Files
或Database
。
#8楼
正如问题中所提到的,Python中的多处理是实现真正并行性的唯一真正方法。 多线程无法实现这一点,因为GIL会阻止线程并行运行。
因此,线程在Python中可能并不总是有用,事实上,甚至可能会导致性能下降,具体取决于您要实现的目标。 例如,如果您正在执行CPU绑定任务,例如解压缩gzip文件或3D渲染(任何CPU密集型),那么线程实际上可能会妨碍您的性能而不是帮助。 在这种情况下,您可能希望使用多处理,因为此方法实际上并行运行,并有助于分配手头任务的权重。 可能会有一些开销因为多处理涉及将脚本的内存复制到每个子进程中,这可能会导致更大的应用程序出现问题。
但是,当您的任务受IO限制时, 多线程会很有用。 例如,如果您的大部分任务都涉及等待API调用 ,那么您将使用多线程,因为为什么不在等待时在另一个线程中启动另一个请求,而不是让您的CPU闲置。
TL; DR
- 多线程是并发的,用于IO绑定任务
- 多处理实现了真正的并行性,并用于CPU绑定任务
#9楼
Python文档引用
我突出了关于Process vs Threads和GIL的关键Python文档引用: CPython中的全局解释器锁(GIL)是什么?
进程与线程实验
为了更具体地展示差异,我做了一些基准测试。
在基准测试中,我为8个超线程 CPU上的各种线程数量计算了CPU和IO绑定工作。 每个线程提供的工作总是相同的,这样更多的线程意味着提供更多的工作。
结果是:
绘制数据 。
结论:
对于CPU绑定工作,多处理总是更快,可能是由于GIL
用于IO绑定工作。 两者速度完全相同
由于我使用的是8个超线程机器,因此线程只能扩展到大约4倍而不是预期的8倍。
与C POSIX CPU绑定工作形成对比,达到预期的8倍加速: “真实”,“用户”和“系统”在时间输出(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))
在带有CPU的联想ThinkPad P51笔记本电脑上测试Ubuntu 18.10,Python 3.6.7:Intel Core i7-7820HQ CPU(4核/ 8线程),RAM:2x三星M471A2K43BB1-CRC(2x 16GiB),SSD:三星MZVLB512HAJQ- 000L7(3,000 MB / s)。
可视化在给定时间运行的线程
这篇文章https://rohanvarma.me/GIL/告诉我,只要使用threading.Thread
的target=
参数调度线程,就可以运行回调,对于multiprocessing.Process
。
这允许我们确切地查看每次运行的线程。 完成后,我们会看到类似的东西(我制作了这个特定的图形):
+-----------+--------------------------------------+
|Thread 1 |******** ************ |
| 2 | ***** *************|
+-----------+--------------------------------------+
|Process 1 |*** ************** ****** **** |
| 2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
这表明:
- 线程由GIL完全序列化
- 进程可以并行运行
#10楼
多
- 多处理增加了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()
#11楼
我在大学里学到的大部分答案都是正确的。 在不同平台上的实践中(总是使用python),产生多个线程最终会产生一个进程。 区别在于多个核心共享负载,而不是只有1个核心处理100%的所有内容。 因此,如果你在4核PC上产生例如10个线程,你最终只会获得25%的cpu功率!! 如果你产生10个进程,你最终将以100%的cpu处理结果(如果你没有其他限制)。 我不是所有新技术的专家。 我回答自己的真实经验背景