线程是执行用的,队列是存放结果用的
目录
1 多线程 threading
1.1 基本用法
使用的库为threading,threading是python内置的库
我们每运行一个python文件相当于开启了一个进程,一个进程中有多个线程,如果使用了多线程我们可以在一段py代码中并行运行程序
我下面做个简单的例子
我们发现第一个任务运行的时候,第二个任务并没有等待第一个任务结束后再开始运行,而是开启了任务一后,直接开启任务二
1.2 循环使用
开始的时候会根据start()的顺序开始,结束的时候,线程各自执行不会等待其他的线程
1.3 等待至线程终止 join()
如果直接在start()后面打印的话,线程与print()会同时执行
如果加上了join()就会在线程结束的时候再打印
1.4 一个线程打断另一个循环的线程
一般来说我们的多线程都会写成无限循环然后根据条件跳出,代码大概是这样的
function()是需要被打断的任务。request请求模拟与下位机的交互
import time
import threading
import requests
def function():
while True:
try:
print('开始执行')
time.sleep(2)
print('执行完毕')
if stop == True:
break
except:
pass
def break_function():
global stop
while True:
try:
time.sleep(0.5)
response = requests.get('http://127.0.0.1:5000/stop').text
if response == '停吧':
stop = True
except:
pass
if __name__ == '__main__':
t1 = threading.Thread(target=function)
t2 = threading.Thread(target=break_function)
t2.daemon = True
t1.start()
t2.start()
服务是模拟下位机的
from flask import Flask
app = Flask(__name__)
@app.route('/stop',methods=['GET'])
def stop():
return '停吧1'
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=True)
当我把return的“停吧1”修改成“停吧”之后再启动一遍服务,那么function()任务就会被打断
(当我把return的“停吧1”修改成“停吧”之后再启动一遍服务)<- 这个就相当于是我手动按了一下按钮
线程可以分为守护线程和非守护线程两种。给线程的daemon设置为True,那么这个线程就会变成守护线程。如果不对线程的daemon进行设置的话,daemon默认为False,当daemon为False的时候线程为非守护线程。
一般一个程序只有一个非守护线程,当非守护线程终止时,所有的守护线程都会自动终止
2 队列 queue
队列常用于处理多线程产生的结果
2.1 先进先出 queue.Queue()
依次放入1,2,3三个值,然后取三次,发现可以成功的按顺序取出1,2,3
如果队列空了之后再取,就会无限等待
2.2 后进先出 queue.LifoQueue()
依次放入1,2,3三个值,然后取三次,发现可以成功的按顺序取出3,2,1
如果队列空了之后再取,就会无限等待
2.3 优先级队列
依次放入(2, 'B') ,(1, 'A') ,(3, 'C') 。元组的第一个值是优先级,数字越小优先级越高
如果队列空了之后再取,就会无限等待
2.4 队列阻塞
在队列中取内容默认是阻塞的,将block置为False可以避免阻塞。避免阻塞后,如果拿不着数据会继续执行后面的内容,而不是死等
比如在多线程预测的时候,预测速度比较慢。我想保证画面的流畅性,那么我就不能预测一张展示一张,我只能让未预测的图像混合预测图像来搞,预测好了就插进来一帧,这样就流畅了
3 线程池
我们现在搞一个4线程的线程池,然后搞五个任务
Pool()中间的值是线程的个数,pool.map的第一个参数是要执行什么函数,第二个参数是函数的参数。pool.close()是不让别的线程再进入线程池。pool.join()是等待线程池中的所有线程结束再执行下面的代码。
我们通过上面的结果可以看出,一开始执行了4个任务,等一个任务完成后自动开始执行第五个任务
4 协程 asyncio
协程就有点像javascript的执行方式,比如javascript中写
打印A
定时器2s后打印B
打印C
执行的结果就是 先打印A,然后打印C,最后打印B。本身JS还是单线程的,打印A与打印C都在单线程中,最后打印B相当于是异步协程
单线程+异步协程相比于多线程会减少占用的资源,但执行时间要长于多线程,且写起来也比较麻烦(平时建议不使用协程,别人写了你能看懂就行了)
asyncio在jupyter notebook环境下执行会有问题
很多库是基于asyncio的,比如爬虫的alohttp
4.1 协程函数
通过关键字async装饰的函数是一个协程函数
执行协程函数得到的返回值是一个协程对象
协程函数不能直接调用
4.2 事件循环
协程函数可以放在事件循环中使用
通过asyncio.get_event_loop()创建事件循环对象
通过run_until_complete()执行协程函数
事件循环相当于一个任务调度站,可以理解为一个列表
创建协程的协程任务会放入事件循环
已完成的任务会删除,未完成的任务会被自动分配资源执行
如果事件循环中的任务全部完成就中断循环
4.3 task对象
task就是执行协程函数的另一种方式,使用create_task()创建task对象
4.4 future对象
future也是是执行协程函数的一种方式,使用asyncio.ensure_future()创建future对象
4.5 回调函数
回调函数是执行完协程函数后执行的函数,通过add_done_callback()添加回调函数,回调函数中可以通过.result获取协程函数的返回值
可以给future对象添加
也可以给task对象添加
- future能干的task基本都能干,后面就不特别演示了
4.6 向事件循环中添加多个协程任务
首先你搞一个列表,列表里放future对象(或task对象),然后最后使用asyncio.wait()来执行future列表
4.7 await
python中的await与js中的await的含义完全不同。在python中await是挂起协程函数,比如
- 在js中await是等待await执行完毕之后再执行别的,python本身是同步的,通过async变成异步。JS本身是异步的,通过async变为同步
asyncio.sleep(1)相当于是异步的等待,一到sleep就通过await给它挂起,所以代码只运行了1s多,而不是3s多