目录

Python全局锁(GIL)

题记

使用threading进行多线程处理的时候,运算速度并没有提高。原因在于python中的全局锁(GIL, global interpreter lock).

样例

计算圆周率的程序

代码

点击展开代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import threading
import random
import math
import time

def print_run_time(func):
    def wrapper(*args, **kw):
        begin_time = time.time()
        ret = func(*args, **kw)
        end_time = time.time()
        run_time = end_time - begin_time
        print('Run time',run_time)
        return ret
    return wrapper

@print_run_time
def run():
    sample_count = 10000000 #采样次数
    inner = 0 #落在圆内的点
    i = 0
    while i <sample_count:
        x_i = random.uniform(-1,1)
        y_i = random.uniform(-1,1)
        if (math.pow(x_i,2) + math.pow(y_i,2)) < 1:
            inner+=1
        i+=1
    pi = 4*(inner * 1.0)/sample_count
    print(pi)
    
@print_run_time
def single_run():
    print('single','='*20)
    for _ in range(5):
        run()
        
@print_run_time
def multi_run():
    print('multi','='*20)
    ts = []
    for i in range(5):
        ts.append(threading.Thread(target=run))
        ts[i].start()
    for i in range(5):
        ts[i].join()

if __name__ == '__main__':
    multi_run()
    single_run()

结果

~/dataset/xiaoaiKWS/G/code$ python3 测试多线程速度.py

multi 多线程

3.140688

Run time 83.02815461158752

3.1416188

Run time 85.89040064811707

3.1412312

Run time 86.34525346755981

3.14129

Run time 88.51046800613403

3.1419212

Run time 88.64371156692505

Run time 88.65449285507202

single

3.1421216

Run time 10.199804782867432

3.1420676

Run time 10.172762870788574

3.1419712

Run time 10.3925302028656

3.1417784

Run time 10.204853534698486

3.1416324

Run time 10.210267305374146

Run time 51.180365800857544

启示

多线程速度要慢于串行,多线程不适合cpu密集型程序(但是可以用在IO密集的程序里).

GIL

参考: http://cenalulu.github.io/python/gil-in-python/

https://zhuanlan.zhihu.com/p/20953544

为什么有GIL

GIL是线程之间的排他锁(官方文档使用mutex这个词),用于保持线程间数据的一致性和状态的同步(这本身是一个非常难的议题, MySQL用了五年时间优化,而python作为高度社区化团队开发为解决这个问题更为艰难),而GIL是一种非常简单粗暴但是优雅的方法.

图解多线程

GIL释放条件

  • 在python2.x里,GIL的释放逻辑是当前线程遇见IO操作或者ticks计数达到100(ticks可以看作是python自身的一个计数器,专门做用于GIL,每次释放后归零,这个计数可以通过 sys.setcheckinterval 来调整),进行释放.
  • 在python3.x中,GIL不使用ticks计数,改为使用计时器(执行时间达到阈值后,当前线程释放GIL),这样对CPU密集型程序更加友好,但依然没有解决GIL导致的同一时间只能执行一个线程的问题,所以效率依然不尽如人意.

两个线程在双核CPU上的执行情况.

  • 绿色部分表示该线程在运行

  • 红色部分为线程被调度唤醒,但是无法获取GIL导致无法进行有效运算等待的时间

  • 白色部分表示IO线程处于等待

两个线程均为CPU密集型运算线程

GIL Performance

一个IO密集型和一个CPU密集型线程

GIL IO Performance

IO密集型代码(文件处理、网络爬虫等),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以python的多线程对IO密集型代码比较友好。

结论

python尽量使用多进程使用python multiprocessing模块),而不是多线程,因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行.

扩展资料