python 并发编程系列(一)之GIL锁

一、GIL锁介绍
1.1、简介
GIL,全局解释器锁(Global Interpreter Lock,简称GIL)是CPython中的一个互斥锁,它确保在同一时刻只有一个线程能够执行Python字节码。这意味着在多线程环境下,Python解释器无法同时利用多个CPU核心进行并行执行
1.2、原理
当Python解释器运行Python代码时,它会获取GIL,然后执行相应的字节码指令。其他线程想要执行Python字节码时,必须先获取GIL,但只有在当前线程释放GIL后才能获得。因此,只有一个线程能够在任意时刻执行Python字节码,这就是GIL的工作原理。
1.3、GIL锁特性
  • GIL锁使得在同一时刻只有一个线程能够执行Python字节码,无法将多个线程映射到多个cpu上执行

  • GIL会根据执行的字节码行数或者时间片释放GIL

  • GIL在遇到IO操作时会主动释放,所以python并不是完全不适合多线程编程,如果是cpu密集型,确实不适合,如果是io密集型,例如网络请求或者文件处理,多线程和多进程效率是差不多的,所以python也是适合io密集型的多线程编程的

1.4、为什么需要GIL

GIL 本质上是一把锁,锁的引入是为了避免并发访问造成数据的不一致。CPython 中有很多定义在函数外面的全局变量,如果多个线程同时申请内存就可能同时修改这些变量,造成数据错乱。另外 Python 的垃圾回收机制是基于引用计数的,

另外Cpython中垃圾回收机制是基于引用计数的,当对象的引用计数为0时,就会进行垃圾回收,自动释放内存。

但是如果多线程的情况,引用计数就变成了一个共享的变量 Cpython是当下最流行的Python的解释器,使用引用计数来管理内存,在Python中,一切都是对象,引用计数就是指向对象的指针数,当这个数字变成0,则会进行垃圾回收,自动释放内存。但是问题是Cpython是线程不安全的。

考虑下如果有两个线程A和B同时引用一个对象obj,这个时候obj的引用计数为2;A打算撤销对obj的引用,完成第一步时引用计数减去1时,这时发生了线程切换,A挂起等待,还没执行销毁对象操作。

B进入运行状态,这个时候B也对obj撤销引用,并完成引用计数减1,销毁对象,这个时候obj的引用数为0,释放内存。如果此时A重新唤醒,要继续销毁对象,可是这个时候已经没有对象了。所以为了保证不出现数据污染,才引入GIL。

1.5、证明GIL会释放
上面说到GIL会根据执行的字节码行数或者时间片释放,在遇到IO操作时也会主动释放,所以python也适合部分多线程编程,这里证明下GIL锁会主动释放

例子一:

# @Author: JIWEI.SUN
# @Date: 2024/9/3 9:03

import threading
import dis

total = 0

def add():
    global total
    for _ in range(10000000):
        total += 1


def sub():
    global total
    for _ in range(10000000):
        total -= 1

thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=sub)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(total)


# 结论
多次执行这个脚本会发现,结果不是0,并且每次结果都不一样,说明GIL并不是在add函数执行完才释放的

例子二:

# @Author: JIWEI.SUN
# @Date: 2024/9/3 10:46

import threading
import time


def get_html(url):
    print("get_html start")
    time.sleep(6)
    print("get_html end")


def get_url(url):
    print("get_url start")
    time.sleep(4)
    print("get_url end")


thread1 = threading.Thread(target=get_html, args=('',))
thread2 = threading.Thread(target=get_url, args=('',))

time_start = time.time()

thread1.start()
thread2.start()

thread1.join()
thread2.join()

time_end = time.time()
print(f'执行花的时间为{time_end - time_start}秒')

# 多次执行结果都是6秒,而不是两个线程执行时间之和 6 + 4 = 10 秒,这个也可以证明如果不是cpu密集型程序,
python中多线程是可以提高效率的

参考文档:
https://www.elecfans.com/d/2252900.html

posted @ 2024-09-03 10:31  有形无形  阅读(681)  评论(0)    收藏  举报