`

【转发】Python 并发编程之使用多线程和多处理器

 
阅读更多

原文地址:http://developer.51cto.com/art/201405/438178.htm

在Python编码中我们经常讨论的一个方面就是如何优化模拟执行的性能。尽管在考虑量化代码时NumPy、SciPy和pandas在这方面已然非常有用,但在构建事件驱动系统时我们无法有效地使用这些工具。有没有可以加速我们代码的其他办法?答案是肯定的,但需要留意!

在这篇文章中,我们看一种不同的模型-并发,我们可以将它引入我们Python程序中。这种模型在模拟中工作地特别好,它不需要共享状态。Monte Carlo模拟器可以用来做期权定价以及检验算法交易等类型的各种参数的模拟。

我们将特别考虑Threading库和Multiprocessing库。

Python并发

当Python初学者探索多线程的代码为了计算密集型优化时,问得最多的问题之一是:”当我用多线程的时候,为什么我的程序变慢了?“

在多核机器上,我们期望多线程的代码使用额外的核,从而提高整体性能。不幸的是,主Python解释器(CPython)的内部并不是真正的多线程,是通过一个全局解释锁(GIL)来进行处理的。

GIL是必须的,因为Python解释器是非线程安全的。这意味着当从线程内尝试安全的访问Python对象的时候将有一个全局的强制锁。在任何时候,仅仅一个单一的线程能够获取Python对象或者C API。每100个字节的Python指令解释器将重新获取锁,这(潜在的)阻塞了I/0操作。因为锁,CPU密集型的代码使用线程库时,不会获得性能的提高,但是当它使用多处理库时,性能可以获得提高。

并行库的实现

现在,我们将使用上面所提到的两个库来实现对一个“小”问题进行并发优化。

线程库

上面我们提到: 运行CPython解释器的Python不会支持通过多线程来实现多核处理。不过,Python确实有一个线程库。那么如果我们(可能)不能使用多个核心进行处理,那么使用这个库能取得什么好处呢?

许多程序,尤其是那些与网络通信或者数据输入/输出(I/O)相关的程序,都经常受到网络性能或者输入/输出(I/O)性能的限制。这样Python解释器就会等待哪些从诸如网络地址或者硬盘等“远端”数据源读写数据的函数调用返回。因此这样的数据访问比从本地内存或者CPU缓冲区读取数据要慢的多。

因此,如果许多数据源都是通过这种方式访问的,那么就有一种方式对这种数据访问进行性能提高,那就是对每个需要访问的数据项都产生一个线程 。

举个例子,假设有一段Python代码,它用来对许多站点的URL进行扒取。再假定下载每个URL所需时间远远超过计算机CPU对它的处理时间,那么仅使用一个线程来实现就会大大地受到输入/输出(I/O)性能限制。

通过给每个下载资源生成一个新的线程,这段代码就会并行地对多个数据源进行下载,在所有下载都结束的时候再对结果进行组合。这就意味着每个后续下载都不会等待前一个网页下载完成。此时,这段代码就受收到客户/服务端带宽的限制。

不过,许多与财务相关的应用都受到CPU性能的限制,这是因为这样的应用都是高度集中式的对数字进行处理。这样的应用都会进行大型线性代数计算或者数值的随机统计,比如进行蒙地卡罗模拟统计。所以只要对这样的应用使用Python和全局解释锁(GIL),此时使用Python线程库就不会有任何性能的提高。

Python实现

下面这段依次添加数字到列表的“玩具”代码,举例说明了多线程的实现。每个线程创建一个新的列表并随机添加一些数字到列表中。这个已选的“玩具”例子对CPU的消耗非常高。

下面的代码概述了线程库的接口,但是他不会比我们用单线程实现的速度更快。当我们对下面的代码用多处理库时,我们将看到它会显著的降低总的运行时间。

让我们检查一下代码是怎样工作的。首先我们导入threading库。然后我们创建一个带有三个参数的函数list_append。第一个参数count定义了创建列表的大小。第二个参数id是“工作”(用于我们输出debug信息到控制台)的ID。第三个参数out_list是追加随机数的列表。

__main__函数创建了一个107的size,并用两个threads执行工作。然后创建了一个jobs列表,用于存储分离的线程。threading.Thread对象将list_append函数作为参数,并将它附加到jobs列表。

最后,jobs分别开始并分别“joined”。join()方法阻塞了调用的线程(例如主Python解释器线程)直到线程终止。在打印完整的信息到控制台之前,确认所有的线程执行完成。

  1. # thread_test.pyimport randomimport threadingdef list_append(count, id, out_list):  
  2.     """  
  3.     Creates an empty list and then appends a   
  4.     random number to the list 'count' number  
  5.     of times. A CPU-heavy operation!  
  6.     """ 
  7.     for i in range(count):  
  8.         out_list.append(random.random())if __name__ == "__main__":  
  9.     size = 10000000   # Number of random numbers to add  
  10.     threads = 2   # Number of threads to create  
  11.  
  12.     # Create a list of jobs and then iterate through  
  13.     # the number of threads appending each thread to  
  14.     # the job list   
  15.     jobs = []  
  16.     for i in range(0, threads):  
  17.         out_list = list()  
  18.         thread = threading.Thread(target=list_append(size, i, out_list))  
  19.         jobs.append(thread)  
  20.  
  21.     # Start the threads (i.e. calculate the random number lists)  
  22.     for j in jobs:  
  23.         j.start()  
  24.  
  25.     # Ensure all of the threads have finished  
  26.     for j in jobs:  
  27.         j.join()  
  28.  
  29.     print "List processing complete." 

我们能在控制台中调用如下的命令time这段代码

  1. time python thread_test.py 

将产生如下的输出

  1. List processing complete.  
  2. real    0m2.003s 
  3. user    0m1.838s 
  4. sys     0m0.161s 

注意user时间和sys时间相加大致等于real时间。这表明我们使用线程库没有获得性能的提升。我们期待real时间显著的降低。在并发编程的这些概念中分别被称为CPU时间和挂钟时间(wall-clock time)

多进程处理库
 

为了充分地使用所有现代处理器所能提供的多个核心 ,我们就要使用多进程处理库 。它的工作方式与线程库完全不同 ,不过两种库的语法却非常相似 。

多进程处理库事实上对每个并行任务都会生成多个操作系统进程。通过给每个进程赋予单独的Python解释器和单独的全局解释锁(GIL)十分巧妙地规避了一个全局解释锁所带来的问题。而且每个进程还可独自占有一个处理器核心,在所有进程处理都结束的时候再对结果进行重组。

不过也存在一些缺陷。生成许多进程就会带来很多I/O管理问题,这是因为多个处理器对数据的处理会引起数据混乱 。这就会导致整个运行时间增多 。不过,假设把数据限制在每个进程内部 ,那么就可能大大的提高性能 。当然,再怎么提高也不会超过阿姆达尔法则所规定的极限值。

Python实现

使用Multiprocessing实现仅仅需要修改导入行和multiprocessing.Process行。这里单独的向目标函数传参数。除了这些,代码几乎和使用Threading实现的一样:

  1. # multiproc_test.pyimport randomimport multiprocessingdef list_append(count, id, out_list):  
  2.     """  
  3.     Creates an empty list and then appends a   
  4.     random number to the list 'count' number  
  5.     of times. A CPU-heavy operation!  
  6.     """ 
  7.     for i in range(count):  
  8.         out_list.append(random.random())if __name__ == "__main__":  
  9.     size = 10000000   # Number of random numbers to add  
  10.     procs = 2   # Number of processes to create  
  11.  
  12.     # Create a list of jobs and then iterate through  
  13.     # the number of processes appending each process to  
  14.     # the job list   
  15.     jobs = []  
  16.     for i in range(0, procs):  
  17.         out_list = list()  
  18.         process = multiprocessing.Process(target=list_append,   
  19.                                           args=(size, i, out_list))  
  20.         jobs.append(process)  
  21.  
  22.     # Start the processes (i.e. calculate the random number lists)        
  23.     for j in jobs:  
  24.         j.start()  
  25.  
  26.     # Ensure all of the processes have finished  
  27.     for j in jobs:  
  28.         j.join()  
  29.  
  30.     print "List processing complete." 

控制台测试运行时间:

  1. time python multiproc_test.py 

得到如下输出:

  1. List processing complete.  
  2. real    0m1.045s 
  3. user    0m1.824s 
  4. sys     0m0.231s 

在这个例子中可以看到user和sys时间基本相同,而real下降了近两倍。之所以会这样是因为我们使用了两个进程。扩展到四个进程或者将列表的长度减半结果如下(假设你的电脑至少是四核的):

  1. List processing complete.  
  2. real    0m0.540s 
  3. user    0m1.792s 
  4. sys     0m0.269s 

使用四个进程差不多提高了3.8倍速度。但是,在将这个规律推广到更大范围,更复杂的程序上时要小心。数据转换,硬件cacha层次以及其他一些问题会减弱加快的速度。

在下一篇文章中我们会将Event-Driben Basketer并行化,从而提高其运行多维参数寻优的能力。

分享到:
评论

相关推荐

    Python高级编程和异步IO并发编程

    最后,异步IO(非阻塞IO)和协程是Python并发编程的高级主题。Python的asyncio库提供了一种事件驱动的编程模型,通过async/await关键字实现协程,可以实现高效的并发执行,特别适用于I/O密集型任务。理解事件循环、...

    python并发编程资料合集

    总的来说,Python并发编程资料合集应该包含了对以上概念的详细讲解,包括如何使用asyncio、线程和进程,以及如何利用Pool类进行任务调度。通过学习这些内容,开发者能够编写出高效、可扩展的并发程序,应对各种复杂...

    Python并发技术实现:多线程、多进程(实例爬虫代码)中文PDF合集版最新版本

    Python多进程并发与多线程主要介绍了Python多进程并发与多线程并发编程,结合实例形式总结分析了Python编程中的多进程并发与多线程并发相关概念、使用方法与操作注意事项。另外包含Python多线程、异步+多进程爬虫...

    Python并发编程GPU

    "Python并发编程GPU"这个主题涉及到如何利用Python的库和框架来实现GPU的并行计算,以便高效地处理大量数据和复杂的数学运算。 并发编程是指在同一时间执行多个任务或子任务,以提高程序的执行效率。在Python中,...

    C语言中文网python并发编程教程

    Python并发编程是一种高效的编程方式,它可以使程序更加快速、可靠和高效。通过使用多进程和多线程,Python可以同时执行多个任务,提高程序的执行速度和效率。 进程和线程 在Python中,进程和线程是两个不同的...

    python并发编程基础

    Python并发编程是Python编程语言中的一个重要领域,它涉及到如何在单个程序中同时处理多个任务。这通常是通过线程(threads)和进程(processes)实现的,它们允许程序并行执行,从而提高效率和响应速度。在多核...

    python多线程编程.rar

    总之,理解并熟练掌握Python的多线程编程,特别是`thread`、`threading`和`Queue`库的使用,是提升Python程序性能和响应能力的重要手段。在设计多线程程序时,应合理选择线程同步机制,充分利用队列进行线程间通信,...

    Python技术并发编程指南.docx

    Python并发编程是提升程序效率和性能的关键技术,它允许程序同时执行多个任务,特别是在处理I/O密集型和计算密集型任务时。Python提供了多种并发模型,包括多线程、多进程和协程。 多线程编程是通过Python的`...

    python 多线程编程

    Python的多线程编程是开发高效并发应用的关键技术之一。在Python中,多线程允许程序同时执行多个任务,这可以充分利用多核处理器的计算能力。然而,由于GIL(全局解释器锁)的存在,Python的多线程在CPU密集型任务上...

    Python异步编程详解【305263】回顾多线程,多进程,生成器概念.zip

    本文将深入探讨Python中的异步编程概念,包括回顾多线程、多进程以及生成器,并结合提供的课件和代码示例进行详细解释。 首先,多线程是并发执行任务的一种方式。在Python中,`threading`模块提供了创建和管理线程...

    Python技术的并发编程实践指南.docx

    Python并发编程是提升软件效率和响应能力的关键技术,它涵盖了多线程、多进程和协程等多种并发模型。本文详细探讨了这些技术的实践指南。 首先,了解并发编程的概念至关重要。并发编程允许在同一时间片内执行多个...

    Python技术如何进行并发编程.docx

    ### Python并发编程技术详解 随着计算机硬件性能的不断提升,尤其是多核处理器的普及,软件并发编程成为了提升程序性能的...通过不断实践和探索,开发者可以更好地掌握Python并发编程技术,为用户提供更高质量的服务。

    Python高级编程

    "Python高级编程"涵盖了Python语言的深入理解和高级用法,这包括但不限于元编程、装饰器、生成器、协程、多线程与多进程、高级数据结构、错误处理、模块化编程、性能优化以及Python在大型项目中的应用。 1. **元...

    python多线程并发及测试框架案例

    总结来说,本文通过实例展示了Python中如何使用多线程实现并发执行,以及如何构建简单的并发测试框架。了解这些知识对于开发高效、可扩展的Python程序至关重要。同时,它也提醒我们在实际编程中要考虑Python的GIL...

    Python-python多线程函数库vthread简而强大

    与Python标准库中的`threading`相比,vthread库强调的是最小化代码改动就能实现多线程和线程池功能,这使得开发者无需对原始代码进行大规模重构,只需少量的代码调整就能利用多核处理器的优势,提高程序的并发执行...

    [Python高级编程]

    在并发处理方面,Python提供了多种方式来实现多线程和多进程。尽管Python的全局解释器锁(GIL)限制了多线程的并行性,但通过多进程和异步I/O(如`asyncio`库)可以有效地利用多核处理器资源。书中会讲解如何在...

    Python技术如何进行多线程编程.docx

    多线程编程可以让服务器程序更好地利用多核处理器的能力,提高服务的响应速度和吞吐量。 #### 四、多线程编程的注意事项 在实际应用多线程编程的过程中,还需要注意以下几点: 1. **线程安全性**:多线程环境下,...

    Python并行编程 中文版

    8. Python在并行世界的应用:Python提供了多种库和工具,用于实现并行计算,如多线程和多进程模块。 9. 线程和进程概念:线程是操作系统能够进行运算调度的最小单位,进程则是系统进行资源分配和调度的一个独立单位...

    基于Linux的python多线程爬虫程序设计.zip

    在Python编程领域,多线程爬虫是一种高效的数据抓取技术,尤其在处理大量网页时,可以显著提高爬取速度。本主题“基于Linux的Python多线程爬虫程序设计”将深入探讨如何在Linux环境下利用Python实现多线程爬虫,以...

Global site tag (gtag.js) - Google Analytics