GIL

一、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解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。

image

需要强调的是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

二、为什么需要GIL

再python的进程中,除了主线程还有其他线程,包括python解释器开启的垃圾回收等解释器级别的线程。假如代码n=1执行时,定义变量n的同时垃圾回收的线程恰好发现变量n并没有绑定值而直接回收。此时的情况是不能允许的,所以需要锁让同一时刻只能有一个线程操作数据,这便是GIL。

image

三、检验GIL存在

image

如下代码检验GIL的存在,定义m=100,创建100个线程同时修改m。

from threading import Thread
import time
m = 100
def test():
    global m
    tmp = m
    tmp -= 1
    m = tmp

for i in range(100):
    t = Thread(target=test)
    t.start()
time.sleep(3)
print(m)  # 0

可能你会觉得m是99,100个线程获得的m都是100,各自-1赋值m为99。但是由于GIL的存在,同一个进程中的多个线程操作数据时是串行的,即获取GIL锁,修改数据并释放GIL锁。

python多线程

介绍完GIL是否会有疑问,同一时刻只有一个线程操作数据,python多线程开销小,但是无法利用多核优势,python狗都不用?

解决这个疑惑,我们需要知道几个点:

  1. cpu遇到io操作会切换,多核对于io操作来说毫无作用。
  2. 计算任务会持续持有cpu,多核意味着并行完成计算,提高计算效率。

所以,对于计算密集型任务,多进程(多核)能够提升执行效率;IO密集型任务,多线程执行效率高。

实验:
1、计算密集型任务

from threading import Thread
from multiprocessing import Process
import time


def task():
    ret = 1
    for i in range(10000000):
        ret *= i
    return ret


if __name__ == '__main__':
    l = []
    start = time.time()
    for i in range(16):
        p = Process(target=task)  # 用时0.9139392375946045秒
        # t = Thread(target=task)   # 用时4.870624780654907秒
        l.append(p)
        # l.append(t)
        p.start()
        # t.start()

    for j in l:
        j.join()

    end = time.time()
    print(end-start)

2、IO密集型任务

from threading import Thread
from multiprocessing import Process
import time


def task():
    time.sleep(2)


if __name__ == '__main__':
    l = []
    start = time.time()
    for i in range(100):
        p = Process(target=task)  # 用时7.828985214233398秒
        # t = Thread(target=task)  # 用时2.0460045337677秒
        l.append(p)
        # l.append(t)
        p.start()
        # t.start()

    for j in l:
        j.join()

    end = time.time()
    print(end-start)

posted @ 2022-01-17 17:35  它叫鸮  阅读(364)  评论(0编辑  收藏  举报