Python多线程辣鸡?那要怎样并行运算呢?

2017-07-01  本文已影响6968人  VellBibi

前言

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和不returnProcessPool会有很大影响,所以前后分别跑了两次

测试结果:

测试存在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操作结果

由上图可知:

继续追加实验,看看多进程和多线程下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而言:

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

上一篇下一篇

猜你喜欢

热点阅读