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:内存管理(垃圾回收机制)
- 应用计数
- 标记清除
- 分代回收
- 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会导致同一进程下的多个线程无法同时进行即 无法利用多核优势
- 针对不同的数据需要加不同的锁处理
- 解释型语言的通病:同一个进程下的多个线程无法利用多核优势
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY