热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python中的多线程求值串行和并行_Python多线程辣鸡?那要怎样并行运算呢?

前言Python在并行运算方面因为GIL(GlobalInterpreterLock,全局解释器锁)而饱受诟病,认为Python的多线程其实是伪的&#x

前言

Python在并行运算方面因为GIL(Global Interpreter Lock,全局解释器锁)而饱受诟病,认为Python的多线程其实是伪的,很鸡肋,这里就大致讲解下吧,

在Python的原始解释器CPython中存在着GIL,因此在解释执行Python代码时,会产生互斥锁来限制线程对共享资源的访问,直到解释器遇到I/O操作或者操作次数达到一定数目时才会释放GIL

所以有GIL效果就是:** 一个进程内同一时间只能允许一个线程进行运算 ** (这尼玛不就是单线程吗?)

至于为什么要有GIL?只能说这是个历史遗留问题了,人家发明Python的时候压根就没想到现在居然有多核CPU,甚至多CPU的电脑啊~

再至于为什么GIL没有被优化掉,总是有人家的考虑的,反正Python3也继续了GIL的优良传统,爱用不用,有兴趣的自行搜索GIL吧

我这里尽量用事实说话,直接黑盒测试下常见的几种并行运算方式

正文

测试环境:

电脑:

我的电脑

Python: 2.7.10

我都是使用 multiprocessing 模块进行对比,对比三种常见的使用情况,我分别取名(非官方):ThreadPool,DummyPool,ProcessPool

引入方式如下:

from multiprocessing.pool import ThreadPool

from multiprocessing.dummy import Pool as DummyPool

from multiprocessing import Pool as ProcessPool

说明: ThreadPool,DummyPool 都是线程池,ProcessPool 是进程池

测试代码如下:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# by vellhe 2017/7/1

from multiprocessing.pool import ThreadPool

from multiprocessing.dummy import Pool as DummyPool

from multiprocessing import Pool as ProcessPool

import time

max_range = 10000000

def run(i):

i = i * i

# return i # return和不return对进程池运行速度会有比较大影响,不return效率更高

def thread_pool(num):

p = ThreadPool(num)

start_time = time.time()

ret = p.map(run, range(max_range))

p.close()

p.join()

print("thread_pool %d, costTime: %fs ret.size: %d" % (num, (time.time() - start_time), len(ret)))

def dummy_pool(num):

p = DummyPool(num)

start_time = time.time()

ret = p.map(run, range(max_range))

p.close()

p.join()

print("dummy_pool %d, costTime: %fs ret.size: %d" % (num, (time.time() - start_time), len(ret)))

def process_pool(num):

p = ProcessPool(num)

start_time = time.time()

ret = p.map(run, range(max_range))

p.close()

p.join()

print("process_pool %d, costTime: %fs ret.size: %d" % (num, (time.time() - start_time), len(ret)))

if __name__ == "__main__":

for i in range(1, 9):

thread_pool(i)

dummy_pool(i)

process_pool(i)

print("=====")

测试说明:

通过并行计算max_range次对于i的二次方,没有任何IO操作,纯运算

这里特别说明,由于偶然发现run方法return和不return对ProcessPool会有很大影响,所以前后分别跑了两次

测试结果:

没有return:

没有return

从上图很容易得出以下结论:

thread_pool和dummy_pool运行速度几乎没有什么区别,因为都是线程池,而且从实现代码分析,其实dummy_pool就是thread_pool,只是套了一层壳而已

DummyPool实现

单线程运行速度比多线程要快,这就是因为python的GIL机制了,一个进程内同一时间只能允许一个线程进行运算,多线程只会让时间白白在线程间切换上了。

这时有人会说,那python的多线程不就废了,要它何用?

其实不然,这里只是做了纯运算的实验,没有任何IO,如果是高IO的话情况就不一样了,因为在等待IO完成时会去处理另外的线程,而IO往往耗时较高,所以在一些高IO情况下(如批量处理文件、网络请求、爬虫等)还是可以合理使用python的多线程的

单进程运行比单线程慢,这个也能理解,毕竟开一个线程比开一个进程要简单得多,没有资源分配等乱七八糟的东西

多进程比单进程运行快,这要是不快就奇怪了,毕竟多进程是分散在不同cpu核上跑的,这里和多线程比优势就很明显了,所以一些科学运算想要提升速度就会用多进程策略了

当多进程数够多情况下会超越多线程的速度,原因很简单,多线程并不会因为线程数增多而变快,而多进程却可以,所以超越是必然的

有return:

有return

拿这张图对比上面没有return的那张就会发现一些有意思的事情:

有return后单线程和多线程速度几乎一致了,但多进程还是比单进程要快很多,由于我电脑是4核的,所以还能大致看出,4个进程以后速度并没有提升了

有return后总体运算速度都慢了,特别是进程的速度,慢了一倍,这里原因是进程间通信耗时较大,需要把结果return到主进程中,所以做大量运算时尽量避免进程间通信

测试存在IO操作的情况

上面都是纯运算,没有IO,接下来就看看存在IO操作会是什么样吧,代码如下,在循环计算前增加request请求

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# by vellhe 2017/7/1

import time

from multiprocessing.pool import ThreadPool

from multiprocessing import Pool as ProcessPool

import requests

max_range = 50

def run(i):

requests.get("http://www.qq.com")

for x in range(10000):

i += i * x

return i

def thread_pool(num):

p = ThreadPool(num)

start_time = time.time()

ret = p.map(run, range(max_range))

p.close()

p.join()

print("thread_pool %d, costTime: %fs ret.size: %d" % (num, (time.time() - start_time), len(ret)))

def process_pool(num):

p = ProcessPool(num)

start_time = time.time()

ret = p.map(run, range(max_range))

p.close()

p.join()

print("process_pool %d, costTime: %fs ret.size: %d" % (num, (time.time() - start_time), len(ret)))

if __name__ == "__main__":

for i in range(1, 9):

thread_pool(i)

process_pool(i)

print("=====")

测试结果:

IO操作结果

由上图可知:

存在IO操作的话,python的多线程才会有用武之地,有效提升了速度

存在IO情况下,多进程效率还是会比多线程高很多

进程和线程数都是在4个后速度没有再提升了,因为我电脑是4核的

继续追加实验,看看多进程和多线程下CPU使用情况

分别做了好几次实验,惊奇的发现个很神奇的事情,不管开多少个进程或者线程,每次cpu核占用情况都是大致如下:

cpu使用情况

结果并没有出现我想像中的,单进程是一个核占用暴涨,其它核都是休息状,我也解释不了为什么了,难道是multiprocessing有优化?还是系统层做了优化?所以再做了一个实验,没有用任何进程池,直接for循环计算:

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# by vellhe 2017/7/1

import time

max_range = 100000000

def run(i):

i = i * i

return i

if __name__ == "__main__":

start_time = time.time()

for i in range(max_range):

run(i)

print("costTime: %fs" % (time.time() - start_time))

结果居然还是各个核的占用情况几乎是均匀的,所以几乎可以断定,这是系统层的优化了,所以先告一段落吧,以后再继续深究

后语

来个大致总结吧,针对python而言:

纯运算情况下单线程比多线程更快

多线程在IO操作较多情况下才能很好的发挥作用,但效率还是低于多进程

单进程运行比单线程慢,但当多进程数够多情况下会超越单线程的速度

多进程比单进程运行快

对于多进程而言,有return会比没有return慢很多很多,对于多线程却只会慢一点点

【疑惑】不管开多少个进程或者线程,各个核占用情况几乎是均匀的,猜测是系统底层有优化

ps:关于我的疑惑,知道明确结论的大侠们请给我留言,多谢



推荐阅读
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 关键词:Golang, Cookie, 跟踪位置, net/http/cookiejar, package main, golang.org/x/net/publicsuffix, io/ioutil, log, net/http, net/http/cookiejar ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 使用Spring AOP实现切面编程的步骤和注意事项
    本文介绍了使用Spring AOP实现切面编程的步骤和注意事项。首先解释了@EnableAspectJAutoProxy、@Aspect、@Pointcut等注解的作用,并介绍了实现AOP功能的方法。然后详细介绍了创建切面、编写测试代码的过程,并展示了测试结果。接着讲解了关于环绕通知的使用方法,并修改了FirstTangent类以添加环绕通知方法。最后介绍了利用AOP拦截注解的方法,只需修改全局切入点即可实现。使用Spring AOP进行切面编程可以方便地实现对代码的增强和拦截。 ... [详细]
  • Python教学练习二Python1-12练习二一、判断季节用户输入月份,判断这个月是哪个季节?3,4,5月----春 ... [详细]
  • 如何优化Webpack打包后的代码分割
    本文介绍了如何通过优化Webpack的代码分割来减小打包后的文件大小。主要包括拆分业务逻辑代码和引入第三方包的代码、配置Webpack插件、异步代码的处理、代码分割重命名、配置vendors和cacheGroups等方面的内容。通过合理配置和优化,可以有效减小打包后的文件大小,提高应用的加载速度。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有