GIL锁

(一)GIL全局解释器锁介绍

  • GIL锁存在于我妈的解释器或者,解释器生来自带的锁
  • GIL锁是CPython解释器独有的一种锁
  • GIL锁导致的后果就是同一时刻同一个进程下只能有一个线程在运行,导致Python无法利用多核优势
# 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

(二)GIL锁与普通互斥锁的区别

  • GIL锁是Cpython解释器的特点,不是python的特点
  • GIL锁用来阻止同一个进程中的多个线程不能利用多核优势
  • GIL锁 能使 一个进程中中的多个线程同一时刻只能由一个线程执行
  • GIL锁保证的数解释器级别的数据
  • 针对不同的数据还是要加入自己的锁
from  threading import Thread,Lock
import  time
mutex=Lock()
number=100
def run_task():
    global number
    temp=number
    time.sleep(1)
    number=temp-1
if __name__ == '__main__':
    task_list=[]
    # 创建线程对象
    for i in range(100):
        t=Thread(target=run_task)
        t.start()
        task_list.append(t)
    for task in task_list:
        task.join()
    print(number)

# 开启多线程后,所有的线程都会先去抢占GIL锁
# 第一个线程抢到GIL锁之后执行任务,遇到IO后释放GIL锁,
# 所有的线程都会获取到全局的number,对数据进行修改
from  threading import Thread,Lock
import  time
mutex=Lock()
number=100
def run_task():
    global number
    mutex.acquire()
    temp=number
    time.sleep(0.1)
    number=temp-1
    mutex.release()
if __name__ == '__main__':
    task_list=[]
    # 创建线程对象
    for i in range(100):
        t=Thread(target=run_task)
        t.start()
        task_list.append(t)
    for task in task_list:
        task.join()
    print(number)
# 0

"""
    开启线程后,所有的子线程都会去抢占GIL锁,子线程获取到GIL锁后,
    子线程又获取到了互斥锁,遇到IO阻塞会释放掉GIL锁,由其他的子线程获取到GIL锁
    但是获取到互斥锁的线程,会使抢到释放后的GIL锁的线程阻塞,释放GIL锁,
    直至获取到互斥锁的线程释放互斥锁,由下一个线程获取互斥锁
"""
  • GIL锁是Cpython解释器的特点,不是python的特点
  • GIL保证的是解释器级别的数据
  • GIL锁使进程中的多个线程在同一时刻只能有一个线程执行,无法利用多核优势
  • 针对不同的数据需要额外的加锁

(三)GIL导致多线程无法利用多核优势

(1)Cpython 解释器中 GIL

  • 在Cpythonh解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时进行
    • 同一个进程下的多个线程无法利用这一优势?
    • Python的多线程是不是一点用都没有?
  • 因为在Cpython中的内存管理不是线程安全的
    • ps:内存管理(垃圾回收机制)
      • 应用计数
      • 标记清除
      • 分代回收

(2)Python的多线程是不是一点用都没有?

  • 同一个进程下的多线程无法利用多核优势,是不是就没用了
  • 多线程是否有用要看情况
    • 单核
      • 四个任务(IO密集型/计算密集型)
    • 多核
      • 四个任务(IO密集型/计算密集型)

(1)计算密集型

一直处在计算运行中

  • 每个任务都需要 10s
    • 单核
      • 多进程:额外消耗资源
      • 多线程:减少开销
    • 多核
      • 多进程:总耗时 10s
      • 多线程:总耗时 40s+
from multiprocessing import Process
from threading import Thread
import time, os


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


def main_t():
    p_list = []
    # 获取当前CPU运行的个数
    print(os.cpu_count())
    start_time = time.time()
    for i in range(12):
        p = Process(target=work)
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()

    print(f'总耗时:>>>{time.time() - start_time}')

    # 8
    # 总耗时:>>>28.140103101730347


def main_p():
    t_list = []
    # 获取当前CPU运行的个数
    print(os.cpu_count())
    start_time = time.time()
    for i in range(12):
        t = Thread(target=work)
        t.start()
        t_list.append(t)

    for t in t_list:
        t.join()

    print(f'总耗时:>>>{time.time() - start_time}')

    # 8
    # 总耗时:>>>63.330037117004395


if __name__ == '__main__':
    # main_t()
    main_p()

(2)IO密集型

存在多个 IO 阻塞切换操作

  • 每个任务都需要 10s
    • 多核
      • 多进程:相对浪费资源
      • 多线程:更加节省资源
from multiprocessing import Process
from threading import Thread
import time, os


def work():
    time.sleep(2)


def main_t():
    p_list = []
    # 获取当前CPU运行的个数
    print(os.cpu_count())
    start_time = time.time()
    for i in range(400):
        p = Process(target=work)
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()

    print(f'总耗时:>>>{time.time() - start_time}')
    # 8
    # 总耗时:>>>36.23059678077698


def main_p():
    t_list = []
    # 获取当前CPU运行的个数
    print(os.cpu_count())
    start_time = time.time()
    for i in range(400):
        t = Thread(target=work)
        t.start()
        t_list.append(t)

    for t in t_list:
        t.join()

    print(f'总耗时:>>>{time.time() - start_time}')

    # 8
    # 总耗时:>>>2.1423909664154053


if __name__ == '__main__':
    main_t()
    # main_p()

(3)小结

  • 计算是消耗CPU的:
    • 代码执行,算数,for都是计算
  • IO不消耗CPU:
    • 打开文件、写入文件、网络操作都是IO
    • 如果遇到IO,该线程就会释放cpu的执行权限,cpu转而去执行别的线程
  • 由于python有GIL锁,开启多条线程,同一时刻,只能有一条线程执行
  • 如果是计算密集型,开了多线程,同一时刻只有一个线程在执行
  • 多核cpu,就会浪费多核优势
  • 如果是计算密集型,我们希望多个核CPU都干活,同一个线程下绕不过gil锁
  • 所以我们开启多进程,gil锁只能锁住某个进程中得线程,开启多个进程,就能利用多核优势
  • io密集型---》只要遇到io,就会释放cpu执行权限
  • 进程内开了多个io线程,线程多半都在等待,开启多进程是不能提高效率的,反而开启进程很耗费资源,所以使用多线程即可

(1)计算密集型任务(多进程)

  • 计算密集型任务主要是指需要大量的CPU计算资源的任务,其中包括执行代码、进行算术运算、循环等。

    • 在这种情况下,使用多线程并没有太大的优势。
    • 由于Python具有全局解释器锁(Global Interpreter Lock,GIL),在同一时刻只能有一条线程执行代码,这意味着在多线程的情况下,同一时刻只有一个线程在执行计算密集型任务。
  • 但是,如果使用多进程,则可以充分利用多核CPU的优势。

    • 每个进程都有自己独立的GIL锁,因此多个进程可以同时执行计算密集型任务,充分发挥多核CPU的能力。
    • 通过开启多个进程,我们可以将计算密集的任务分配给每个进程,让每个进程都独自执行任务,从而提高整体的计算效率。
  • IO密集型任务主要是指涉及大量输入输出操作(如打开文件、写入文件、网络操作等)的任务。

    • 在这种情况下,线程往往会因为等待IO操作而释放CPU执行权限,不会造成太多的CPU资源浪费。
    • 因此,使用多线程能够更好地处理IO密集型任务,避免了频繁切换进程的开销。
  • 当我们在一个进程中开启多个IO密集型线程时,大部分线程都处于等待状态,开启多个进程却不能提高效率,反而会消耗更多的系统资源。

    • 因此,在IO密集型任务中,使用多线程即可满足需求,无需开启多个进程。

(3)总结

  • 计算密集型任务使用多进程可以充分利用多核CPU的优势,而IO密集型任务使用多线程能够更好地处理IO操作,避免频繁的进程切换开销。
    • 根据任务的特性选择合适的并发方式可以有效提高任务的执行效率。

(四)GIL锁总结

  • GIL锁不是python的特点而是Cpython解释器的特点
  • GIL保证了解释器级别的数据安全
  • GIL会导致同一进程下的多个线程无法同时进行即 无法利用多核优势
  • 针对不同的数据需要加不同的锁处理
  • 解释型语言的通病:同一个进程下的多个线程无法利用多核优势
posted @   苏苏!!  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示