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

_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 threaddef 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 threadlock = 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, timelock = 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, timelock = 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, timelock = 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, timelock = thread.allocate_lock()    # 定义一个锁
exit_flags = [False]*5   # 定义一个全局共享列表,包含5个布尔变量Falsedef func(id):time.sleep(id)    # 子线程会睡眠,运行时长将超过主线程with lock:print('我是 {} 号子线程'.format(id))exit_flags[id] = True    # 执行完成后将共享列表中对应位置的值改为Truefor 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.xdnf.cn/news/1541027.html

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

相关文章

ElasticsearchRestTemplate DSL日志打印

ElasticsearchRestTemplate DSL日志打印 痛点解决方案打印基础文档查询信息打印最终DML语句 痛点 在使用 ElasticsearchRestTemplate 进行数据操作时,经常遇到的一个问题是线上问题排查困难。具体来说,在线上环境中,当出现问题时&#xff0c…

vue项目中——如何用echarts实现动态水球图

有时候UI的脑洞真的很大,总是设计出一些稀奇古怪的图形,但又不得不佩服他们的审美,确实还挺好看的。今天给大家介绍echarts如何实现动态水球图。如图所示: 实现步骤 一、引入 在vue页面中引入echarts,如未安装需要先…

Java面试篇基础部分-Synchronized关键字详解

Synchronized关键字用于对Java对象、方法、代码块等提供线程安全操作。Synchronized属于独占式的悲观锁机制,同时也是可重入锁。我们在使用Synchronized关键字的时候,可以保证同一时刻只有一个线程对该对象进行访问;也就是说它在同一个JVM中是线程安全的。   Java中的每个…

Golang | Leetcode Golang题解之第420题强密码检验器

题目: 题解: func strongPasswordChecker(password string) int {hasLower, hasUpper, hasDigit : 0, 0, 0for _, ch : range password {if unicode.IsLower(ch) {hasLower 1} else if unicode.IsUpper(ch) {hasUpper 1} else if unicode.IsDigit(ch)…

TLC/TK Adv学习笔记1 - Py版本+美化

Python下重点 tkinter.ttk 模块自 Tk 8.5 开始引入,它提供了对 Tk 风格的部件集的访问。 它还带来了一些额外好处包括在 X11 下的反锯齿字体渲染和透明化窗口(需要有 X11 上的混合窗口管理器)。 tkinter.ttk 的基本设计思路,就是…

【Python】探索 Errbot:多功能聊天机器人框架

不是旅行治愈了你,是你在路上放过了自己。 在当今的数字化时代,聊天机器人已成为企业与客户互动、提升工作效率和增加乐趣的重要工具。Errbot是一个高度可扩展的聊天机器人框架,它允许开发者使用Python轻松创建和定制机器人。本文将介绍Errb…

乐观锁、悲观锁及死锁

乐观锁、悲观锁 1.概念 悲观锁(悲观锁定):具有强烈的独占和排他特性。在整个执行过程中,将处于锁定状态。悲观锁在持有数据的时候总会把资源或者数据锁住,这样其他线程想要请求这个资源的时候就会阻塞,直到等到悲观锁把资源释放为…

如何基于Flink CDC与OceanBase构建实时数仓,实现简化链路,高效排查

本文作者:阿里云Flink SQL负责人,伍翀,Apache Flink PMC Member & Committer 众多数据领域的专业人士都很熟悉Apache Flink,它作为流式计算引擎,流批一体,其核心在于其强大的分布式流数据处理能力&…

DHCP协议原理(网络协议)

DHCP简介 定义 DHCP(动态主机配置协议)是一种网络管理协议,能够自动为局域网中的每台计算机分配IP地址及其他网络配置参数,包括子网掩码、默认网关和DNS服务器等。这一机制极大简化了网络管理,尤其在大型局域网中&am…

李沐 过拟合和欠拟合【动手学深度学习v2】

模型容量 模型容量的影响 估计模型容量 难以在不同的种类算法之间比较,例如树模型和神经网络 给定一个模型种类,将有两个主要因素: 参数的个数参数值的选择范围 VC维 线性分类器的VC维 VC维的用处 数据复杂度 多个重要因素: 样…

信息安全数学基础(20)中国剩余定理

前言 信息安全数学基础中的中国剩余定理(Chinese Remainder Theorem,简称CRT),又称孙子定理,是数论中一个重要的定理,主要用于求解一次同余式组。 一、背景与起源 中国剩余定理最早见于我国南北朝时期的数学…

鸿蒙小技巧

1.子调用父的方法 子组件 父组件 2.使用emitter实现孙子传爷 孙子组件 import emitter from ohos.events.emitter;let event: emitter.InnerEvent {eventId: 1,priority: emitter.EventPriority.HIGH};let eventData: emitter.EventData {data: {"state": true,…

R语言APSIM模型进阶应用与参数优化、批量模拟实践技术

随着数字农业和智慧农业的发展,基于过程的农业生产系统模型在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农田固碳和温室气体排放等领域扮演着越来越重要的作用。APSIM (Agricultural Production Systems sIMulator)模型是世界知名的作物生…

帧率和丢帧分析实践

一、识别丢帧 1、使用AppAnalyzer检测性能问题 首先使用AppAnalyzer工具进行性能问题检测,AppAnalyzer是DevEco Studio中提供的检测评分工具,用于测试并评价HarmonyOS应用或元服务的质量,能快速提供评估结果和改进建议,当前支持的…

Visual Studio 引入外部静态库与动态库

Windows Visual Studio 引入外部静态库与动态库 1.前言 在C开发中不可避免地要在自己的项目中引入外部库(OpenGL、OpenCV、OCC等),使用这些库都需要在项目中配置相应的属性才能正常开发编译。 2.引入 引入外部库主要引入三种文件&#xf…

C语言 | Leetcode C语言题解之第420题强密码检验器

题目&#xff1a; 题解&#xff1a; #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b))int strongPasswordChecker(char * password) {int n strlen(password);bool has_lower false, has_upper false, has_digit false;for …

高质量的翻译:应用程序可用性和成功的关键

在日益全球化的应用市场中&#xff0c;开发一款优秀的产品只是成功的一半。另一半&#xff1f;确保你的用户&#xff0c;无论他们在哪里或说什么语言&#xff0c;都能无缝理解和使用它。这就是高质量翻译的用武之地——不是事后的想法&#xff0c;而是应用程序可用性和最终成功…

攻防世界---->ReverseMe-120

做题学习笔记。 前言&#xff1a;目前遇见的reverse都是&#xff0c;已知密文&#xff0c;去求解明文flag&#xff1b; 此题逆着来&#xff0c;通过明文&#xff0c;去求解密文flag。 base加密的识别&#xff0c;还算容易。 那么&#xff0c;base解码的识别呢&#xff1f; 攻…

Java调用数据库 笔记06 (修改篇)

1.创建Java的普通class类 2.加载驱动 Class.forName("com.mysql.jdbc.Driver"); 3.驱动管理类调用方法进行连接&#xff0c;得到连接对象 DriverManager.getConnection(url, user, password); 其中设置参数&#xff1a; static final String url "jdbc:my…

聊天组件 Vue3-beautiful-chat 插槽

前言 Vue3-beautiful-chat 组件有四个插槽可以定制 一、user-avatar(头像) 首先是头像插槽,我们可以直接在 <beautiful-chat></beautiful-chat> 中间使用; 作用: 我们可以在用户头像上添加自定义样式,比如添加节日边框、可以使用首字母作为头像。。。 …