Python 低层多线程接口_thread的用法

news2024/11/13 15:10:45

_thread是python标准库中的一个低层多线程API,可以在进程中启动线程来处理任务,并且提供了简单的锁机制来控制共享资源的同步访问。本文就_thread模块的用法和特性做个简单的演示。

文章目录

  • 一、进程和线程的区别
  • 二、_thread模块的用法
    • 2.1 派生线程
    • 2.2 同步化访问控制
    • 2.3 子线程的退出控制
      • 2.3.1 通过sleep等待子线程运行结束
      • 2.3.2 通过锁的状态监测子进程结束
      • 2.3.3 通过共享变量监测子进程结束

一、进程和线程的区别

并行(多任务处理)是现代操作系统的基本特性,一个程序可以同时处理很多任务而不被阻塞。多任务处理的基本方式有两种:进程分支和线程派生。

进程分支就是从当前进程复制一个程序副本,程序中的内存副本,文件描述符等都会被复制,子进程改变一个全局对象只是修改本地的副本,不会影响到父进程。常用在启动独立的程序。

线程派生和进程分支类似,但是子线程依然在原进程中运行,所有的子线程都会共享进程中的全局对象,相对于进程分支,少了一个"复制"的操作,因此更加轻量化,且由于共享进程对象,相当于自带了线程间的通信机制(进程间的通信则需要借助管道,套接字,信号等外部工具)。常用在处理一些轻量级任务。

但这些共享对象的访问可能出现冲突,_thread也提供了锁机制来同步化对象访问,例如修改一个对象时,先获取锁,完成后再释放,这样就可以保证任意时间点,最多能有1个线程能修改这个共享对象,防止出现混乱。

二、_thread模块的用法

_thread模块中的start_new_thread可以开启一个新的线程并运行指定程序。

2.1 派生线程

当程序启动时,其实它就已经启动了一个线程,这个就是主线程。通过_thread.start_new_thread方法,传入一个函数对象和一个参数元组,就可以开启新线程来执行所传入的函数对象。简单的演示如下:

import _thread as thread

def func(id):
    print('我是 {} 号子线程'.format(id))

for i in range(5):
    thread.start_new_thread(func,(i,))

在这里插入图片描述

上面代码执行示意图如下:for循环执行了5次start_new_thread,这会开启5个线程,每个线程去执行func函数。因为线程是并行的,所以输出的顺序并不是0,1,2,3,4而是混乱的。注意3,4号子线程打印出现了重叠,这是因为它们共享一个标准输出流,而我们没有做同步化访问控制,因而它们同时打印输出。
在这里插入图片描述

2.2 同步化访问控制

由于子线程可以共享进程中的资源,这既是一个优势(方便线程间通信),也带来了共享资源访问的问题,如果多个子线程同时修改一个共享资源,那么就容易出现冲突,例如上面的输出重叠。

为了解决这类问题,_thread模块中的allocate_lock方法提供了一个简单的锁机制来控制共享访问,每次获取共享资源时先获取锁,可以保证任何时间点最多只有1个线程可以访问该资源。示例如下:

import _thread as thread

lock = thread.allocate_lock()    # 定义一个锁

def func(id):
    with lock:    # 用上下文管理器自动控制锁的获取和释放
        print('我是 {} 号子线程'.format(id))

for i in range(5):
    thread.start_new_thread(func,(i,))

在这里插入图片描述

with lock会自动管理锁对象的获取和释放,默认线程会一直等待直到锁的获取,如果想要更精细的控制可以使用下面的方式:

lock.acquire()    # 获取锁
print('我是 {} 号子线程'.format(id))
lock.release()    # 释放锁

lock.acquire()有2个可选的参数:blocking=True代表无法获取锁时等待,blocking=False代表如无法立刻获取锁则返回。timeout=-1代表等待的秒数(-1代表无限等待),此参数只有在blocking设置为True时才能指定。

2.3 子线程的退出控制

_thread派生子线程后,如果主线程运行完毕,而子线程依然在运行中,那么所有子线程就会随着主线程的退出而被终止。

我们在子进程中增加一个sleep来模拟长时间任务,让其运行时长超过主线程。将下面的代码保存到一个thread1.py中,以一个独立进程启动:

import _thread as thread, time

lock = thread.allocate_lock()    # 定义一个锁

def func(id):
    time.sleep(id)    # 子线程会睡眠,运行时长将超过主线程
    with lock:
        print('我是 {} 号子线程'.format(id))
    
for i in range(5):
    thread.start_new_thread(func,(i,))

print('主线程结束,退出...')    # 主线程退出时打印提示

在这里插入图片描述

可以看到,主线程打印了退出提示,但子线程却没有任何输出,这是因为主线程运行的时间非常短,当其退出时,所有子线程都终止了。这种情况显然不是我们想看到的。示例图如下:
在这里插入图片描述

2.3.1 通过sleep等待子线程运行结束

为了解决上面的问题,一个简单的解决方案可以在主线程中加一个sleep,让其等待一段时间再退出,但这个时间我们只能预估。在上面的代码基础上,增加一个time.sleep(3),让主线程退出前等待3秒,保存为thread2.py,再次执行:

import _thread as thread, time

lock = thread.allocate_lock()    # 定义一个锁

def func(id):
    time.sleep(id)    # 子线程会睡眠,运行时长将超过主线程
    with lock:
        print('我是 {} 号子线程'.format(id))
    
for i in range(5):
    thread.start_new_thread(func,(i,))

time.sleep(3)    # 主线程等待3秒再退出
print('主线程结束,退出...')    # 主线程退出时打印提示

在这里插入图片描述
可以看到部分子进程运行完毕,但还有部分子进程未完成,因此这种方法不是很准确,虽然你可以给一个足够长的时间来保证所有子进程运行结束,但如果进程长时间不结束,也会占用系统资源。

2.3.2 通过锁的状态监测子进程结束

_thread.allocate_lock除了可以控制共享对象的访问,还可以用来传递全局状态,下面定义了包含5把锁的列表,每个子线程执行完成后会去获取其中对应位置上的锁,在主线程中通过lock.locked()来检查是否所有的锁都被获取,当所有锁都被获取时(代表所有子线程都结束),主线程退出。将下面代码保存到thread3.py中,再次运行:

import _thread as thread, time

lock = thread.allocate_lock()    # 定义一个锁
exit_locks = [thread.allocate_lock() for I in range(5)]   # 定义一个列表,包含5把锁,对应稍后启动的5个子线程

def func(id):
    time.sleep(id)    # 子线程会睡眠,运行时长将超过主线程
    with lock:
        print('我是 {} 号子线程'.format(id))
    exit_locks[id].acquire()    # 执行完成后获取exit_locks中对应位置的锁

for i in range(5):
    thread.start_new_thread(func,(i,))

for lock in exit_locks:
    while not lock.locked(): pass    # lock.locked()检测锁是否已被获取
    
print('主线程结束,退出...')    # 主线程退出时打印提示

在这里插入图片描述
测试时可以发现,主线程会在所有子线程执行完毕后立刻退出,即不会提前导致子线程终止,也不会推迟浪费系统资源。

2.3.3 通过共享变量监测子进程结束

由于子线程可以共享进程中的变量,因此子线程中对共享对象的修改在主线程也可以看到,我们可以将上面的锁替换为简单的变量,可以达到相同的效果,下面使用一个共享列表,通过在子线程中修改变量值传递状态,将下面代码保存为thread4.py并执行:

import _thread as thread, time

lock = thread.allocate_lock()    # 定义一个锁
exit_flags = [False]*5   # 定义一个全局共享列表,包含5个布尔变量False

def func(id):
    time.sleep(id)    # 子线程会睡眠,运行时长将超过主线程
    with lock:
        print('我是 {} 号子线程'.format(id))
    exit_flags[id] = True    # 执行完成后将共享列表中对应位置的值改为True

for i in range(5):
    thread.start_new_thread(func,(i,))

while False in exit_flags:pass    # 检测列表中是否有False,如果全部为Ture,代表所有子线程执行完毕

print('主线程结束,退出...')    # 主线程退出时打印提示

在这里插入图片描述
可以看到主线程会等待子线程执行完毕后退出,这种方式相比上面可以节约锁分配的资源,看上去也更加简单。

以上即是_thread模块的基本用法。基于_thread模块还有高级的threading模块,_threading模块是基于类和对象的高级接口,并提供了额外的控制工具,例如threading.join()可以实现等待子进程退出。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2151903.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Percona发布开源DBaaS平台;阿里云RDS发布全球多活数据库(GAD);Redshift支持自然语言生成SQL

重要更新 1. 云栖大会于本周四/五在杭州举行,周五上午云栖主论坛阿里云数据库负责人李飞飞将发表《从数据到智能:DataAI驱动的云原生数据库》演讲,另外,还有多场次的数据库专场,感兴趣的可以现场或在线观看&#xff1a…

个人小结(2.0)

离谱,困扰着几周的问题今天偶然发现了解决方法。 问题如下:就是对应的模块引入爆红,但是单击进入引入的文件没有问题 然后它的提示是: 无法找到模块“../views/screen/index.vue”的声明文件。“c:/Users/10834/Desktop/0716_pro…

vue-使用refs取值,打印出来是个数组??

背景: 经常使用$refs去获取组件实例,一般都是拿到实例对象,这次去取值的时候发现,拿到的竟然是个数组。 原因: 这是vue的特性,自动把v-for里面的ref展开成数组的形式,哪怕你的ref名字是唯一的&#xff01…

DataX--Web:图形化界面简化大数据任务管理

在处理大数据任务时,频繁地修改配置文件或编写脚本可能会变得繁琐且容易出错。DataX Web提供了一个图形化界面,旨在简化这些操作,让用户通过直观的界面管理数据同步任务。 DataX Web简介 DataX Web是一个开源项目,它允许用户通过…

帧率和丢帧分析理论

一、丢帧问题概述 应用丢帧通常指的是在应用程序的界面绘制过程中,由于某些原因导致界面绘制的帧率下降,从而造成界面卡顿、动画不流畅等问题。以60Hz刷新率为例子,想要达到每秒60帧(即60fps)的流畅体验,每…

Python 序列( 列表 字典 元组 集合)

列表简介: 1.列表:用于存储任意数目、任意类型的数据集合。 2.列表是内置可变序列,是包含多个元素的有序连续的内存空间。列表的标准语法格式:a[10,20,30,40]其中,10,20,30,40这些称为:列表a的元素。 3.…

海外云市场分析

海外云市场数据洞察 2024 H1 季度数据 H1季度,全球云基础设施服务指数同比增长21%,达到798亿美元 (相比去年增加134亿美元),三大云服务提供商— AWS,微软Azure 和GCP 营收总增长率为24%,占总市场66%。 其中三大云厂商同比营收增长排序(2024 H1):微软 31%,G…

用户态缓存:环形缓冲区(Ring Buffer)

目录 环形缓冲区(Ring Buffer)简介 为什么选择环形缓冲区? 代码解析 1. 头文件与类型定义 1.1 头文件保护符 1.2 包含必要的标准库 1.3 类型定义 2. 环形缓冲区结构体 2.1 结构体成员解释 3. 辅助宏与内联函数 3.1 min 宏 3.2 is…

【Python报错已解决】xlrd.biffh.XLRDError: Excel xlsx file; not supported

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 专栏介绍 在软件开发和日常使用中,BUG是不可避免的。本专栏致力于为广大开发者和技术爱好者提供一个关于BUG解决的经…

最新LinPay码支付 免签支付系统源码 免授权版本(含搭建教程)

最新LinPay码支付 免签支付系统源码 免授权版本 服务集成商兼容市面所有易支付,兼容所有商城LinPay是专为个人站长打造的聚合免签系统,拥有卓越的性能和丰富的功能。它采用全新轻量化的界面UI,让您能更方便快捷地解决知识付费和运营赞助的难…

中间件知识点-消息中间件(Rabbitmq)一

消息中间件介绍 MQ的作用(优点)主要有以下三个方面: a.异步 b.解耦 c.削峰 MQ的作用(缺点)主要有以下三个方面: a.系统可用性降低 b.系统复杂度提高 c.存在消息一致性问题需要解决 备注: 引入MQ后系统的复杂度会大大提高。 以前服务之间可以…

移动开发(三):使用.NET MAUI打包第一个安卓APK完整过程

目录 一、修改AndroidManifest.xml 配置APP基本信息权限 二、修改项目属性调整输出Android包格式为APK 三、项目发布 四、APP分发 五、总结 之前给大家介绍过使用使用.NET MAUI开发第一个安卓APP,今天给大家介绍如何打包成APK,然后安装到安卓手机正常运行。这里还是沿用…

如何下载ComfyUI开发版

看B站视频,见用绘世可以下载ComfyUI开发版,而我又不想在电脑里放太多东西,于是研究了一下,如何直接从GitHub网站下载。具体步骤看图示。 看压缩包内容,应该直接解压覆盖就可以了,暂未有时间测试。

【JS】ESMoudle机制与符号绑定

前言 JS 模块化有两种方式,分别为:CommonJS 和 ESModule。与 CommonJS 不同,ESModule 是静态模块系统,意味着在代码编译阶段(而不是运行时),模块依赖关系就已经被确定。 ESModule 优势 更好地…

传知代码-多示例AI模型实现病理图像分类

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 本文将基于多示例深度学习EPLA模型实现对乳腺癌数据集BreaKHis_v1的分类。EPLA模型是处理组织病理学图像的经典之作。EPLA模型是基于多示例学习来进行了,那么多示例学习模型对处理病理学图像具有…

VCNet论文阅读笔记

VCNet论文阅读笔记 0、基本信息 信息细节英文题目VCNet and Functional Targeted Regularization For Learning Causal Effects of Continuous Treatments翻译VCNet和功能目标正则化用于学习连续处理的因果效应单位芝加哥大学年份2021论文链接[2103.07861] VCNet和功能定向正…

java数据结构----树

二叉查找树 二叉查找树的API设计 put方法的实现思想: public class BinaryTree<Key extends Comparable<Key>, Value> {private Node root;private int N;public int size(){return N;}public void put(Key key, Value value){root put(root,key,value);}public …

k8s 中的 Ingress 简介

一、关于 Ingress Ingress 是 K8s 中的一个 API 对象&#xff0c;用于管理和配置外部对集群内服务的访问。它可定义 HTTP 和 HTTPS 路由规则&#xff0c;将请求从集群外部的负载均衡器引导到相应的服务。Ingress 的灵活性使得我们能够实现高级的应用程序路由、SSL 终端和负载均…

一种新的电子邮件攻击方式:AiTM

新的攻击组利用合作伙伴组织之间的信任关系来绕过多重身份验证。 一种新的攻击方式开始出现&#xff0c;它利用合作伙伴组织之间的信任关系绕过多重身份验证。在一个利用不同组织之间关系的攻击中&#xff0c;攻击者成功地对四家或更多组织进行了商业电子邮件欺诈(BEC)攻击&…

中泰免签,准备去泰国旅游了吗?《泰语翻译通》app支持文本翻译和语音识别翻译,解放双手对着说话就能翻译。

泰国是很多中国游客的热门选择&#xff0c;现在去泰国旅游更方便了&#xff0c;因为泰国对中国免签了。如果你打算去泰国&#xff0c;那么下载一个好用的泰语翻译软件是很有必要的。 简单好用的翻译工具 《泰语翻译通》App就是为泰国旅游设计的&#xff0c;它翻译准确&#x…