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中多线程是可以提高效率的

浙公网安备 33010602011771号