当前位置:首页>编程日记>正文

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

本站寻求有缘人接手,详细了解请联系站长QQ1493399855

在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管理问题,这是因为多个处理器对数据的处理会引起数据混乱 。这就会导致整个运行时间增多 。不过,假设把数据限制在每个进程内部 ,那么就可能大大的提高性能 。当然,再怎么提高也不会超过阿姆达尔法则所规定的极限值。

使用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并行化,从而提高其运行多维参数寻优的能力。


http://www.coolblog.cn/news/e4d91263d94fccfb.html

相关文章:

  • asp多表查询并显示_SpringBoot系列(五):SpringBoot整合Mybatis实现多表关联查询
  • s7day2学习记录
  • 【求锤得锤的故事】Redis锁从面试连环炮聊到神仙打架。
  • 矿Spring入门Demo
  • 拼音怎么写_老师:不会写的字用圈代替,看到孩子试卷,网友:人才
  • Linux 实时流量监测(iptraf中文图解)
  • Win10 + Python + GPU版MXNet + VS2015 + RTools + R配置
  • 美颜
  • shell访问php文件夹,Shell获取某目录下所有文件夹的名称
  • 如何优雅的实现 Spring Boot 接口参数加密解密?
  • LeCun亲授的深度学习入门课:从飞行器的发明到卷积神经网络
  • 支撑微博千亿调用的轻量级RPC框架:Motan
  • Mac原生Terminal快速登录ssh
  • 法拉利虚拟学院2010 服务器,法拉利虚拟学院2010
  • java受保护的数据与_Javascript类定义语法,私有成员、受保护成员、静态成员等介绍...
  • mysql commit 机制_1024MySQL事物提交机制
  • 2019-9
  • jquery 使用小技巧
  • 科学计算工具NumPy(3):ndarray的元素处理
  • vscode pylint 错误_将实际未错误的py库添加到pylint白名单
  • linux批量创建用户和密码
  • 工程师在工作电脑存 64G 不雅文件,被公司开除后索赔 41 万,结果…
  • js常用阻止冒泡事件
  • newinsets用法java_Java XYPlot.setInsets方法代碼示例
  • 气泡图在开源监控工具中的应用效果
  • 各类型土地利用图例_划重点!国土空间总体规划——土地利用
  • php 启动服务器监听
  • dubbo简单示例
  • Ubuntu13.10:[3]如何开启SSH SERVER服务
  • [iptables]Redhat 7.2下使用iptables实现NAT
  • 【设计模式】 模式PK:策略模式VS状态模式
  • CSS小技巧——CSS滚动条美化
  • JS实现-页面数据无限加载
  • 最新DOS大全
  • Django View(视图系统)
  • 阿里巴巴分布式服务框架 Dubbo
  • 阿里大鱼.net core 发送短信
  • Sorenson Capital:值得投资的 5 种 AI 技术
  • 程序员入错行怎么办?
  • 两张超级大表join优化
  • Arm芯片的新革命在缓缓上演
  • 第九天函数
  • Linux软件安装-----apache安装
  • HDU 5988 最小费用流
  • 《看透springmvc源码分析与实践》读书笔记一
  • nagios自写插件—check_file
  • python3 错误 Max retries exceeded with url 解决方法
  • 正式开课!如何学习相机模型与标定?(单目+双目+鱼眼+深度相机)
  • 通过Spark进行ALS离线和Stream实时推荐
  • 行为模式之Template Method模式