day12 课堂笔记 GIL全局解释器锁

1. GIL全局解释器

1.1 python执行一个文件的过程

  • 引子

    知乎,百度一些大V说python并发效率不行,不能多线程,并行不行等等。我们下面就仔细研究一下。

  • python执行文件的过程

    你在终端输入python py文件的路径回车就执行了python文件,他在内存中的整个过程是什么样的?

    在内存中开启一个进程空间,将python解释器与py文件同时加载到内存中。将py文件当做实参,将python解释器当做函数,执行函数的过程,最终返回一个结果。

在这里插入图片描述

理论上来说,我们一个py文件可以开启多线程,这些线程都可以进入CPython解释器然后并行的执行任务。

在这里插入图片描述

**但是!**这个只是理论来说的。python的单个进程的多个线程应用不了多核。CPython源码的程序员给进入解释器的线程加了一把锁,就是我们常说的互斥锁。

在这里插入图片描述

一个进程下的多个线程不能并行,但是可以并发。

1.2 为什么加这把锁GIL锁?

历史原因:

  • 当时那个年代是单核时代,而且CPU价格非常昂贵,python期初作为一种脚本语言,面临的需求单核解决足以。
  • 如果不加这把锁,同一时刻进入CPython解释器线程数量不定,我们要保证CPython解释器的数据资源安全,就需要在源码内部需要主动加入大量的互斥锁保证数据安全性,这样非常麻烦并且对于CPython源码的开发速度势必减慢。

1.3 为什么不去掉这把锁?

CPython解释器内部的管理以及业务逻辑全部是围绕单线程实现的,并且从龟叔创建CPython到现在,CPython源码已经更新迭代马上到4版本了,源码内容体量庞大,如果要去掉,这个工程无异于重新构建python,太难。

CPython解释器是官方推荐的解释器,处理速度快,功能强大。

JPython就是编译成Java识别的字节码,没有GIL锁。

pypy属于动态编译型,规则和漏洞很多,现在还在测试阶段(未来可能会成为主流)没有GIL锁。

只有CPython解释器有GIL锁,其他类型的解释器以及其他语言都没有。

这把锁不是python语言的缺陷,而是CPython解释器的缺陷。

1.4 这把锁带来的影响以及如何解决?

这把锁带来的影响是单进程下多线程不能利用多核并行,只能利用单核并发。怎么优化?

  • 我们可以用多进程的并行替代,只不过可能是开启进程有些开销大,但是效率差不多。
  • 我们也可以采用C的模块或者嵌入C语言去处理这种单进程的多线程的并行的问题。

1.5 这把锁真的是影响开发效率么?

我们多线程或者多进程的处理任务,此时这个任务分为两种。

IO密集型:我们以后从事的开发面对业务,基本上都是IO密集型,对于IO密集型单个进程下的多线程并发解决就可以了。

在这里插入图片描述

IO密集型:操作系统可以操控着CPU遇到IO就将CPU强型的切换执行另一个任务,而这个任务遇到IO阻塞了,马上又会切换,所以IO密集型利用单个进程的多线程并发是最好的解决方式(后面还会有协程也非常好用)

计算密集型:多个任务都是纯计算都没有IO阻塞,那么此时应该利用多进程并行的处理任务。

在这里插入图片描述

小结

  • GIL全局解释器锁只存在于CPython解释器中,他是给进入解释器的线程上锁带来的影响:
    • 优点:便于CPython解释器的内部资源管理,保证了CPython解释器的数据安全。
    • 缺点:单个进程的多线程不能利用多核。
    • GIL全局解释器锁并不是让CPython不能利用多核,多进程是可以利用多核的,况且IO密集型的任务,单个进程的多线程并发处理足以。
  • IO密集型:单个进程的多线程并发处理。
  • 计算密集型:多个进程并行处理。

2. GIL锁与普通的互斥锁的区别

  • 两把锁保护对象不同
    • GIL锁保护的是解释器以及各种库的数据安全。而普通的互斥锁保护的是我们自己写的程序的数据安全。
  • 两把锁的性质相同
    • GIL锁实际上就是互斥锁,只是保护的对象不同。

3. CPython解释器并发效率验证

3.1 IO密集型的效率验证

多进程并行:

from multiprocessing import Process
from threading import Thread
import time
import random


def task():
    res = 0
    for i in range(5):
        time.sleep(1)
        res += 1
      
  
if __name__ == '__main__':
    start_time = time.time()
    l1 = []
    for i in range(4):
        p = Process(target=task,)
        p.start()
        l1.append(p)
       
    
    for i in l1:
        i.join()
       
    print(f'执行时间是{time.time()-start_time}') # 5.1...

多线程并发:

from multiprocessing import Process
from threading import Thread
import time
import random


def task():
    res = 0
    for i in range(5):
        time.sleep(1)
        res += 1
      
  
if __name__ == '__main__':
    start_time = time.time()
    l1 = []
    for i in range(4):
        p = Thread(target=task,)
        p.start()
        l1.append(p)
       
    
    for i in l1:
        i.join()
        
    print(f'执行时间是{time.time()-start_time}') # 5.0...
  • 小结:

    IO密集型来说,单进程下的多线程的并发与多进程的并行效率差不多。

    多进程的并行:

    from multiprocessing import Process
    from threading import Thread
    import time
    
    
    def task1():
        res = 0
        for i in range(1000000):
            res += 1
           
        
    def task2():
        res = 0
        for i in range(1000000):
            res -= 1
           
       
    def task3():
        res = 2
        for i in range(1000000):
            res *= 2
           
       
    def task4():
        res = 10
        for i in range(1000000):
            res /= 2
           
       
    if __name__ == '__main__':
        start_time = time.time()
        l1 = []
        p1 = Process(target=task1,)
        p2 = Process(target=task2,)
        p3 = Process(target=task3,)
        p4 = Process(target=task4,)
        p1.start()
        p2.start()
        p3.start()
        p4.start()
        p1.join()
        p2.join()
        p3.join()
        p4.join()
        print(f'执行时间是{time.time()-start_time}') # 15.89...
    

    多线程的并发:

    from multiprocessing import Process
    from threading import Thread
    import time
    
    
    def task1():
        res = 0
        for i in range(1000000):
            res += 1
           
        
    def task2():
        res = 0
        for i in range(1000000):
            res -= 1
           
       
    def task3():
        res = 2
        for i in range(1000000):
            res *= 2
           
       
    def task4():
        res = 10
        for i in range(1000000):
            res /= 2
           
       
    if __name__ == '__main__':
        start_time = time.time()
        l1 = []
        p1 = Thread(target=task1,)
        p2 = Thread(target=task2,)
        p3 = Thread(target=task3,)
        p4 = Thread(target=task4,)
        p1.start()
        p2.start()
        p3.start()
        p4.start()
        p1.join()
        p2.join()
        p3.join()
        p4.join()
        print(f'执行时间是{time.time()-start_time}') # 16.12...
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值