全局解释器锁--GIL

参考博客:https://www.cnblogs.com/mindsbook/archive/2009/10/15/thread-safety-and-GIL.html

        https://www.cnblogs.com/MnCu8261/p/6357633.html

     http://python.jobbole.com/87743/

一、前言

  在多核cpu的背景下,基于多线程以充分利用硬件资源的编程方式也不断发展,也就是在同一时间,可以运行多个任务。但是Cpython中由于GIL的存在,导致同一时间内只有一个线程在运行。GIL的全称为Global Interpreter Lock,也就是全局解释器锁。存在在Python语言的主流执行环境Cpython中,GIL是一个真正的全局线程排他锁,在解释器执行任何Python代码时,都需要获得这把GIL锁。虽然 CPython 的线程库直接封装操作系统的原生线程,但 CPython 进程做为一个整体,同一时间只会有一个获得了 GIL 的线程在跑,其它的线程都处于等待状态等着 GIL 的释放。GIL 直接导致 CPython 不能利用物理多核的性能加速运算。

            

  不同的线程也是被分配到不同的核上面运行的,但是同一时间只有一个线程在运行

二、为什么存在GIL

  2.1 线程安全

  想要利用多核的优势,我们可以采用多进程或者是多线程,两者的区别是资源是否共享。前者是独立的,而后者是共享的。相对于进程而言,多线程环境最大的问题是如果保证资源竞争、死锁、数据修改等。于是就有了线程安全。

   线程安全 是在多线程的环境下, 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取.

  既然,多线程环境下必须存在资源的竞争,那么如何才能保证同一时刻只有一个线程对共享资源进行存取?

加锁, 加锁可以保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取。

通常加锁也有2种不同的粒度的锁:

  1. fine-grained(所谓的细粒度), 那么程序员需要自行地加,解锁来保证线程安全
  2. coarse-grained(所谓的粗粒度), 那么语言层面本身维护着一个全局的锁机制,用来保证线程安全

前一种方式比较典型的是 java, Jython 等, 后一种方式比较典型的是 CPython (即Python)。

  2.2 Python自身特点

  依照Python自身的哲学, 简单 是一个很重要的原则,所以, 使用 GIL 也是很好理解的。多核 CPU 在 1990 年代还属于类科幻,Guido van Rossum 在创造 python 的时候,也想不到他的语言有一天会被用到很可能 多核的 CPU 上面,一个全局锁搞定多线程安全在那个时代应该是最简单经济的设计了。简单而又能满足需求,那就是合适的设计(对设计来说,应该只有合适与否,而没有好与不好)。

三、线程切换

  一个线程无论何时开始睡眠或等待网络 I/O,其他线程总有机会获取 GIL 执行 Python 代码。这是协同式多任务处理。CPython 也还有抢占式多任务处理。如果一个线程不间断地在 Python 2 中运行 100次指令,或者不间断地在 Python 3 运行15 毫秒,那么它便会放弃 GIL,而其他线程可以运行。

  3.1 协同式多任务处理

  当一项任务比如网络 I/O启动,而在长的或不确定的时间,没有运行任何 Python 代码的需要,一个线程便会让出GIL,从而其他线程可以获取 GIL 而运行 Python。这种礼貌行为称为协同式多任务处理,它允许并发,多个线程同时等待不同事件。  

def do_connect():
    s = socket.socket()
    s.connect((‘python.org‘, 80))  # drop the GIL

for i in range(2):
    t = threading.Thread(target=do_connect)
    t.start()

  两个线程在同一时刻只能有一个执行 Python ,但一旦线程开始连接,它就会放弃 GIL ,这样其他线程就可以运行。这意味着两个线程可以并发等待套接字连接,这是一件好事。在同样的时间内它们可以做更多的工作。

  3.2 抢占式多任务处理

  如果没有I/O中断,而是CPU密集型的的程序,解释器运行一段时间就会放弃GIL,而不需要经过正在执行代码的线程允许,这样其他线程便能运行。在python3中,这个时间间隔是15毫秒。

四、Python中的线程安全

  如果一个线程可以随时失去 GIL,你必须使让代码线程安全。 然而 Python 程序员对线程安全的看法大不同于 C 或者 Java 程序员,因为许多 Python 操作是原子的。

  在列表中调用 sort(),就是原子操作的例子。线程不能在排序期间被打断,其他线程从来看不到列表排序的部分,也不会在列表排序之前看到过期的数据。原子操作简化了我们的生活,但也有意外。例如,+ = 似乎比 sort() 函数简单,但+ =不是原子操作。

  在python 2中(python3中结果没有问题):

# -*- coding: UTF-8 -*-
import time
import threading

n = 0

def add_num():
    global n
    time.sleep(1)
    n += 1

if __name__ == ‘__main__‘:
    thread_list = []

    for i in range(100):
        t = threading.Thread(target=add_num)
        t.start()
        thread_list.append(t)

    for t in thread_list:
        t.join()

    print ‘final num:‘, n

  输出:

[[email protected] ~]# python mutex.py
final num: 98
[[email protected] ~]# python mutex.py
final num: 100
[[email protected] ~]# python mutex.py
final num: 96
[[email protected] ~]# python mutex.py
final num: 99
[[email protected] ~]# python mutex.py
final num: 100

  得到的结果本来应该是100,但是实际上并不一定。

  原因就在于,运行中有线程切换发生,一个线程失去了GIL,当一个线程A获取n = 43时,还没有完成n +=1这个操作,就失去了GIL,此时正好另一个线程B获取了GIL,并也获取了 n = 43,B完成操作后,n = 44。可是先前那个线程A又获得了GIL,又开始运行,最后也完成操作 n = 44。所有最后的结果就会出现偏差。

  

  上图就是n += 1运行到一半时失去GIL后又获得GIL的过程。

五、Mutex排他锁

  如何解决上面的偏差,保证结果的正确性?其实我们要做的就是确保每一次的运行过程是完整的,就是每次线程在获取GIL后,要将得到的共享数据计算完成后,再释放GIL锁。那又如何能做到这点呢?还是加锁,给运行的程序加锁,就能确保在程序运行时,必须完全运行完毕。  

# -*- coding: UTF-8 -*-
import time
import threading

n = 0
lock = threading.Lock()    # 添加一个锁的实例

def add_num():
    global n
    with lock:    # 获取锁
        n += 1

if __name__ == ‘__main__‘:
    thread_list = []

    for i in range(100):
        t = threading.Thread(target=add_num)
        t.start()
        thread_list.append(t)

    for t in thread_list:
        t.join()        # 主线程等待所有线程执行完毕

    print ‘final num:‘, n

  注:给程序加锁,程序就变成串行的了。所以程序中不能有sleep,同样数据量也不能特别大,否则会影响效率

时间: 2024-11-09 00:14:37

全局解释器锁--GIL的相关文章

python 什么是全局解释器锁GIL

什么是全局解释器锁GIL Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行.对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行. 在多线程环境中,Python 虚拟机按以下方式执行: 1. 设置GIL2. 切换到一个线程去运行3. 运行:    a. 指定数量的字节码指令,或者 b.

Python 36 GIL全局解释器锁

一:GIL全局解释器锁介绍 在CPython中,全局解释器锁(或GIL)是一个互斥锁, 它阻止多个本机线程同时执行Python字节码.译文:之所以需要这个锁, 主要是因为CPython的内存管理不是线程安全的.(然而,由于GIL的存在, 其他特性已经变得依赖于它所执行的保证.) 1. 什么是GIL全局解释器锁GIL本质就是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL,同一进程内的多个线程必须抢到GIL之后才能使用Cpython解释器来执行自己的代码,即同一进程下的多个线程无法实现并行

117 GIL全局解释器锁

一.GIL全局解释器锁 cpython中自带的GIL全局解释器,GIL本身就是一把互斥锁 重点:因为有了GIL全局解释器锁,导致了在同一进程的同一时刻只有一个线程在执行,无法利用多核优势 其实就算我们在程序中写了一个线程的并行操作,实际上GIL会因为垃圾回收机制的问题,操作系统调度的问题,会把并行的线程还是变成了串行,这正是这个GIL全局解释器锁导致了同一进程的同一时刻只有一个线程在运行, Python代码的执行由Python虚拟机(也叫解释器主循环)来控制.Python在设计之初就考虑到要在主

GIL全局解释器锁及协程

GIL全局解释器锁 1.什么是GIL全局解释器锁 GIL本质是一把互斥锁,相当于执行权限,每个进程内都会存在一把GIL同一进程内的多线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的代码,即同一进程下的多个线程无法实现并行,但可以实现并发 Cpython解释器下想实现并行可以开启多个进程 2.为何要有GIL 因为Cpython解释器的垃圾回收机制不是线程安全的,保证了数据的安全 3.GIL全局解释器的优缺点 优点:保证了数据安全 缺点:单个进程下开启多个线程只能实现并发不能实现并行

python开发线程:线程&守护线程&全局解释器锁

一 threading模块介绍 multiprocess模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,因而不再详细介绍 官网链接:https://docs.python.org/3/library/threading.html?highlight=threading# 二 开启线程的两种方式 #方式一 from threading import Thread import time def sayhi(name): time.sleep(2) print('%s

python 全局解释锁GIL

Python的全局解释器锁GIL用于保护python解释器,使得任意时刻,只有一个线程在解释器中运行.从而保证线程安全 在多线程环境中,Python 虚拟机按以下方式执行: 1. 设置GIL2. 切换到一个线程去运行3. 运行:    a. 指定数量的字节码指令,或者 b. 线程主动让出控制(可以调用time.sleep(0))4. 把线程设置为睡眠状态5. 解锁GIL6. 再次重复以上所有步骤 由上可知,至少有两种情况python会解锁GIL,做线程切换:一是一但有IO操作时:线程连续执行了一

33、线程与全局解释器锁(GIL)

之前我们学了很多进程间的通信,多进程并发等等,今天我们来学习线程,线程和进程是什么关系,进程和线程有什么相同而又有什么不同今天就来揭晓这个答案. 一.线程概论 1.何为线程 每个进程有一个地址空间,而且默认就有一个控制线程.如果把一个进程比喻为一个车间的工作过程那么线程就是车间里的一个一个流水线. 进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位. 多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间(

Python之路44-进程与线程区别和GIL全局解释器锁

进程 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就成为进程,程序和进程的区别,程序是指令的集合,它是进程运行的静态描述文本,进程是程序的一次执行活动,属于动态概念 有了进程为什么还要有线程? 进程只能同一时间干一件事 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖此输入的数据,也将无法执行 线程 线城是操作系统能够进行运算调度的最小单位,它包含在进程中,是进程的实际运行单位 进程和线程的区别 线程共享创建它的进程的

GIL(全局解释器锁)

GIL(全局解释器锁) 我们知道多进程(mutilprocess) 和 多线程(threading)的目的是用来被多颗CPU进行访问, 提高程序的执行效率. 但是在python内部存在一种机制(GIL),在多线程 时同一时刻只允许一个线程来访问CPU. GIL 并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念.就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码.有名的编译器例如GCC,INTEL C++,Visual C++等.