python3 - 全局解释器锁(GIL)

目录

定义:

系统层面的线程处理:

未加GIL锁存在的安全隐患(线程安全):

GIL总结:

GIL下的多进程和多线程

 - 多线程、多进程效率测试

 - 计算密集型:多进程效率高

- I/O密集型:多线程效率高

GIL和Lock(即全局解释器锁和本地锁)


定义:

 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中,全局解释器锁(GIL),本质是一把互斥锁。用于防止本地线程,多次调用执行Python字节码。
这把锁存在的必要,是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在,其他功能已经发展到依赖于它所执行的保证。

系统层面的线程处理:

  1. 系统开启多线程
  2. 线程编译代码,查找语法错误
  3. 调用解释器外的GIL锁
  4. 抢到GIL锁的线程,调用解释器执行程序代码
  5. 结束代码执行,释放GIL锁

未加GIL锁存在的安全隐患(线程安全):

对于同一个数据,线程1的处理是使用数据计算,垃圾回收线程的处理是进行回收。因为进行并发执行,存在恰好,线程1在处理数据前,进行了IO操作导致被垃圾回收线程夺取了解释器执行权,则等线程1再次获取权限执行的时候,数据已经被回收。

原因:1.相同进程内的线程数据共享 2.当CPU处理资源遇到IO操作等会进行权限切换

GIL总结:

  • GIL本质是一把互斥锁,相当于使用Cpython解释器的权限,默认每一个进程内都存在一把GIL。
  • 同一进程内的多个线程必须抢到GIL后才能使用Cpython解释器执行代码。
  • 同一个进程下的多个线程无法实现并行,但是可以实现并发。
  • 若想要以线程实现并行,则需要开启多个进程实现。

GIL下的多进程和多线程

CPU单核: ====> 都选择使用线程
多任务处理计算密集型,无法实现并行,创建进程的开销大。选择开启一个进程,多个线程。

多任务处理I/O密集型,无法实现并行的进程开销又大,而切换进程的速度不如线程快。选择开启一个进程,多个线程。

CPU多核:

多任务处理计算密集型,多核实现进程并行,对于线程无法实现并行。选择开启多个进程。

多任务处理I/O密集型,多核情况下进程并发,线程在一个cpu核内(cpu不固定,任意切换)进行切换并发,差别在于并发的切换速度。选择开启一个进程,多个线程。

结论:由于市场上基本上是多核处理器,开发多是IO密集型程序,则对于性能的提升多用多线程。


 - 多线程、多进程效率测试

 - 计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import os, time


def work():
    res = 0
    for i in range(100000000):
        res *= i


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 查看本地计算器cpu核数 本机为4核
    start = time.time()
    for i in range(4):
        # p = Process(target=work)  # 进程处理时间 :run time is 13.770233631134033
        p = Thread(target=work)  # 线程处理时间:run time is 21.67884659767151
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))

- I/O密集型:多线程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os, time


def work():
    time.sleep(2)
    print('===>')


if __name__ == '__main__':
    l = []
    print(os.cpu_count())  # 本机为4核
    start = time.time()
    for i in range(400):
        p = Process(target=work)  # 耗时37.698168992996216多,大部分时间耗费在创建进程上
        # p = Thread(target=work)  # 耗时 2.1336281299591064
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' % (stop - start))

GIL和Lock(即全局解释器锁和本地锁)

GIL:用来保护解释器级别的数据

Lock:用来保护程序内部的局部数据,实现局部串行。

例:

from threading import Thread, Lock
import time

mutex = Lock()
n = 100


def task():
    global n
    with mutex:
        temp = n
        time.sleep(0.1)
        n = temp - 1


if __name__ == '__main__':
    l = []
    for i in range(100):
        t = Thread(target=task)
        l.append(t)
        t.start()

    for t in l:
        t.join()
    print(n)

多线程共同争抢GIL  -> 线程1抢到了GIL进入执行代码块 ->线程1开启本地锁,执行代码,遇到IO操作休眠,系统层面被并发 ->线程2抢到GIL锁进入,遇到本地锁被锁定原地等待解放……

打个比喻:

一大门为GIL,是一个人工智能自动门,可检测内部隔间内是否存在浪费资源不干活的人和进入超时的人。

隔间门为Lock本地锁,只能容纳一个人进入,并且进入之后手动上锁。

当一个人进入隔间,偷懒未工作,被AI捕获,则打开大门放一个抢到GIL的新人进来。因为隔间门手动上锁,如果里面人耍流氓不出来,新人也只能原地等待。 


猜你喜欢

转载自blog.csdn.net/qq_33961117/article/details/82496677