GIL全局解释器锁

GIL全局解释器锁

(1)简介

  • 在 CPython 中,GIL(全局解释器锁)是一种机制,用于确保在同一时刻只有一个线程执行 Python 字节码。这个锁对于 Python 解释器来说是必要的,因为 CPython 的内存管理并不是线程安全的。当多个线程试图执行 Python 代码时,GIL 会确保同一时刻只有一个线程能够执行字节码指令,这样可以避免多线程并发执行时的数据竞争和内存一致性问题。

  • 简单来说,GIL 会在解释器级别上进行加锁,这意味着在同一时刻只有一个线程能够执行 Python 代码。即使在多核处理器上,由于 GIL 的存在,Python 解释器仍然只能够利用单个核心执行 Python 代码,因为即使有多个线程,它们也无法同时执行 Python 字节码指令。

  • 虽然 GIL 确保了线程安全,但它也带来了一些缺点。其中之一是它限制了 Python 在多核处理器上的并行性能,因为无法充分利用多个 CPU 核心。这也意味着在 CPU 密集型的任务中,Python 程序的性能可能受到影响。然而,对于 I/O 密集型任务,GIL 的影响通常较小,因为线程在等待 I/O 操作完成时会释放 GIL,允许其他线程执行。

from threading import Thread,Lock
import time

money = 100

lock = Lock()

def task():
    global money
    with lock:
        temp = money
        time.sleep(2)
        money = temp - 1


def main():
    t_list = []
    for i in range(5):
        t = Thread(target=task)
        t.start()
        t_list.append(t)

    for t in t_list:
        t.join()

    # 所有子线程结束后打印 money
    print(money)


if __name__ == '__main__':
    main()
# 95

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

  • Cpython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时进行

    • 同一个进程下的多个线程无法利用这一优势?

    • Python的多线程是不是一点用都没有?

  • Cpython中的内存管理不是线程安全的

    • ps:内存管理(垃圾回收机制)
      • 应用计数
      • 标记清除
      • 分代回收

(1)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}')


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}')


if __name__ == '__main__':
    main_t()  # 总耗时:>>>13.233189344406128
    main_p()  # 总耗时:>>>45.123342752456665

(2)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}')


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}')


if __name__ == '__main__':
    main_t()  # 总耗时:>>>33.62358331680298
    main_p()  # 总耗时:>>>2.073925733566284

(3)总结

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

  • 计算密集型任务主要是指需要大量的CPU计算资源的任务,其中包括执行代码、进行算术运算、循环等。
    • 在这种情况下,使用多线程并没有太大的优势。
    • 由于Python具有全局解释器锁(Global Interpreter Lock,GIL),在同一时刻只能有一条线程执行代码,这意味着在多线程的情况下,同一时刻只有一个线程在执行计算密集型任务。
  • 但是,如果使用多进程,则可以充分利用多核CPU的优势。
    • 每个进程都有自己独立的GIL锁,因此多个进程可以同时执行计算密集型任务,充分发挥多核CPU的能力。
    • 通过开启多个进程,我们可以将计算密集的任务分配给每个进程,让每个进程都独自执行任务,从而提高整体的计算效率。

(2)IO密集型任务(多线程)

  • IO密集型任务主要是指涉及大量输入输出操作(如打开文件、写入文件、网络操作等)的任务。
    • 在这种情况下,线程往往会因为等待IO操作而释放CPU执行权限,不会造成太多的CPU资源浪费。
    • 因此,使用多线程能够更好地处理IO密集型任务,避免了频繁切换进程的开销。
  • 当我们在一个进程中开启多个IO密集型线程时,大部分线程都处于等待状态,开启多个进程却不能提高效率,反而会消耗更多的系统资源。
    • 因此,在IO密集型任务中,使用多线程即可满足需求,无需开启多个进程。

(3)总结

  • 计算密集型任务使用多进程可以充分利用多核CPU的优势,而IO密集型任务使用多线程能够更好地处理IO操作,避免频繁的进程切换开销。
    • 根据任务的特性选择合适的并发方式可以有效提高任务的执行效率。
posted @ 2024-01-28 17:25  ssrheart  阅读(16)  评论(0编辑  收藏  举报