首页 > 其他分享 >GIL全局解释器锁

GIL全局解释器锁

时间:2024-05-23 13:29:38浏览次数:15  
标签:解释器 多线程 task 线程 time 密集型 全局 GIL

GIL全局解释器锁介绍
【1】官方解释
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple
native threads from executing Python bytecodes at once. This lock is necessary mainly

because CPython’s memory management is not thread-safe. (However, since the GIL
exists, other features have grown to depend on the guarantees that it enforces.)

结论:在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
【2】Python解释器版本
● Cpython
● Jpython
● Pypypython
● 但是普遍使用的都是Cpython解释器
【二】GIL锁与普通互斥锁的区别
【1】普通版 1.0
● 当睡了 0.1s 后
● 所有线程都去抢那把 GIL 锁住的数据,当所有子线程都抢到后再去修改数据就变成了 99
from threading import Thread
import time

money = 100

def work():
global money
temp = money
time.sleep(0.1)
money = temp - 1

def main():
task_list = [Thread(target=work) for i in range(100)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(money)

if name == 'main':
main()
# 99
【2】升级版 2.0
● 谁先抢到谁就先处理数据
from threading import Thread
import time

money = 100

def work():
global money

temp = money
money = temp -1

def main():
task_list = [Thread(target=work) for i in range(100)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(money)

if name == 'main':
main()
# 0
【3】终极版 3.0
● 自动加锁并解锁
● 子线程启动 , 后先去抢 GIL 锁 , 进入 IO 自动释放 GIL 锁 , 但是自己加的锁还没解开 ,其他线程资源能抢到 GIL 锁,但是抢不到互斥锁
● 最终 GIL 回到 互斥锁的那个进程上,处理数据
from threading import Thread, Lock
import time

mutex = Lock()

money = 100

def work():
global money

# 自动执行 加锁 再解锁操作
with mutex:
    temp = money
    # 只要进入 IO 会自动释放 GIL 锁
    time.sleep(0.1)
    money = temp -1

def main():
task_list = [Thread(target=work) for i in range(100)]
[task.start() for task in task_list]
[task.join() for task in task_list]
print(money)

if name == 'main':
main()
# 0
【三】GIL导致多线程无法利用多核优势
【1】 Cpython 解释器中 GIL
● 在 Cpython 解释器中 GIL 是一把互斥锁,用来阻止同一个进程下的多个线程的同时进行
○ 同一个进程下的多个线程无法利用这一优势?
○ Python的多线程是不是一点用都没有?
● 因为在 Cpython 中的内存管理不是线程安全的
○ ps:内存管理(垃圾回收机制)
■ 应用计数
■ 标记清除
■ 分代回收
【2】Python的多线程是不是一点用都没有?
● 同一个进程下的多线程无法利用多核优势,是不是就没用了
● 多线程是否有用要看情况
○ 单核
■ 四个任务(IO密集型/计算密集型)
○ 多核
■ 四个任务(IO密集型/计算密集型)
(1)计算密集型
一直处在计算运行中
● 每个任务都需要 10s
○ 单核
■ 多进程:额外消耗资源
■ 多线程:减少开销
○ 多核
■ 多进程:总耗时 10s
■ 多线程:总耗时 40s+
from multiprocessing import Process
from threading import Thread
import time
import os

def work_calculate(n=1000):
result = 1
for i in range(1, n + 1):
result *= i
# 为了确保计算确实发生并防止编译器优化,可以考虑使用结果
_ = sum(int(digit) for digit in str(result))

def timer(func):
def inner(args, **kwargs):
# 定义起始时间
start_time = time.time()
func(
args, **kwargs)
# 计算总耗时
print(f'总耗时:>>>{time.time() - start_time}')

return inner

@timer
def main(cls):
# 获取当前CPU运行的个数
print(f'当前正在使用的CPU个数 :>>>> {os.cpu_count()}')

# 创建任务列表
task_list = [cls(target=work_calculate) for i in range(400)]
# 启动任务
[task.start() for task in task_list]
# 阻塞任务
[task.join() for task in task_list]

if name == 'main':
# 计算密集型
# 多进程下的耗时
# main(cls=Process)
# 当前正在使用的CPU个数 :>>>> 12
# 总耗时:>>>15.169148921966553

# 多线程下的耗时
main(cls=Thread)
# 当前正在使用的CPU个数 :>>>> 12
# 总耗时:>>>5.78026819229126

(2)IO密集型
存在多个 IO 阻塞切换操作
● 每个任务都需要 10s
○ 多核
■ 多进程:相对浪费资源
■ 多线程:更加节省资源
from multiprocessing import Process
from threading import Thread
import time
import os

def work_io():
time.sleep(2)

def work_calculate(n=1000):
result = 1
for i in range(1, n + 1):
result *= i
# 为了确保计算确实发生并防止编译器优化,可以考虑使用结果
_ = sum(int(digit) for digit in str(result))

def timer(func):
def inner(args, **kwargs):
# 定义起始时间
start_time = time.time()
func(
args, **kwargs)
# 计算总耗时
print(f'总耗时:>>>{time.time() - start_time}')

return inner

@timer
def main(cls):
# 获取当前CPU运行的个数
print(f'当前正在使用的CPU个数 :>>>> {os.cpu_count()}')

# 创建任务列表
task_list = [cls(target=work_io) for i in range(400)]
# 启动任务
[task.start() for task in task_list]
# 阻塞任务
[task.join() for task in task_list]

if name == 'main':
# 计算密集型
# 多进程下的耗时
main(cls=Process)
# 当前正在使用的CPU个数 :>>>> 12
# 计算密集型 总耗时:>>>14.954952001571655
# IO密集型 总耗时:>>>17.02257513999939

# 多线程下的耗时
# main(cls=Thread)
# 当前正在使用的CPU个数 :>>>> 12
# 计算密集型 总耗时:>>>0.2667570114135742
# IO密集型 总耗时:>>>2.039600133895874

【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的能力。
○ 通过开启多个进程,我们可以将计算密集的任务分配给每个进程,让每个进程都独自执行任务,从而提高整体的计算效率。
(2)IO密集型任务(多线程)
● IO密集型任务主要是指涉及大量输入输出操作(如打开文件、写入文件、网络操作等)的任务。
○ 在这种情况下,线程往往会因为等待IO操作而释放CPU执行权限,不会造成太多的CPU资源浪费。
○ 因此,使用多线程能够更好地处理IO密集型任务,避免了频繁切换进程的开销。
● 当我们在一个进程中开启多个IO密集型线程时,大部分线程都处于等待状态,开启多个进程却不能提高效率,反而会消耗更多的系统资源。
○ 因此,在IO密集型任务中,使用多线程即可满足需求,无需开启多个进程。
(3)总结
● 计算密集型任务使用多进程可以充分利用多核CPU的优势,而IO密集型任务使用多线程能够更好地处理IO操作,避免频繁的进程切换开销。
○ 根据任务的特性选择合适的并发方式可以有效提高任务的执行效率。
【四】GIL特点总结
● 1.GIL 不是python的特点而是Cpython解释器的特点
● 2.GIL 保证解释器级别的数据的安全
● 3.GIL会导致同一个进程下的多个线程的无法同时进行即无法利用多核优势
● 4.针对不同的数据还是需要加不同的锁处理
● 5.解释型语言的通病:同一个进程下的多个线程无法利用多核优势

标签:解释器,多线程,task,线程,time,密集型,全局,GIL
From: https://www.cnblogs.com/zenopan101861/p/18208229

相关文章

  • 全局设置element-ui Dialog组件的close-on-click-modal属性为false
    前言element组件库的Dialog对话框默认可以通过点击modal关闭Dialog,即点击空白处弹框可关闭。属性  importElementUIfrom'element-ui'import'element-ui/lib/theme-chalk/index.css'//默认主题//全局修改默认配置,点击空白处不能关闭弹窗ElementUI.Dialog.......
  • 数据库全局修改 utf8mb4_general_ci
    --修改数据库字符集和校对规则ALTERDATABASEyour_database_nameCHARACTERSET=utf8mb4COLLATE=utf8mb4_general_ci; --修改表的字符集和校对规则ALTERTABLEyour_table_nameCONVERTTOCHARACTERSETutf8mb4COLLATEutf8mb4_general_ci; --修改列的......
  • 全局句柄表
    1.全局句柄表句柄介绍句柄一共有3种全局句柄表进程线程句柄表私有句柄表进程私有的窗口句柄全局句柄表在全局句柄表种只有进程和线程对象OpenProcess/OpenThread权限是否继承和id获取创建进程的handle的流程:获取到进程id取全局句柄表种找到进程对象,把进程对象......
  • django-drf 全局封装
    封装全局异常common_exceptions.pyfromrest_framework.viewsimportexception_handlerfromrest_framework.responseimportResponsedefcommon_exception_handler(exc,context):#记录日志request=context.get('request')view=context.get(�......
  • 全局异常处理和jwt介绍与使用
    全局异常处理和jwt介绍与使用1.全局异常处理#APIView的dispatch的时候--》三大认证,视图类的方法中--》出了异常--》被异常捕获--》都会执行一个函数:#只要出了异常,都会执行dispatch中的这句,这个函数response=self.handle_exception(exc)#handle_exception源码分析d......
  • ASP.NET Core的全局拦截器(在页面回发时,如果判断当前请求不合法,不执行OnPost处理器)
    ASP.NETCoreRazorPages中,我们可以在页面模型基类中重载OnPageHandlerExecuting方法。下面的例子中,BaseModel继承自PageModel,是所有页面模型的基类。推荐方案:在BaseModel.cs中,重载OnPageHandlerExecuting方法(看下面代码中的注释):publicoverridevoidOnPageHandlerExecuting......
  • H5 新增的全局属性
    属性名功能contenteditable表示元素可否被用户编辑,可选值:true、falsedraggable表示元素可以拖动,可选值:true、falsehidden隐藏元素和CSSdisplaynone的是一样的spelicheck拼写检查,可选值:true、falsecontextmenu规定元素上下文菜单,在用户点击元素时显示......
  • 全局变量和局部变量以及静态修饰作用
    1,全局变量和全局静态变量a、全局变量:全局变量存放在静态存储区,作用域是全局(对比下面添加static),整个声明周期都可以使用,其他文件如需要使用,需要添加externb、全局静态变量(static):分配的内存与全局变量一样,也是在静态存储内存上,其生命周期也是与整个程序同在的,从程序开始到结束一......
  • Agile PLM数据库表结构(Oracle)
    刚进公司,任务是接管PLM系统,但是还在给外包团队开发,没有代码。无妨先看业务和数据库,ok,业务看不懂,只能先看数据库,数据库没有数据字典,这个系统没有任何文档产出......练手时发现数据库类型是Oracle,面对百度不成问题,数据字典只能看前端然后去数据库里面一个个找着对应自己整理了,纯折磨......
  • nginx常用全局变量
    nginx常用全局变量$args请求中的参数,如www.123.com/1.php?a=1&b=2的$args就是a=1&b=2$content_lengthHTTP请求信息里的"Content-Length"$conten_typeHTTP请求信息里的"Content-Type"$document_rootnginx虚拟主机配置文件中的root参数对应的值$document_uri当前请求......