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




python多线程 multiprocessing教程 (5)

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

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

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

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

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

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

我在文档中找到了清楚的例子,但不是很详尽; 只要我尝试让事情变得复杂一些,就会收到很多奇怪的错误(如不能被腌渍的方法等)。

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

你能把我和一些资源联系起来解释这两个模块背后的概念,以及如何正确使用它们来完成复杂的任务吗?


Giulio Franco所说的对于多线程与一般的多处理来说是真实的。

但是,Python *还有一个额外的问题:有一个全局解释器锁,它可以防止同一进程中的两个线程同时运行Python代码。 这意味着如果您有8个内核,并且将您的代码更改为使用8个线程,则它将无法使用800%CPU并且运行速度提高8倍; 它将使用相同的100%CPU并以相同的速度运行。 (实际上,它会运行得慢一点,因为线程会带来额外的开销,即使你没有任何共享数据,但现在就忽略它。)

这也有例外。 如果你的代码繁重的计算实际上并不是在Python中发生的,但是在一些使用自定义C代码的库中可以正确处理GIL,就像一个numpy应用程序一样,你将从线程中获得预期的性能优势。 如果大量计算是由您运行并等待的某个子进程完成的,则情况也是如此。

更重要的是,有些情况下这并不重要。 例如,网络服务器花费大部分时间从网络上读取数据包,GUI应用程序大部分时间都在等待用户事件。 在网络服务器或GUI应用程序中使用线程的一个原因是允许您在不停止主线程继续服务网络数据包或GUI事件的情况下执行长时间运行的“后台任务”。 这对Python线程来说工作得很好。 (从技术角度而言,这意味着Python线程可以为您提供并发性,即使它们不会提供核心并行性。)

但是如果你用纯Python写一个CPU绑定的程序,使用更多的线程通常是没有用的。

使用单独的流程对GIL没有这样的问题,因为每个流程都有自己独立的GIL。 当然,在任何其他语言中,线程和进程之间仍然存在所有相同的折衷 - 在进程之间共享数据比线程之间共享数据更困难和更昂贵,运行大量进程或创建和销毁代价可能很高他们经常等等。但是,GIL严重依赖于对流程的平衡,而对于C或Java这种情况并非如此。 因此,您会发现自己在Python中比使用C或Java更频繁地使用多处理。

与此同时,Python的“包含电池”哲学带来了一些好消息:编写可以在线程和进程之间来回切换的代码非常容易,只需进行一次线程更改。

如果你使用独立的“作业”来设计你的代码,除了输入和输出之外,其他作业(或主程序)不共享任何东西,你可以使用concurrent.futures库在线程池中编写代码喜欢这个:

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

你甚至可以得到这些工作的结果,并将它们传递给更多的工作,等待执行顺序或完成顺序等。 有关详细信息,请阅读Future对象部分。

现在,如果你的程序总是使用100%的CPU,并且增加更多的线程会让它变慢,那么你遇到了GIL问题,所以你需要切换到进程。 你所要做的就是改变第一行:

with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

唯一真正的警告是,你的工作的论点和返回值必须是可以选择的(并且不需要太多的时间或记忆来腌制),以便可以跨进程使用。 通常这不是问题,但有时候是这样。

但是如果你的工作不能独立完成呢? 如果你可以根据将消息从一个消息传递到另一个消息的作业来设计你的代码,它仍然很容易。 您可能需要使用threading.Threadmultiprocessing.Process而不是依赖于池。 你将不得不显式创建queue.Queuemultiprocessing.Queue对象。 (还有很多其他选项 - 管道,套接字,带有群集的文件,......但问题是,如果Executor的自动魔法不足,则必须手动执行某些操作。)

但是如果你甚至不能依靠消息传递呢? 如果你需要两项工作来改变相同的结构,并且看到彼此的变化呢? 在这种情况下,您需要手动同步(锁,信号量,条件等),并且如果要使用进程,则需要显式共享内存对象来引导。 这是多线程(或多处理)变得困难的时候。 如果你能避免它,好极了; 如果你不能,你需要阅读的内容比能够放入SO答案的人更多。

从评论中,你想知道Python中的线程和进程有什么不同。 真的,如果你阅读朱利奥佛朗哥的答案和我的所有链接,这应该涵盖一切......但总结肯定会有用,所以这里有:

  1. 线程默认共享数据; 过程不。
  2. 作为(1)的结果,在进程之间发送数据通常需要酸洗和取消它。 **
  3. 作为(1)的另一个结果,在进程之间直接共享数据通常需要将其放入像Value,Array和ctypes类型这样的低级格式。
  4. 流程不受GIL限制。
  5. 在某些平台(主要是Windows)上,创建和销毁过程要昂贵得多。
  6. 对进程有一些额外的限制,其中一些在不同平台上有所不同。 详细信息请参阅编程指南
  7. threading模块没有multiprocessing模块的某些功能。 (您可以使用multiprocessing.dummy在线程之上获取大部分缺失的API,或者可以使用诸如concurrent.futures类的更高级别的模块,而不用担心它。)

*这实际上并不是Python这个语言,而是CPython,它是该语言的“标准”实现。 其他一些实现没有GIL,就像Jython一样。

**如果您使用fork启动方法进行多处理 - 您可以在大多数非Windows平台上使用这种方法,则每个子进程都会获取父进程启动时的任何资源,这可以是将数据传递给子进程的另一种方式。


下面是python 2.6.x的一些性能数据,这些数据调用了质疑线程更有效的概念,即IO限制场景中的多处理。 这些结果来自40个处理器的IBM System x3650 M4 BD。

IO绑定处理:进程池比Thread Pool执行得更好

>>> 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')

那么,大部分问题都由Giulio Franco解答。 我将进一步阐述消费者 - 生产者问题,我想这会让你走上正确的轨道,为你的解决方案使用多线程应用程序。

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

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


当Python解释器读取源文件时,它会执行其中的所有代码。

在执行代码之前,它将定义一些特殊变量。 例如,如果Python解释器将该模块(源文件)作为主程序运行,则它将特殊的__name__变量设置为具有值"__main__" 。 如果从另一个模块导入此文件,则__name__将设置为模块的名称。

在你的脚本的情况下,让我们假设它作为主要功能执行,例如你说的话

python threading_example.py

在命令行上。 设置特殊变量后,它将执行import语句并加载这些模块。 然后它将评估def块,创建一个函数对象并创建一个名为myfunction的变量,该变量指向函数对象。 然后它将读取if语句并看到__name__等于"__main__" ,因此它将执行那里显示的块。

这样做的一个原因是有时你会编写一个模块( .py文件)来直接执行它。 或者,它也可以导入并在另一个模块中使用。 通过执行主检查,您可以仅在希望将模块作为程序运行时执行该代码,而在有人只想导入模块并自行调用函数时不执行该代码。

有关其他详细信息,请参阅此页面

注意 (由Stainsor提供):如果将代码放在函数定义之前,它将在main之前执行。

print("This code executes before main.") 

def functionA():
    print("Function A")

def functionB():
    print("Function B")

if __name__ == '__main__':
    functionA()
    functionB()

如果此模块确实是主模块,则此代码会导致:

This code executes before main. 
Function A 
Function B

如果这个模块不是主要的,你得到:

This code executes before main. 




python multithreading parallel-processing multiprocessing