GIL与多线程

什么是GIL?

GIL全称global interpreter lock,全局解释器锁。每一个线程在执行之前都需要获取GIL权限才可执行代码,也就是说在多线程中,实际上同一时间只有一个线程在运行。GIL并非是python自带的特性,而是Cpython解释器引入的一个概念,在Jpython(Java实现的python解释器)中就没有GIL。

这是一个历史遗留问题,如今大量开发者习惯了这套机制,代码量也越来越多 ,已经不容易通过修改Cpython来解决这个问题。

并行与并发

在进一步了解GIL之前,我们先回顾一下并行与并发的概念。

并行:同一时间,可以处理多个任务,多个任务是一起被执行。

并发:同一时间,不能处理多个任务,但是可以交替处理多个任务。

举个简单的例子:

你边写作业,边玩手机,说明你支持并行。

你写5分钟作业然后玩10分钟手机,再写5分钟,再玩10分钟,说明你支持并发。

他们虽然方式不同,但都指向了一个点,多任务执行,目的是要提高CPU的使用效率。这里需要注意 一点,单核CPU永远无法实现并行,一个CPU无法同时运行多个程序,但是可以实现并发。在多线程执行代码过程中会带来一个问题,线程间的数据一致性和状态同步完整性。(补充说明:子进程在开始后,他们的执行是随机的,并没有先后顺序)解决这个问题最简单的方案,就是加上GIL这把全局大锁。因为有了GIL的存在,python中多线程的效率会大打折扣。甚至几乎等于python就是单线程程序。

下面我们做一个python下多线程和单线程效率对比:

from threading import Thread
import time

def task():         #执行任务
    i = 0
    for _ in range(10000000):
        i = i + 1
    return True
复制代码

下面main1模仿的是串行执行任务,其中采用for循环创建线程主要是增加创建线程的时间,更准确的对比出多线程有GIL的差别。

def main1():        #单线程串行
    start_time = time.time()
    for tid in range(20):
        t = Thread(target=task)
        t.start()
        t.join()
    end_time = time.time()

    print("单线程耗时: {}".format(end_time - start_time))


def main2():        #多线程并发
    thread_array = {}
    start_time = time.time()
    for tid in range(20):
        t = Thread(target=task)
        t.start()
        thread_array[tid] = t
    for i in range(20):
        thread_array[i].join()
    end_time = time.time()

    print("多线程耗时: {}".format(end_time - start_time))


if __name__ == '__main__':
    main1()
    main2()

复制代码

测试CPU为i7 7700k,python版本为3.7,测试结果如下:

当任务为计算密集型时,多线程并发对比串行区别并不大。

单线程耗时: 16.951716423034668
多线程耗时: 16.735241651535034
复制代码

下面我们修改一下执行任务,暂停1秒模仿程序IO操作:

def task():         #执行任务
    time.sleep(1)
    return True
复制代码

执行结果如下:

对比明显,多线程在执行IO密集型效率会比串行高很多。

单线程耗时: 20.019601345062256
多线程耗时: 1.0051548480987549
复制代码

接下来我们对比一下多线程与多进程在计算密集型和IO密集型的差异:

计算密集型

# 计算密集任务
def task():
    sum = 1
    for i in range(100000000):
        sum *= i
    pass
复制代码

IO密集型

# IO密集任务
def task():
    time.sleep(5)
    pass
复制代码
if __name__ == '__main__':      

    start_time = time.time()
    
    # 多线程
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    t3 = Thread(target=task)
    t4 = Thread(target=task)
    t5 = Thread(target=task)
    t6 = Thread(target=task)
    # 多进程
    # t1 = Process(target=task)
    # t2 = Process(target=task)
    # t3 = Process(target=task)
    # t4 = Process(target=task)
    # t5 = Process(target=task)
    # t6 = Process(target=task)

    t1.start()
    t2.start()
    t3.start()
    t4.start()
    t5.start()
    t6.start()

    t1.join()
    t2.join()
    t3.join()
    t4.join()
    t5.join()
    t6.join()

    end_time = time.time()

    print("多线程耗时: {}".format(end_time - start_time))
复制代码

对比结果如下:

计算密集型

多线程耗时: 38.91398763656616
多进程耗时: 9.934444665908813
复制代码

IO密集型

多线程程耗时: 5.002830743789673
多进程程耗时: 5.4561707973480225
复制代码

在计算密集型中,多进程效率几乎碾压多线程,而在IO密集型多线程的优势就体现出来了,并且随着线程数量越多优势越明显。

总结: 经过实验对比,我们不难发现在GIL下的多线程也有自己的特色,在IO场景下有较好的性能,而在其他方面和单线程差异不大,当遇到被迫需要多线程的场景下,也可以考虑用多进程来代替。Python GIL是功能和性能之间权衡后的产物,它尤其存在的合理性,也有较难改变的客观因素。


ps:本文是学习之后的思考与总结,如有不足之处望指点。

转载于:https://juejin.im/post/5be95fda6fb9a049c6435f45

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值