| by 鲍 建伟 | No comments

Python的GIL和多进程研究(1)

多线程多进程和协程,是Python中入门时候遇到的几个小坑,一般来说,我们想让代码同时执行多个任务,比如一边爬取某些网页的图片的URL插到队列内,一边让数据库从队列里读取URL入库,一边用下载器下载队列内的URL链接。这时候如果顺序执行就会因为下载图片和入库操作导致程序变的很慢,这时候我们会有三种方法操作:

1.多进程
2.多线程
3.多进程,每个进程下多个线程

但是啊,提到线程和进程,那就绕不开GIL,什么是GIL呢?GIL全称是 Global Interpreter Lock ,他是一把超级全局大锁,Python官方的解释链接在这:

https://wiki.python.org/moin/GlobalInterpreterLock

先说说什么是锁,锁这个词很形象了,一般来说,如果代码内多个线程公用某个变量,一会你改一会我改,没有互斥锁的话,大家一起改来改去变量就会改的稀烂,举个例子:

import threading

money = 0

def money_activity(x):
    global money
    money = money + x
    money = money - x

def test_thread(x):
    for i in range(100000):
        money_activity(x)


if __name__ == '__main__':
    t1 = threading.Thread(target=test_thread, args=(10,))
    t2 = threading.Thread(target=test_thread, args=(15,))
    t3 = threading.Thread(target=test_thread, args=(12,))
    t1.start()
    t2.start()
    t3.start()
    print(money)

上面这些代码,就是不加锁的后果演示,大家公用money这个全局变量,理论上一份代码运行出来的结果,应该是每次都一样,但是这份代码运行多次,每次结果都不一样,原因就是没有给money加锁,导致大家抢着操作money这个变量,导致变量被改乱了呗。

说完锁,再说Python的GIL机制,其实GIL不完全是Python的问题,是因为CPython这个解释器的历史问题,就像C++有GCC啊VC的编译器一样,Python也有很多解释器,CPython这种解释器用的人太多了,几乎就是默认的解释器,所以GIL的锅也就被Python背上了。。。

GIL这把大锁实在是太大了,大到几乎让Python变成一个纯单线程的语言了,它是一把全局锁,换成人话就是,就算我的CPU有一万个核心,无论在任何一个时间点只能有一个线程处于执行状态,很尴尬,相当没效率 ,做个实验就知道了:

# 循环做两次计数
import time

def thread_test():
    for i in range(1000000000):
        i = i + 1
    return i

if __name__ == '__main__':
    t_1 = time.time()
    for i in range(2):
        thread_test()
    t_2 = time.time()
    print(t_2-t_1)
上面代码的运行截图
# 开两个线程
import threading
import time

def thread_test():
    for i in range(1000000000):
        i = i + 1
    return i

if __name__ == '__main__':
    t_1 = time.time()
    t1 = threading.Thread(target=thread_test)
    t2 = threading.Thread(target=thread_test)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    t_2 = time.time()
    print(t_2-t_1)
运行截图

这特么也差太多了吧!按理说有GIL这个破锁, 再差也不会比单线程的效率差吧,然而事实狠狠的扇了我一个大嘴巴子,很难受。。。说实话,具体原理我特么也没仔细研究,大概意思就是因为GIL的存在,导致Python是约等于一个纯正的单线程程序,而线程之间切换也要时间呀,所以就导致效率较低。

我大概看了一下,Python的线程之间切换并不是靠时间片,而是依赖解释器判断的时间来切换,很尴尬,可以理解为这把锁释放的不够给力吧,但是怎么解决呢?有办法啊,用多进程。

from multiprocessing import Process
import time

def thread_test():
    for i in range(1000000000):
        i = i + 1
    return i

if __name__ == '__main__':
    t_1 = time.time()
    p1 = Process(target=thread_test)
    p2 = Process(target=thread_test)
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    t_2 = time.time()
    print(t_2-t_1)
运行结果

看运行结果,嗯,爆炸式速度提升,很给力。下一篇再仔细研究,待续。

发表评论