线程和多处理模块之间有什么区别?

时间:2013-08-07 21:37:06

标签: python multithreading parallel-processing process multiprocessing

我正在学习如何使用Python中的threadingmultiprocessing模块并行运行某些操作并加快我的代码。

我发现这很难(可能因为我没有任何理论背景)来理解threading.Thread()对象和multiprocessing.Process()对象之间的区别。

另外,我不完全清楚如何实例化一个作业队列,只有4个(例如)并行运行,而另一个等待资源在执行之前自由。

我发现文档中的示例清晰,但不是很详尽;一旦我尝试使事情复杂化,我就会收到许多奇怪的错误(比如一种无法腌制的方法,等等)。

那么,我何时应该使用threadingmultiprocessing模块?

您能否链接我一些资源,解释这两个模块背后的概念以及如何正确使用它们来完成复杂的任务?

5 个答案:

答案 0 :(得分:28)

单个进程中可以存在多个线程。 属于同一进程的线程共享相同的内存区域(可以读取和写入相同的变量,并且可以相互干扰)。 相反,不同的进程存在于不同的存储区域中,并且每个进程都有自己的变量。为了进行通信,进程必须使用其他通道(文件,管道或套接字)。

如果你想并行化计算,你可能需要多线程,因为你可能希望线程在同一个内存上合作。

谈到性能,线程比进程创建和管理更快(因为操作系统不需要分配全新的虚拟内存区域),并且线程间通信通常比进程间通信更快。但线程更难编程。线程可以相互干扰,并且可以写入彼此的内存,但这种情况发生的方式并不总是很明显(由于几个因素,主要是指令重新排序和内存缓存),因此您将需要同步原语来控制访问你的变量。

答案 1 :(得分:4)

Python文档报价

我在What is the global interpreter lock (GIL) in CPython?

上突出了有关Process vs Threads和GIL的主要Python文档报价。

进程与线程实验

我做了一些基准测试,以便更具体地显示差异。

在基准测试中,我为8 hyperthread CPU上各种线程的CPU和IO绑定工作计时。每个线程提供的工作始终是相同的,因此,更多线程意味着提供了更多的总工作。

结果是:

enter image description here

Plot data

结论:

  • 对于CPU限制的工作,多处理总是更快,这可能是由于GIL造成的。

  • 用于IO绑定工作。两者的速度完全相同

  • 线程只能扩展到大约4倍,而不是预期的8倍,因为我使用的是8个超线程计算机。

    与C POSIX CPU绑定的工作达到预期的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)。

可视化在给定时间正在运行的线程

这篇帖子https://rohanvarma.me/GIL/告诉我,只要使用target= argument of threading.Thread安排了线程,而multiprocessing.Process安排了线程,就可以运行回调。

这使我们可以准确查看每次运行哪个线程。完成此操作后,我们将看到类似的内容(我制作了此特定图表):

            +--------------------------------------+
            + Active threads / processes           +
+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
            + Time -->                             +
            +--------------------------------------+

这将表明:

  • 线程由GIL完全序列化
  • 进程可以并行运行

答案 2 :(得分:3)

我相信this link会以优雅的方式回答您的问题。

简而言之,如果你的一个子问题必须等待而另一个完成,那么多线程是好的(例如,在I / O繁重的操作中);相反,如果您的子问题可能同时发生,则建议进行多处理。但是,您不会创建比核心数更多的进程。

答案 3 :(得分:2)

这里提供了python 2.6.x的一些性能数据,这些数据调用了一个问题,即线程在IO绑定方案中的多处理性能更高。这些结果来自40个处理器的IBM System x3650 M4 BD。

IO绑定处理:进程池的性能优于线程池

>>> do_work(50, 300, 'thread','fileio')
do_work function took 455.752 ms

>>> do_work(50, 300, 'process','fileio')
do_work function took 319.279 ms

CPU绑定处理:进程池的性能优于线程池

>>> do_work(50, 2000, 'thread','square')
do_work function took 338.309 ms

>>> do_work(50, 2000, 'process','square')
do_work function took 287.488 ms

这些都不是严格的测试,但他们告诉我,与线程相比,多处理并不是完全无法解决的。

交互式python控制台中用于上述测试的代码

from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import time
import sys
import os
from glob import glob

text_for_test = str(range(1,100000))

def fileio(i):
 try :
  os.remove(glob('./test/test-*'))
 except : 
  pass
 f=open('./test/test-'+str(i),'a')
 f.write(text_for_test)
 f.close()
 f=open('./test/test-'+str(i),'r')
 text = f.read()
 f.close()


def square(i):
 return i*i

def timing(f):
 def wrap(*args):
  time1 = time.time()
  ret = f(*args)
  time2 = time.time()
  print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
  return ret
 return wrap

result = None

@timing
def do_work(process_count, items, process_type, method) :
 pool = None
 if process_type == 'process' :
  pool = Pool(processes=process_count)
 else :
  pool = ThreadPool(processes=process_count)
 if method == 'square' : 
  multiple_results = [pool.apply_async(square,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]
 else :
  multiple_results = [pool.apply_async(fileio,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]


do_work(50, 300, 'thread','fileio')
do_work(50, 300, 'process','fileio')

do_work(50, 2000, 'thread','square')
do_work(50, 2000, 'process','square')

答案 4 :(得分:-3)

嗯,朱利奥佛朗哥回答了大部分问题。我将进一步阐述消费者 - 生产者问题,我认为这将使您在使用多线程应用程序的解决方案上走上正轨。

fill_count = Semaphore(0) # items produced
empty_count = Semaphore(BUFFER_SIZE) # remaining space
buffer = Buffer()

def producer(fill_count, empty_count, buffer):
    while True:
        item = produceItem()
        empty_count.down();
        buffer.push(item)
        fill_count.up()

def consumer(fill_count, empty_count, buffer):
    while True:
        fill_count.down()
        item = buffer.pop()
        empty_count.up()
        consume_item(item)

您可以从以下位置阅读有关同步原语的更多信息:

 http://linux.die.net/man/7/sem_overview
 http://docs.python.org/2/library/threading.html

伪代码在上面。我想你应该搜索生产者 - 消费者问题以获得更多的参考。