zoukankan      html  css  js  c++  java
  • Python GIL问题

    Python‘最难’的问题——GIL问题

    一、什么是GIL

    GIL(解释器全局锁)

    从名字上看能告诉我们很多东西,很显然,这是一个加在解释器上的全局(从解释器的角度看)锁(从互斥或者类似角度看)。

    首先来看回顾一下什么是锁:

    为什么加锁

    由于多线程共享进程的资源和地址空间,因此,在对这些公共资源进行操作时,为了防止这些公共资源出现异常的结果,必须考虑线程的同步和互斥问题。

    加锁的作用

    1、用于非线程安全,2、控制一段代码,确保其不产生调度混乱。

    GIL官方给出的解释

    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

    在CPython中,全局解释器锁(global interpreter lock, GIL)是一个互斥体,它防止多个本机线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经逐渐依赖于它强制执行的保证。)

    二、GIL的影响

    GIL的设计缺陷

    从上文的介绍和官方的定义来看,GIL就是一把全局排他锁。这种方式当然很安全,但是这对于任何Python程序来说,不管有多少的处理器,任何时候都总是只有一个线程在执行。毫无疑问全局锁的存在会对多线程的效率有不小影响。

    但是我们课上讲的例子,并不是这样啊

    上课的多线程例子:

    from threading import Thread
    import time
    
    def task():
        time.sleep(5)
        
    def run():
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        start = time.time()
        t1.start()
        t1.join()
    
        t2.start()
        t2.join()
        end = time.time()
        print(f'Total time: {end - start}')
    	
        '''
        串行结果:
        Total time:10
        '''
    
        
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        
        
        '''
        并行结果:
        Total time:5
        '''
    	
    

    但是!

    看看这个

    from threading import Thread
    import time
    
    
    def counter():
        # 计数到一亿
        i = 0
        for _ in range(100000000):
            i += 1
        return True
      
    
    
    def run():
        t1 = Thread(target=counter)
        t2 = Thread(target=counter)
        start = time.time()
        t1.start()
        t1.join()
        t2.start()
        t2.join()
        end = time.time()
        print(f'Total time: {end - start}')
    	
        '''
    	串行结果(即单线程):
    	Total time: 15.838918209075928
    	'''
    	
        t1.start()
        t2.start()
        t1.join()
        t2.join()
        
        '''
        并行结果:
        Total time: 16.79609990119934 
        (其实他们两个结果我跑的时候不相上下,但是也能说明问题)
        '''
    if __name__ == '__main__':
        run()
    
    

    问题来了。

    为什么多线程并行比单线程慢,但是一些例子为什么多线程并行时间又更少?

    刚刚也说了是因为GIL导致的,python解释器任何时候都是一个线程在执行。

    一些情况下多线程并行快的原因是: 线程做的是i/o操作, 可以挂起当前线程去执行下一线程。因为遇到像 i/o操作这种 会有时间空闲情况 造成cpu闲置的情况会释放GIL

    所以在python上只要在进行耗时的IO操作的时候,能释放GIL,这样也还是可以提升运行效率的。

    为什么GIL有这个缺陷而不改进?

    改不了!

    为什么改不了

    历史遗留问题:因为硬件的升级cpu单核变多核,当时python开发者为了利用多核,就出现了多线程编程,而随之带来的就是线程间数据一致性和状态同步的困难。 python开发者的解决方法就是加GIL这把大锁。

    但是人们之后发现了这种多线程编程实现方式是垃圾的,想去除他,但是这时候已经发现离不开他了,大量的开发者已经重度依赖GIL了。

    有了GIL的存在,python有这两个特点

        1、进程可以利用多核,但是开销大。

        2、多线程开销小,却无法利用多核优势。

      也就是说Python中的多线程是假的多线程,Python解释器虽然可以开启多个线程,但同一时间只有一个线程能在解释器中执行,而做到这一点正是由于GIL锁的存在,它的存在使得CPU的资源同一时间只会给一个线程使用,而由于开启线程的开销小,所以多线程才能有一片用武之地,不然就真的是鸡肋了。

      而python的多线程到底有没有用呢?

    ​ 有用。我们需要看任务是I/O密集型,还是计算密集型:

        如果是I/O密集型任务,有再多核也没用,即能开再多进程也没用,所以我们利用python的多线程一点问题也没有;

        如果是计算密集型任务,我们就直接使用多进程就可以了

    如何解决GIL锁带来的问题

    1、不用cpython,使用jpython(不太推荐)

    2、使用多进程完成多线程任务(python专家推荐)

    用multiprocess来代替Thread

    multiprocess库的出现很大程度上是为了弥补thread库因为GIL而低效的缺陷。它完整的复制了一套thread所提供的接口方便迁移。唯一的不同就是它使用了多进程而不是多线程。每个进程有自己的独立的GIL,因此也不会出现进程之间的GIL争抢。

    但是它的引入会增加程序实现时线程间数据通讯和同步的困难。

    3、使用多线程时用c语言实现

  • 相关阅读:
    sort和uniq去重操作【转】
    MySQL中interactive_timeout和wait_timeout的区别【转】
    Keepalived详解(五):Keepalived集群中MASTER和BACKUP角色选举策略【转】
    Keepalived详解(四):通过vrrp_script实现对集群资源的监控【转】
    Keepalived详解(三):Keepalived基础功能应用实例【转】
    Spring详解(六)------AspectJ 实现AOP
    深入理解计算机系统(1.3)------操作系统的抽象概念
    深入理解计算机系统(1.2)------存储设备
    深入理解计算机系统(1.1)------Hello World 是如何运行的
    深入理解计算机系统(序章)------谈程序员为什么要懂底层计算机结构
  • 原文地址:https://www.cnblogs.com/Wunsch/p/11542258.html
Copyright © 2011-2022 走看看