关于多线程:多处理vs线程python

Multiprocessing vs Threading Python

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


以下是我提出的一些利弊。

多重处理赞成的意见

  • 分离内存空间
  • 代码通常很简单
  • 利用多个CPU和核心
  • 避免对cpython的gil限制
  • 消除了对同步原语的大多数需求,除非您使用共享内存(相反,它更像是IPC的通信模型)
  • 子进程是可中断/可杀死的
  • python multiprocessing模块包括有用的抽象,其接口与threading.Thread非常相似。
  • CPU绑定处理必须使用cpython

欺骗

  • 工控机有点复杂,开销更大(通信模型与共享内存/对象)
  • 更大的内存占用

螺纹加工赞成的意见

  • 轻量-内存占用低
  • 共享内存-使从另一个上下文访问状态更容易
  • 允许您轻松做出响应的用户界面
  • 正确释放gil的cpython c扩展模块将并行运行。
  • I/O绑定应用程序的绝佳选择

欺骗

  • CPython-受GIL约束
  • 不可中断/可杀死
  • 如果不遵循命令队列/消息泵模型(使用Queue模块),则必须手动使用同步原语(锁定粒度需要决策)。
  • 代码通常很难理解,也很难正确理解-竞争条件的可能性会急剧增加


threading模块使用线程,multiprocessing模块使用进程。不同之处在于,线程在相同的内存空间中运行,而进程具有独立的内存。这使得在多处理的进程之间共享对象有点困难。由于线程使用相同的内存,必须采取预防措施,否则两个线程将同时写入同一内存。这就是全局解释器锁的作用。

生成进程比生成线程慢一点。一旦他们开始跑步,就没有什么不同了。


线程的工作是使应用程序能够响应。假设您有一个数据库连接,并且需要响应用户输入。没有线程,如果数据库连接繁忙,应用程序将无法响应用户。通过将数据库连接拆分为单独的线程,您可以使应用程序更具响应性。另外,由于两个线程处于同一进程中,它们可以访问相同的数据结构—良好的性能,加上灵活的软件设计。

请注意,由于gil的原因,应用程序实际上并不是同时执行两件事情,但是我们所做的是将数据库上的资源锁放在一个单独的线程中,以便在它和用户交互之间切换CPU时间。在线程之间分配CPU时间。

多处理是指在任何给定的时间内,您确实希望完成不止一件事情的情况。假设您的应用程序需要连接到6个数据库,并对每个数据集执行复杂的矩阵转换。将每个作业放在一个单独的线程中可能会有所帮助,因为当一个连接空闲时,另一个连接可能会获得一些CPU时间,但处理不会并行进行,因为gil意味着您只使用一个CPU的资源。通过将每个作业放入一个多处理过程中,每个作业都可以在自己的CPU上运行并以最高效率运行。


关键的优势是隔离。崩溃进程不会导致其他进程崩溃,而崩溃线程可能会对其他线程造成严重破坏。


另一件没有提到的事情是,这取决于你在速度方面使用的操作系统。在Windows中,进程成本高昂,因此线程在Windows中会更好,但在UNIX中,进程比其Windows变体更快,因此在UNIX中使用进程更安全,而且生成速度更快。


其他的答案更多地集中在多线程和多处理方面,但在Python中,必须考虑全局解释器锁(GIL)。当创建了更多的线程(例如k)时,它们通常不会将性能提高k倍,因为它仍然作为单线程应用程序运行。gil是一个全局锁,它锁定所有内容,只允许使用单个内核执行单个线程。在使用C扩展(如numpy、network、i/o)的地方,性能确实有所提高,在那里完成了大量的后台工作并发布了gil。因此,当使用线程时,只有一个操作系统级线程,而python创建的伪线程完全由线程本身管理,但实际上是作为单个进程运行的。抢占发生在这些伪线程之间。如果CPU以最大容量运行,您可能需要切换到多处理。现在,对于独立的执行实例,您可以选择池。但是,在数据重叠的情况下,如果您希望进程进行通信,那么应该使用multiprocessing.Process


正如问题中提到的,在Python中进行多处理是实现真正并行性的唯一真正方法。多线程无法实现这一点,因为gil阻止线程并行运行。

因此,线程在Python中可能并不总是有用的,事实上,根据您试图实现的目标,线程甚至可能导致更差的性能。例如,如果您正在执行一个CPU绑定的任务,例如解压缩gzip文件或3D呈现(CPU密集型的任务),那么线程可能实际上会妨碍您的性能,而不是帮助您。在这种情况下,您将希望使用多处理,因为只有这个方法实际上是并行运行的,并且有助于分配手头任务的权重。这可能会产生一些开销,因为多处理涉及将脚本的内存复制到每个子进程中,这可能会导致较大应用程序出现问题。

但是,当您的任务是IO绑定时,多线程将变得非常有用。例如,如果您的大部分任务涉及等待API调用,那么您将使用多线程,因为为什么不在等待时在另一个线程中启动另一个请求,而不是让您的CPU闲置。

DR

  • 多线程是并发的,用于IO绑定任务
  • 多处理实现真正的并行性,用于CPU绑定的任务


Python文献

我在Cpython的全球翻译锁定(GIL)是什么?

过程vs thread experiments

我做了一个基准比特,以显示更多的具体差异。

在底板上,我给CPU和我在8个超线程上的各种线程工作。The work supplied is the same for each number of threads(thus more threads means more total work supplied).

The results were:

MGX1〔0〕

结论:

  • 对于CPU bound work,multirocessing is always faster,presumably due to the Gil

  • 为了我的工作。两者都是一样的速度

  • 自从我坐在8台超高速机器上以来,恐惧只会升级到4x instead of the expected 8x.

    与此相反的是,有一个C posix cpu-bound work which reaches the expected 8x speedup:what do real',user and sys mean in the output of time(1)?

    我不知道原因,一定还有别的无效的Python进入比赛。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/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.

Tested on Ubuntu 18.10,Python 3.6.7,in a lenovo thinkpad p51 laptop with CPU:intel core I7-7820HQ CPU(4 core s/8 threads),ram:2x samsung m471a2k43b1-CRC(2X 16GIB),SD:Samsung MZVLB512HAJQ-000L7(3000 MB/S).


进程可以有多个线程。这些线程可以共享内存,并且是进程中的执行单元。

进程在CPU上运行,因此线程驻留在每个进程下。流程是独立运行的单个实体。如果要在每个进程之间共享数据或状态,可以使用内存存储工具,如Cache(redis, memcache)FilesDatabase


多路处理器

  • 多处理器添加剂CPUS以增加计算功率。
  • 多个过程是相互执行的。
  • 一个过程的创建是时间消耗和资源密集。
  • 多处理器可以是对称的或不对称的。
  • The multiprocessing library in Python uses separate memory space, multiple CPU cores, bypasses GIL limitations in CPython, child processes are killable (ex. function calls in program) and is much easier to use.
  • Some caveats of the module are a larger memory footprint and IPC’s a little more complicated with more overhead.

多线程

  • 多线程创建一个单一过程的多线程以增加计算功率。
  • 一个单一工艺的多个线程同时执行。
  • 威胁的产生既节省时间又节省资源。

BLCK1/

以Python为例的多线程和多线程处理

Python3号有平行任务的发射设施。这使我们的工作容易。

它有助于提高生产力和提高生产力。

The following gives an insight:

螺纹驱动程序

ZZU1

处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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()