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

[TOC]python

1、什么是GIL

GIL(解释器全局锁)

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

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

为何加锁

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

加锁的做用

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

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存在以来,其余特性已经逐渐依赖于它强制执行的保证。)设计

2、GIL的影响

GIL的设计缺陷

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

可是咱们课上讲的例子,并非这样啊接口

上课的多线程例子:进程

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有这两个特色

    一、进程能够利用多核,可是开销大。

    二、多线程开销小,却没法利用多核优点。

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

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

​ 有用。咱们须要看任务是I/O密集型,仍是计算密集型:

    若是是I/O密集型任务,有再多核也没用,即能开再多进程也没用,因此咱们利用python的多线程一点问题也没有;

    若是是计算密集型任务,咱们就直接使用多进程就能够了

如何解决GIL锁带来的问题

一、不用cpython,使用jpython(不太可能)

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

用multiprocess来代替Thread

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

可是它的引入会增长程序实现时线程间数据通信和同步的困难。

三、使用多线程时用c语言实现 (还要学c,我可去他的)

相关文章
相关标签/搜索