多处理与线程 Python [重复]

2024-11-21 08:34:00
admin
原创
6
摘要:问题描述:我试图了解多处理相对于线程的优势。我知道多处理可以绕过全局解释器锁,但是还有什么其他优势,线程不能做同样的事情吗?解决方案 1:以下是我认为的一些优点/缺点。多处理优点独立内存空间代码通常很简单充分利用多个 CPU 和核心避免 cPython 的 GIL 限制除非使用共享内存,否则消除了对同步原语的...

问题描述:

我试图了解多处理相对于线程的优势。我知道多处理可以绕过全局解释器锁,但是还有什么其他优势,线程不能做同样的事情吗?


解决方案 1:

以下是我认为的一些优点/缺点。

多处理

优点

  • 独立内存空间

  • 代码通常很简单

  • 充分利用多个 CPU 和核心

  • 避免 cPython 的 GIL 限制

  • 除非使用共享内存,否则消除了对同步原语的大多数需求(相反,它更像是 IPC 的通信模型)

  • 子进程可中断/可终止

  • Pythonmultiprocessing模块包含一些有用的抽象,其接口类似于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)FilesDatabase

解决方案 12:

正如我在大学学到的,上面的大多数答案都是正确的。在不同平台上的实践中(总是使用 python),生成多个线程最终就像生成一个进程一样。不同之处在于多个核心共享负载,而不是只有 1 个核心以 100% 的速度处理所有事情。因此,如果您在 4 核 PC 上生成例如 10 个线程,您最终将只获得 25% 的 CPU 功率!!如果您生成 10 个进程,那么您最终将以 100% 的速度处理 CPU(如果您没有其他限制)。我并不是所有新技术的专家。我是根据自己的实际经验背景来回答的

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用