当前位置: 首页 > ops >正文

系统学习Python——并发模型和异步编程:基础实例-[使用线程实现旋转指针]

分类目录:《系统学习Python》总目录


在讨论线程以及如何避免GIL的过程中,Python贡献者Michele Simionato发布了一个示例,可以看作演示并发的“Hello World”示例,即能展示Python“一心二用”最简单的程序。Simionato的程序使用的是multiprocessing,经过我们修改,又分别实现了使用threadingasyncio的版本。

接下来的示例的想法很简单:启动一个函数,阻塞3秒,期间在终端展示字符动画,让用户知道程序正在运转,没有停滞。这个脚本在界面上的相同位置依次显示字符串\|/-中的各个字符,实现旋转指针动画。当缓慢的计算结束后,旋转指针那一行内容清空,显示结果:Answer: 42。下面的代码是线程threading版本:

import itertools
import time
from threading import Thread, Eventdef spin(msg: str, done: Event) -> None:  # 这个函数将在单独的线程中运行。done参数的值是一个threading.Event实例,一个用于同步线程的简单对象。for char in itertools.cycle(r'\|/-'):  # 这是一个无限循环,因为itertools.cycle一次产出一个字符,一直反复迭代字符串。status = f'\r{char} {msg}'  # 用文本实现动画的技巧:使用ASCII回车符('\r')把光标移到行头。print(status, end='', flush=True)if done.wait(.1):  # 如果其他线程设置了这个事件,则Event.wait(timeout=None)方法返回True;经过timeout指定的时间后,返回False。这里把暂停时间设为0.1秒,作用是把动画的帧率设为10fps。如果我们希望指针旋转快一些,那就把暂停时间值设置小一些。break  # 退出无限循环。blanks = ' ' * len(status)print(f'\r{blanks}\r', end='')  # 退出无限循环。def slow() -> int:time.sleep(3) # slow()由主线程调用。假设有一个API调用通过网络发送,速度很慢。调用sleep阻塞主线程,但是GIL已被释放,因此指针还能继续旋转。return 42def supervisor() -> int:  #  supervisor返回slow的结果.done = Event()  # threading.Event实例是协调main线程和spinner线程活动的关键,详见下面的分析spinner = Thread(target=spin, args=('thinking!', done))  # 创建一个Thread实例,target关键字参数的值是一个函数,args参数的值是一个元组,即传给target函数的位置参数。print(f'spinner object: {spinner}')  # 显示spinner对象。输出是<Thread(Thread-1,initial)>,其中initial是线程的状态,表示尚未启动。spinner.start()  # 启动spinner线程。result = slow()  # 调用slow,阻塞main线程。同时,次线程运行旋转指针动画。done.set()  # 把Event标志设为True,终止spin函数中的for循环。spinner.join()  # 等待,直到spinner线程结束。return resultdef main() -> None:result = supervisor()  # 运行supervisor函数。之所以分别定义main和supervisor两个函数,是为了与后续中的asyncio版本保持对应。print(f'Answer: {result}')if __name__ == '__main__':main()

通过上面的示例,我们了解到的最重要的一点是,调用time.sleep()阻塞所在的线程,但是释放GIL,其他Python线程可以继续运行

spinslow两个函数并发执行。主线程(启动程序时唯一的线程)将启动一个新线程运行spin,然后调用slow。Python没有提供终止线程的API,如果想终止线程,则必须向线程发送相应的消息。在Python中,协调线程的信号机制,使用threading.Event类最简单。Event实例有一个内部布尔标志,开始时为False。调用Event.set()可把这个标志设为True。这个标志为False时,在一个线程中调用Event.wait(),该线程将被阻塞,直到另一个线程调用Event.set(),致使Event.wait()返回True。使用Event.wait(s)设置一个暂停时间(单位为秒)​,经过这段时间后,Event.wait(s)调用返回False,如果另一个线程调用Event. set(),则立即返回True。下面的示例中的supervisor函数使用一个Event实例向spin函数发送退出信号。

main线程设置done事件后,spinner线程终将收到信号,干净退出。

参考文献:
[1] Mark Lutz. Python学习手册[M]. 机械工业出版社, 2018.
[2] 卢西亚诺·拉马略.流畅的Python 第2版(全2册) 编程语言[M].人民邮电出版社,2023.

http://www.xdnf.cn/news/15173.html

相关文章:

  • Ruby如何采集直播数据源地址
  • tiktok 弹幕 逆向分析
  • 后端定时过期方案选型
  • Linux/Ubuntu安装go
  • ​Windows API 介绍及核心函数分类表
  • MySQL 5.7.29升5.7.42实战:等保三漏洞修复+主从同步避坑指南
  • 一分钟快速了解Apache
  • Ether and Wei
  • 【android bluetooth 协议分析 07】【SDP详解 2】【SDP 初始化】
  • 详解缓存淘汰策略:LRU
  • python数据分析及可视化课程介绍(01)以及统计学的应用、介绍、分类、基本概念及描述性统计
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十一课——图像均值滤波的FPGA实现
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十课——图像gamma矫正的FPGA实现
  • C++11的整理笔记
  • 【LeetCode 热题 100】25. K 个一组翻转链表——迭代+哨兵
  • 【YOLOv8-obb部署至RK3588】模型训练→转换RKNN→开发板部署
  • Jenkins+Gitee+Docker容器化部署
  • super task 事件驱动框架
  • 用AI做带货视频评论分析【Datawhale AI 夏令营】
  • 冒泡排序和快速排序
  • 「Linux命令基础」文本模式系统关闭与重启
  • 【C/C++】动态内存分配:从 C++98 裸指针到现代策略
  • Linux操作系统之进程间通信:命名管道
  • 飞算JavaAI:给Java开发装上“智能引擎”的超级助手
  • vue入门学习教程
  • 车载诊断进阶篇 --- 关于网关转发性能引起的思考
  • 匿名函数作递归函数引用
  • uniapp制作一个视频播放页面
  • C++11中的std::minmax与std::minmax_element:原理解析与实战
  • WIFI协议全解析06:Beacon帧、Probe帧你必须懂,搞WiFi通信绕不开它们