Python 异步编程之——线程

article/2025/8/26 8:01:00

上一篇我们讲到,进程是一个相对独立的单元。而线程则是一个进程内单一顺序的控制流,是操作系统运行调度的最小单元。因此,一个进程可以包含多个线程。比如,播放视频时,画面和声音就是不同的线程在处理。

1.创建线程

(1)使用threading.Thread()直接创建

def fun1():print('任务1开始')time.sleep(2)print('任务1结束')def fun2():print('任务2开始')time.sleep(4)print('任务2结束')thread1 = threading.Thread(target=fun1)
thread2 = threading.Thread(target=fun2)# 守护线程,随主线程结束而结束,即不会打印“任务2结束”
thread2.setDaemon(True)# 启动任务
thread1.start()
thread2.start()# 主线程会等待本线程完成
thread1.join()

(2)继承Thread

class MyThread(threading.Thread):# 可以给线程取名字def __init__(self, name):super().__init__(name=name)# 需要实现的核心代码def run(self):print("这里写核心功能")t = MyThread('线程1')
t.start()
t.join()

2.线程间通讯

同一个进程下的线程,使用的是同一块内存。因此天然可以进行通信。此外,还可以使用队列。参看第3部分。

# 定义任意类型的全局变量都可以实现线程间通信
lst = []def fun1():global lstlst.append(1)def fun2():global lstdt = lst.pop()print(dt)thread1 = threading.Thread(target=fun1)
thread2 = threading.Thread(target=fun2)
thread1.start()
thread2.start()

3.数据安全和锁的概念

先直接看一个例子

import threadingdef add():global totalfor i in range(1000000):total += 1def desc():global totalfor i in range(1000000):total -= 1if __name__ == '__main__':total = 0thread1 = threading.Thread(target=add)thread2 = threading.Thread(target=desc)thread1.start()thread2.start()thread1.join()thread2.join()print(total)

(1)数据安全

上面代码表示,有两个任务,分别由两个线程完成,定义了一个全局变量total。add函数可以理解为领工资,余额增加;desc函数可以理解为消费,余额减少。循环相同次数,每次变量值相同。可以想见,最终结果应该是0。可当你执行代码时,你会发现,结果不仅不为0,甚至每次结果都不相同。这样的结果,不仅不是我们想要的,而且是危险的,因为它变得不可控。而要解释这个现象,就需要了解程序是怎么执行的。

我们以为的是这样的:

(2)字节码

但实际却不是如此。因为代码在执行时,会先被编译为字节码,下面展示一个简单函数的字节码

# 定义一个简单函数
def add(a):a += 1return a# 查看编译后的字节码用dis
print(dis.dis(add))"""3           0 LOAD_FAST                0 (a)2 LOAD_CONST               1 (1)4 INPLACE_ADD6 STORE_FAST               0 (a)4           8 LOAD_FAST                0 (a)10 RETURN_VALUE
None
"""

 可以看到,一个只包含变量自增功能的函数实际是分步骤完成的。加载变量a—>加载常量1—>执行加法—>保存a。程序在执行时是按照字节码行数和时间片在不同线程之间跳转的。不是我们看到的代码级更不是函数级来切换。因此,实际执行的可能过程示例如下:

 关键点就在线程2修改变量这里,线程2在线程1完成修改前就已经加载了变量total,虽然total的值被线程1修改了,但线程2不会再次加载total。因此,最终执行的是0-1=-1。因此,循环次数越大,结果就越不具有确定性。

(3)锁

原理搞清楚了,但问题还没解决。而解决线程数据不安全的方法之一就是引入锁的机制。

从锁的字面含义就已经表明其解决的方式了-通过锁来保护变量。举个生活中的例子,一个变量相当于一个房间,房门有锁。A拿到钥匙,进去了,反锁了门。那么B就只有等着。等A办完事出来,交出钥匙,B拿到钥匙才能进入,B也会把门反锁。具体代码实现如下:

"""使用互斥锁解决数据安全问题"""
import threadingdef add():global totalfor i in range(1000000):# 本线程在访问total这个变量时,其他线程不能访问lock.acquire()total += 1# 释放锁lock.release()def desc():global totalfor i in range(1000000):lock.acquire()total -= 1lock.release()if __name__ == '__main__':total = 0lock = threading.Lock()thread1 = threading.Thread(target=add)thread2 = threading.Thread(target=desc)thread1.start()thread2.start()thread1.join()thread2.join()print(total)

锁不是万能的,使用锁会带来其他问题,比如死锁。简单讲就是A获得了锁,但A需要B的执行结果。可是B还没有获得锁,无法为A提供结果。最终出现相互等待。此外,同一个线程多次请求同一个资源,也会引起死锁。

(4)使用队列进行通信

上面的方式很基本,为了更方便实现安全的通信,可以使用queue.Queue(内部实现了锁),使用方法和多进程的队列一致。

q = queue.Queue(maxsize=10)
q.put(1)
dt = q.get()
print(dt)

4.多线程和多进程

(1)多进程是开启了多个独立的单元,相互之间不影响。是真正的并行,同时进行。但是多进程是有代价的。第一,独立就意味着通信更麻烦;第二,进程与进程的切换是很消耗计算机资源的。

(2)多线程因为是共享内存,因此通信可以很方便。但太方便就意味着可能失控。因此又引入了锁的机制。实际上python自带一个大锁——GIL——全局解释器锁。GIL的存在使得同一个时刻,只有一个线程在一个CPU上执行字节码,且无法将多个线程映射到多个CPU上。实际的执行示意图如下:

 这样看来,多线程似乎是没有意义的。实际上,很多人都评论python的多线程是鸡肋。但,实际上。鸡肋吃起来还是有味道的。

 线程1发送请求后,会进入漫长的等待。如果这个时候能切换到线程2,即等待的过程我可以做其他事情。那这样就会比单线程效率更高。

(3)总结

多IO操作的任务使用多线程,多CPU的任务使用多进程。线程的切换代价低于进程,但对于某些任务可能仍然是不可接收的,那是否能在一个线程内完成任务的切换呢?这就是协程的概念了。


http://chatgpt.dhexx.cn/article/1ZTdJaHa.shtml

相关文章

深入理解Python异步编程

文章转载自: 驹说码事,内容有部分修改,仅用作学习,如有侵权,请联系删除。 很多朋友对异步编程都处于“听说很强大”的认知状态。鲜有在生产项目中使用它。而使用它的同学,则大多数都停留在知道如何使用 Tor…

Python异步编程Future对象详解

今天继续给大家介绍Python相关知识,本文主要内容是Python异步编程Future对象详解。 一、Python Future对象简介 在上文Python Task对象详解中,我们介绍到了Task对象,而Future对象是Task对象的基类,比Task更加底层。一个Future是…

Python异步编程之concurrent.futures中的Future对象详解

今天继续给大家介绍Python相关知识,本文主要内容是Python异步编程之concurrent的future对象详解。 一、concurrent.futures中Future对象简介 在前文Python异步编程Future对象详解种,我们介绍了asyncio中的Future对象。然而,在Python中&…

python网络编程实战_Python 异步网络编程实战

近年来 Python 的发展的非常迅速,“简单”、“高效”是 Python 吸引人的一大特色。在国内 Python 开发需求越来越大,Python 具有丰富强大的库,现在各个领域都在广泛使用,从 Web 开发,到运维开发,到机器学习…

Python异步编程实战入门:从概念到实战

概述 读者可前往我的博客获得更好的阅读体验 在Python中存在GIL机制,该机制保证了在Python中同时间内仅能运行一行代码,这导致了Python无法真正实现多线程,但可以通过多进程打破GIL限制,我们会在本文的最后讨论此内容。但Python…

Python异步编程详解

一、异步编程相关概念 1、I/O模型 IO操作实际过程涉及到内核和调用这个IO操作的进程。对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说&am…

python进阶(十):异步编程

Python异步编程详解 原文 | 大纲 | 首页 异步编程是一种编程范式,可以提高程序的并发性和响应性。在Python中,可以使用asyncio模块来实现异步编程。了解Python的异步编程对于编写高效和可扩展的程序非常重要。 使用asyncio模块 Python的asyncio模块提…

超简单的Python教程系列——异步

Python 3.5 引入了两个新关键字:​ ​async​ ​​和​ ​await​ ​​。这些看似神奇的关键字完全可以在没有任何线程的情况下实现类似线程的并发。在本教程中,我们将介绍异步编程的原因,并通过构建我们自己的小型异步类框架来说明Python的​…

python之异步编程

一、异步编程概述 异步编程是一种并发编程的模式,其关注点是通过调度不同任务之间的执行和等待时间,通过减少处理器的闲置时间来达到减少整个程序的执行时间;异步编程跟同步编程模型最大的不同就是其任务的切换,当遇到一个需要等…

[进阶] --- Python3 异步编程详解(史上最全篇)

[进阶] - Python3 异步编程详解:https://blog.csdn.net/lu8000/article/details/45025987 参考:http://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html 木风卜雨:https://blog.csdn.net/lu8000 1 什么是异步编程 1.1 阻塞…

tp5框架添加数据

tp5添加数据 添加 (js部分) 添加(php部分) 删除(js部分)

TP5框架后台排序

在写TP5框架开发的官网时,遇见需要为列表按倒序排列,若从数据库直接取出ID会因为该ID不连续,造成用户阅读困难。 因此查找了解决该问题的方法,因为涉及到分页,所以利用分页解决该问题 后台方法 前端页面数据 上图为正…

TP5框架查询数据获取结果集为数组的办法

TP5框架查询数据获取结果集为数组的办法 title: TP5框架查询数据获取结果集为数组的办法 tags: [TP5,模型,结果集,数组] 众所周知,使用TP5框架查询数据时,返回的结果集一般为对象,例如: $data \app\home\model\User::select();打…

tp5框架开发RESTful风格接口例子

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/example440982/article/details/80328087 tp5框架开发RESTful风格接口例子 time: 2018/5/15 author:…

TP5框架学习心得————(TP5框架的下载与其的基本目录结构)

一个好的软件直接影响到了我们的学习效率 TP5实在TP3.2的基础上改进的,相对与其他的框架个人觉得更适合与我们中国人毕竟是我们中国人自主研发的,想要学习起来其实也不难,只要看懂手册结合手册用一些小demo实现增、删、改、查基本上也就算入门了。 第一步:下载TP5框架 在…

tp5 框架使用Redis缓存,详解

1.小皮配置下载redis环境 1.打开小皮软件,选择软件管理找到redis下载, 2.找到网站域名,点击管理选中PHP扩展,选中redis 3.在首页启动redis,并查看配置 一般我们在小皮内启动redis后,查看配置参数是否正确…

tp5框架实现登录功能

TP5框架实现登录功能 安装TP框架 使用最简单的安装方式,直接从官网下载解压,将压缩包里的文件复制到项目目录下。 管网地址:http://www.thinkphp.cn/down.html 安装完框架的目录如图所示 添加控制器 在application\index\controller目录…

TP5框架目录解析

|-application 应用目录(几乎整个项目的内容都写在这里)|-index(这里的文件夹tp5叫做模块-----一般是前台模块,也可以根据需要需求修改成其他(例如:home),需要修改配置文件,修改默认模块、控制器、操作) 【注】:TP5默认只有一个index文件(模块)和一个控制层(con…

tp5登出_tp 5框架实现登录,登出及session登录状态检测功能示例

本文实例讲述了tp 5框架实现登录,登出及session登录状态检测功能。共享给大家供大家参考,详细如下: 1,访问http://localhost/tp5/admin.php时,判断有没有登录: 想法:写一个父类,继承…