Python的协程与GIL
发布日期:2021-10-03 22:59:27 浏览次数:69 分类:技术文章

本文共 4806 字,大约阅读时间需要 16 分钟。

1 协程是什么?

我们知道多线程 / 多进程模型,是解决并发问题的经典模型之一。但是随刻客户端数量达到一定量级,进程上下文切换占用了大量的资源,线程也顶不住如此巨大的压力,对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。

协程 ,又称为微线程,它是实现多任务的另一种方式,只不过是比线程更小的执行单元。因为它自带CPU的上下文,这样只要在合适的时机,我们可以把一个协程切换到另一个协程。协程,只使用一个线程,在一个线程中规定某个代码块执行顺序。线程是抢占式的调度,而协程是协同式的调度,也就是说,协程需要自己做调度。

通俗的理解: 在一个线程中的某个函数中,我们可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的 ,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。

协程与线程的差异:在实现多任务时, 线程切换从系统层面看远不止保存和恢复CPU上下文这么简单。操作系统为了程序运行的高效性,每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作,所以线程的切换非常耗性能。但是协程的切换只是单纯地操作CPU的上下文,性能就高多了。

2 协程有什么用?

在别的语言中协程意义不大,多线程可解决问题,但是python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,如果一个线程里面I/O操作特别多,协程就比较适用;

在python中多线程的执行情况如下图:

 

3 python中的GIL

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python的一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock,官方给出的解释:

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.)

GIL是一个防止多线程并发执行机器码的一个Mutex,就是一个全局锁。GIL的存在会对多线程的效率有不小影响。

4 例子

python3可以使用asyncio 和 async / await 的方法实现协程

例子1

 

import asyncioimport timeasync def sub_function(str):    print(' {}'.format(str))    sleep_time = int(str.split('_')[1])    await asyncio.sleep(sleep_time)    print('OK {}'.format(str))async def main(strs):    for str in strs:        await sub_function(str)# asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.runt0 = time.time()asyncio.run(main(['str_1', 'str_2', 'str_3', 'str_4']))#如果python版本低于3.7,使用下面的代码#asyncio.get_event_loop().run_until_complete(main(['str_1', 'str_2', 'str_3', 'str_4']))t1 = time.time()print("Total time running: %s seconds" %(str(t1 - t0)))

 执行结果:

$ python3 ./coroutine.py  str_1OK str_1 str_2OK str_2 str_3OK str_3 str_4OK str_4Total time running: 10.013939380645752 seconds

一共是 10s 和我们顺序分析 分别等待 1 2 3 4 秒 好像没有什么提升作用,主要是因为使用了await 来调用实现,但是await 执行的效果,和 Python 正常执行是一样的,也就是说程序会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续,await 是同步调用,相当于我们用异步接口写了个同步代码,所以运行时间没有得到提升。

例子2

import timeimport asyncioasync def sub_function(str):    print(' {}'.format(str))    sleep_time = int(str.split('_')[1])    await asyncio.sleep(sleep_time)    print('OK {}'.format(str))async def main(strs):    #tasks = [asyncio.create_task(sub_function(str)) for str in strs]    tasks = [asyncio.get_event_loop().create_task(sub_function(str)) for str in strs]    for task in tasks:        await task    ‘’‘    # *tasks 解包列表,将列表变成了函数的参数;与之对应的是, ** dict 将字典变成了函数的参数。    await asyncio.gather(*tasks)    ’‘’# asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.runt0 = time.time()#asyncio.run(main(['str_1', 'str_2', 'str_3', 'str_4']))asyncio.get_event_loop().run_until_complete(main(['str_1', 'str_2', 'str_3', 'str_4']))t1 = time.time()print("Total time running: %s seconds" %(str(t1 - t0)))

执行结果:

$ python3 ./coroutine2.py  str_1 str_2 str_3 str_4OK str_1OK str_2OK str_3OK str_4Total time running: 4.002293109893799 seconds

使用create_task任务创建后很快就会被调度执行,这样,我们的代码也不会阻塞在任务这里。结果显示,运行总时长等于运行时间最长的一句。

例子3

处理exception 或者协程超时的处理

import timeimport asyncioasync def worker_1():    await asyncio.sleep(1)    return 1async def worker_2():    await asyncio.sleep(2)    return 2 / 0async def worker_3():    await asyncio.sleep(3)    return 3async def main():    '''    #python3.7 use this code    task_1 = asyncio.create_task(worker_1())    task_2 = asyncio.create_task(worker_2())    task_3 = asyncio.create_task(worker_3())    '''    task_1 = asyncio.get_event_loop().create_task(worker_1())    task_2 = asyncio.get_event_loop().create_task(worker_2())    task_3 = asyncio.get_event_loop().create_task(worker_3())    await asyncio.sleep(2)    task_3.cancel()    res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)    print(res)# asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.runt0 = time.time()#asyncio.run(main())asyncio.get_event_loop().run_until_complete(main())t1 = time.time()print("Total time running: %s seconds" %(str(t1 - t0)))

 执行结果:

$ python3 ./coroutine3.py [1, ZeroDivisionError('division by zero',), CancelledError()]Total time running: 2.0031282901763916 seconds

return_exceptions=True 用于内部处理exception。这个参数默认值为False, 如果不设置这个参数,错误就会完整地 throw 到执行层,从而需要 try except 来捕捉,

这也就意味着其他还没被执行的任务会被全部取消掉。为了避免这个情况,我们将 return_exceptions 设置为 True 即可。

5 Trouble shooting

1 使用asyncio.run()会报错:

AttributeError: module 'asyncio' has no attribute 'run'

分析:

   Python 版本低于 3.7

 

     asyncio.run(main())  换成 asyncio.get_event_loop().run_until_complete(main())

 

reference:

 

转载地址:https://blog.csdn.net/lclfans1983/article/details/110921279 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:ROS 使用GDB调试详解
下一篇:Influxdb python API使用说明

发表评论

最新留言

很好
[***.229.124.182]2024年03月27日 03时06分35秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

c 泛型与java泛型_C ++和Java中的“泛型”类型之间有什么区别? 2019-04-21
java 返回实体对象_java 封装返回结果实体类 返回结果以及错误信息 2019-04-21
java web 防止sql注入攻击_JavaWeb防注入知识点(一) 2019-04-21
java ssm 异常分类_SSM项目常见的异常与处理提示(一) 2019-04-21
java定义矩形类_Java定义矩形类 2019-04-21
java变量怎么变常量_Java的常量与变量是什么?怎么学习呀? 2019-04-21
java开发招聘试题_客户化开发招聘试题-Java开发.doc 2019-04-21
java jdk win10 1335_win10下安装java jdk,tomcat 2019-04-21
java list二分查找_java中的ArrayList和LinkedList的二分查找速度比 | 学步园 2019-04-21
php中的变量名称用什么表示,PHP变量,方法,类等名称中的有效字符是什么? 2019-04-21
pic32mx是什么cpu_PIC32MX单片机外设库使用(Ⅰ)- 系统时钟及I/O口基本设置 2019-04-21
用c 在mysql上存图片_C 批量保存图片进 mysql 利用MYSQL_BIND插入longblob 2019-04-21
mysql 1045 28000_mysql报关于用户密码1045(28000),几种处理方法 (zhuan) 2019-04-21
solr比mysql的优势_Solr与Elasticsearch的优缺点比较总结和归纳 2019-04-21
华为博士招聘上机考试题目_牛客网-华为-2020届校园招聘上机考试-3 2019-04-21
python中for可以做变量名吗_Python中使用动态变量名的方法 2019-04-21
mysql 日期转换天数_MySQL 日期操作 增减天数、时间转换、时间戳 2019-04-21
java对象去重复_JAVA中List对象去除重复值的方法 2019-04-21
java bss_[转] .bss段和.data段的区别 2019-04-21
java上传图片损坏_大神求助 上传图片后 图片损坏 2019-04-21