https://blog.csdn.net/tjcwt2011/article/details/80885410
https://blog.csdn.net/m0_38011218/article/details/81938261
https://www.runoob.com/w3cnote/python-dynamic-var.html
https://blog.csdn.net/k_koris/article/details/82903363
https://blog.csdn.net/w18306890492/article/details/82872571
https://www.liaoxuefeng.com/wiki/897692888725344/966405998508320
https://www.cnblogs.com/houdj/p/12100159.html
import datetime
def run(arg):
# for i in range(3):
# time.sleep(1)
msg = "I'm ok %d" % arg
print(msg, threading.currentThread())
l=[]
names=locals()
for j in range(5):
t = threading.Thread(target=run, args=(j,))
dateWithTime = str(datetime.datetime.now())
names['t'+dateWithTime] = t
l.append(names['t'+dateWithTime])
print('lllllllllllllll', l)
for i in l:
i.start()
import threading
import time
def listen_music(name):
while True:
time.sleep(1)
print(name,"正在播放音乐")
def download_music(name):
while True:
time.sleep(2)
print(name,"正在下载音乐")
p2 = threading.Thread(target=download_music,args=("网易云音乐",))
if __name__ == '__main__':
p1 = threading.Thread(target=listen_music,args=("网易云音乐",))
p2 = threading.Thread(target=download_music,args=("网易云音乐",))
p1.start()
p2.start()
协程相关:
https://www.cnblogs.com/cheyunhua/p/11017057.html
CPU密集型也叫计算密集型,指的是系统的硬盘,内存性能相对CPU要好很多,特点是要进行大量的计算,消耗CPU资源,比如计算圆周率,对视频进行高清解码等等,全靠CPU的运算能力,这种计算密集型的任务虽然也可以用多任务来完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就会越低,所以,要更高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数.计算密集型任务由于主要消耗CPU资源,所以,代码运行效率至关重要,python这样的脚本语言运行效率很低,所以,对于计算密集型任务,最好是用C语言来编写
IO密集型指的是系统的CPU相对于硬盘,内存要好很多,涉及到网络,磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成,因为IO的速度远远低于CPU和内存的速度,对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度,常见的大部分任务都是IO密集型任务,比如web应用. 因此,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差.
IO操作一般是指网络请求,文件读取等
python通过yield提供了对协程的基本支持,但是不完全,而第三方的gevent为python提供了比较完善的协程支持。gevent是第三方库,通过greenlet实现协程,其基本思想是:
当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其它的let,等到IO操作完成,再在适当的时候切换回来继续执行,由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO.所以在进行IO密集型程序时(如爬虫),使用gevent可以提高效率,有效利用网络延迟的时间去执行其他任务.
注意:使用gevent,可以获得极高的并发性能,但gevent只能在Unix/Linux下运行,在windows下不保证可以正常安装和运行.
学习异步IO模型之前,需要了解协程
协程又称为微线程, 纤程, Coroutine
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用C,C执行完毕返回,B 执行完毕返回,最后A执行完毕
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序
子程序调用总是一个入口一次返回,调用顺序是明确的,而协程的调用和子程序不同
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行
注意:在一个子程序中中断,去执行其它子程序,不是函数调用,有点类似CPU的中断,比如子程序A. B
import time
def task_1():
while True:
print("--This is task 1!--before")
yield
print("--This is task 1!--after")
time.sleep(0.5)
def task_2():
while True:
print("--This is task 2!--before")
yield
print("--This is task 2!--after")
time.sleep(0.5)
if __name__ == "__main__":
t1 = task_1() # 生成器对象
t2 = task_2()
# print(t1, t2)
while True:
next(t1) # 1、唤醒生成器t1,执行到yield后,保存上下文,挂起任务;下次再次唤醒之后,从yield继续往下执行
print("\nThe main thread!\n") # 2、继续往下执行
next(t2) # 3、唤醒生成器t2,....
协程的特点在于是一个线程执行,那和多线程比较,协程有何优势:
- 最大的优势在于协程有极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
- 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多
- 协程是一个线程执行,要想利用多核CPU,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,而获得极高的性能
python对协程的支持是通过generator来实现的,在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值
但是python的yield不但可以返回一个值,它还可以接收调用者发出的参数
def fab(max):
n, a, b = 0, 0, 1
while n < max:
yield b
# print b
a, b = b, a + b
n = n + 1
for i in fab(5):
print('iiiiiiiiiii', i)
print(isgeneratorfunction(fab))
iiiiiiiiiii 1
iiiiiiiiiii 1
iiiiiiiiiii 2
iiiiiiiiiii 3
iiiiiiiiiii 5
yield的作用就是将一个函数变成generator,带有yield的函数不再是一个普通函数,python解释器会将其视为一个generator,调用fab(5)不会执行fab函数,而是返回一个iterable对象,在for循环执行时,每次循环都会执行fab函数内部的代码,执行到yield b时,fab函数就会返回一个迭代值,下次迭代时,代码从yield b的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前完全一样,于是函数继续执行,知道再次遇到yield
注意:fab和fab(5)是不一样的,fab是一个generator function,而fab(5)是调用fab返回的一个generator,好比类的定义和类的实例的区别
也可以手动调用fab(5)的next()方法,因为fab(5)是一个generator对象,该对象具有next()方法,这样我们就可以清楚地看到fab的执行流程。
每次调用fab函数都会生成一个新的generator实例,各实例互不影响:
f1=fab(3)
f2.fab(5)
print(f1.next())
print(f2.next())
如果直接对文件对象调用read()方法,会导致不可预测的内存占用,好的方法是利用固定长度的缓冲区来不断读取文件内容,通过yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:
def read_file(fpath):
BLOCK_SIZE = 1024
with open(fpath, 'rb') as f:
while True:
block = f.read(BLOCK_SIZE)
if block:
yield block
else:
return
ff=read_file('./deve.txt')
print(ff.__next__())
print(ff.__next__())
print(ff.__next__())
print(ff.__next__())
理解yield:
-
通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,它的缺陷是所有的数据都在内存中,如果由海量数据的话将会非常耗内存
-
生成器是可以迭代的,但只可以读取它一次,因为用的时候才生成,比如mygenerator = (x*x for x in range(3)), 注意这里用到了(),它就不是数组,而上面的例子是[]
-
send(msg)和next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数
-
send(msg)与next()区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值,换句话说,就是send可以强行修改上一个yield表达式值,比如函数中有一个yield赋值 a=yield 5,第一次迭代到这里会返回5,a还没有赋值,第二次迭代时,使用send(10), 那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10
-
第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield,可以任务next()等同于send(None)
以下是通过生成器实现生产者-消费者模型,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高
def consumer():
print('--4、开始执行生成器代码--')
response = None
while True:
print('--5、yield,中断,保存上下文--')
n = yield response # 4、yield,中断,保存上下文
print('--8、获取上下文,继续往下执行--')
if not n:
return
print("[Consumer]: consuming {} ..".format(n))
response = "OK"
def produce(c):
print("--3、启动生成器,开始执行生成器consumer--")
c.send(None) # 3、启动生成器,开始执行生成器consumer
print("--6、继续往下执行--")
n = 0
while n < 5:
n += 1
print("[Producer]: producing {} ..".format(n))
print("--7、第{}次唤醒生成器,从yield位置继续往下执行!--".format(n+1))
r = c.send(n) #第二次唤醒生成器
print("--9、从第8步往下--")
print("[Producer]: consumer return {} ..".format(r))
c.close()
if __name__ == "__main__":
c = consumer() # 1、定义生成器,consumer并不执行
produce(c) # 2、运行produce函数
python2.x协程
类库: yield gevent
Gevent是第三方库,通过greenlet实现,其基本思想是:当一个greenlet遇到IO操作时,就自动切换到其他的greenlet,等到IO操作完成,再适当的时候切换回来继续执行,gevent自动切换协程,保证总有greenlet在运行,而不是等待IO
import gevent
import requests
from gevent import monkey
monkey.patch_all()
def get_body(i):
print(f'start-{i}')
requests.get('http://www.baidu.com').content.decode()
print(f'end-{i}')
# tasks = [gevent.spawn(get_body, i) for i in range(3)]
tasks = [gevent.spawn(get_body, 1), gevent.spawn(get_body, 2), gevent.spawn(get_body, 4)]
gevent.joinall(tasks)
python3.x协程
ayncio
import asyncio
@asyncio.coroutine
def get_body(i):
print(f'start-{i}')
yield from asyncio.sleep(1)
print(f'end-{i}')
loop = asyncio.get_event_loop()
tasks = [get_body(i) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
start-1
start-3
start-0
start-2
start-4
end-1
end-0
end-4
end-3
end-2
- 它的作用是和Gevent一样,遇到IO操作的时候,自动切换上下文
- 不同的是,它在tasks的操作
- tasks先把这5个参数不同的函数全部加载进来,在执行任务,在这个过程中,任务执行时无序的
- @asyncio.coroutine把一个generator标记为coroutine类型,然后把这个coroutine扔到eventloop中执行
- yield from语法让我们方便地调用另一个generator,由于asyncio.sleep()也是一个coroutine,县城不会等待,直接中断执行下一个消息循环,当asyncio.sleep()返回时,线程可以从yield from 拿到返回值(此处是None) 然后接着执行下一行语句
async/await
为了更好地表示异步IO,python3.5开始引入了新语法,async和await,让coroutine语法更简洁,用async代替@asyncio.coroutine替换成async, await替换yield from.
import asyncio
async def get_body(i):
print(f'start{i}')
await asyncio.sleep(1)
print(f'end{i}')
loop = asyncio.get_event_loop()
tasks = [get_body(i) for i in range(4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
import time
import requests
import aiohttp
import asyncio
targets = [
"https://lightless.me/archives/python-coroutine-from-start-to-boom.html",
"https://github.com/aio-libs",
"https://www.python.org/dev/peps/pep-0380/",
"https://www.baidu.com/",
"https://www.zhihu.com/",
]
final_results = {}
async def get_content(url):
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64,verify_ssl=False)) as session:
async with session.get(url) as resp:
content = await resp.read()
return len(content)
async def spider(url):
length = await get_content(url)
final_results[url]=length
return True
def main():
loop = asyncio.get_event_loop()
cor =[spider(url) for url in targets]
start_time = time.time()
result = loop.run_until_complete(asyncio.gather(*cor))
print("Use time: {:.2f}s".format(time.time()-start_time))
show_results(final_results)
print("loop:result:",result)
def show_results(results):
for url, length in results.items():
print("Length: {:^7d} URL: {}".format(length, url))
if __name__ =='__main__':
main()
Use time: 1.41s
Length: 227 URL: https://www.baidu.com/
Length: 52905 URL: https://www.zhihu.com/
Length: 44435 URL: https://lightless.me/archives/python-coroutine-from-start-to-boom.html
Length: 60201 URL: https://www.python.org/dev/peps/pep-0380/
Length: 313224 URL: https://github.com/aio-libs
loop:result: [True, True, True, True, True]
[Finished in 1.9s]
在main()函数中,我们先获取一个可用的事件循环,紧接着将生成好的协程任务添加到这个循环中,并且等待执行完成,在每个spider()中,执行到await的时候,会交出控制权,并且切到其它的协程继续运行,等到get_content()执行完成返回后,那么恢复spider()协程的执行,get_content()函数中只是通过async with调用aiohttp库的最基本方法获取页面内容,并且返回了长度,仅此而已.
为什么要用aiohttp,可以换成requests吗?答案是不能,在协程中,一切操作都是需要避免阻塞,禁止所有的阻塞型调用,因为所有的协程都是运行在同一个线程中的,requests库是阻塞型的调用,当在等待IO时,并不能将控制权转交给其他协程,甚至还会将当前线程阻塞,其他的协程也无法运行,如果在异步编程的时候需要用到一些其他的异步组件,可以在https:?/github.com/aio-libs/这里找找看,也许就会有需要的异步库.
关于asyncio
的异步编程资料目前来说还不算很多,官方文档应该算是相当不错的参考文献了,其中非常推荐的两部分是:Develop with asyncio 和 Tasks and coroutines,各位同学有兴趣的话可以自行阅读。asyncio
这个异步框架中包含了非常多的内容,甚至还有TCP Server/Client
的相关内容,如果想要掌握asyncio
这个异步编程框架,还需要多加练习。顺带一提,asyncio
非常容易与其他的框架整合,例如tornado
已经有实现了asyncio.AbstractEventLoop
的接口的类AsyncIOMainLoop
,还有人将asyncio
集成到 QT 的事件循环中了,可以说是非常的灵活了。
aiohttp: https://www.cnblogs.com/well-666/p/12854588.html
from gevent import monkey
import gevent
import random
import time
def task_1(name):
for i in range(5):
print(name, i)
time.sleep(1) # 协程遇到耗时操作后会自动切换其他协程运行
def task_2(name):
for i in range(3):
print(name, i)
time.sleep(1)
if __name__ == "__main__":
monkey.patch_all() # 给所有的耗时操作打上补丁
gevent.joinall([ # 等到协程运行完毕
gevent.spawn(task_1, "task_1"), # 创建协程
gevent.spawn(task_2, "task_2")
])
print("the main thread!")
task_1 0
task_2 0
task_1 1
task_2 1
task_1 2
task_2 2
task_1 3
task_1 4
the main thread!
[Finished in 5.5s]
异步协程
python中使用协程最常用的库就是asyncio,
- event_loop事件循环:相当于一个无限循环,我们可以把一些函数注册在这个事件循环上,当满足条件时,就会调用对应的处理方法
- coroutine协程:协程对象,只一个使用async关键字定义的函数,他的调用不会立即执行函数,而是返回一个协程对象,协程对象需要注册在事件循环中,由事件循环调用
- task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程的进一步封装,其中包含任务的各种状态
- future:代表将来执行或者没有执行的任务结果,它与task没有本质的区别
- async/await关键字:async定义一个协程,await用于挂起阻塞的异步调用接口
一. 定义一个协程 :通过async定义一个协程,协程是一个对象,不能直接运行,需要把协程加入到事件循环中,由loop在适当的时候调用协程,asyncio.get_event_loop()方法可以创建一个事件循环,然后由run_until_complete(协程对象)将协程注册到事件循环中并启动事件循环
run_until_complete根据传递的参数的不同,返回的结果也有所不同
- run_until_complete()传递的是一个协程对象或者task对象,则返回他们finished的返回结果(前提是他们得有return的结果,否则返回None)
- run_until_complete(asyncio.wait(多个协程对象或任务)),函数会返回一个元组包括(done, pending),通过访问done里面的task对象,获取返回值
- run_until_complete(asyncio.gather(多个协程对象或任务)),函数会返回一个列表,列表里面包括各个任务的返回结果,按顺序排列
python3.7以前版本调用异步函数的步骤:
- 调用asyncio.get_event_loop()函数获取事件循环loop对象
- 通过不同的策略调用loop.run_forever()方法或者loop.run_until_complete()方法执行异步函数
python3.7以后的版本使用asyncio.run即可,此函数总是会创建一个新的事件循环并在结束时关闭之.它应当被用作asyncio程序的主入口点,理想情况下应当只被调用一次
import asyncio
async def work(x): # 通过async关键字定义一个协程
for _ in range(3):
print('Work {} is running ..'.format(x))
coroutine_1 = work(1) # 协程是一个对象,不能直接运行
# 方式一:
# loop = asyncio.get_event_loop() # 创建一个事件循环
# result = loop.run_until_complete(coroutine_1) # 将协程对象加入到事件循环中,并执行
# print(result) # 协程对象并没有返回结果,打印None
# 方式二:
asyncio.run(coroutine_1) #创建一个新的事件循环,并以coroutine_1为程序的主入口,执行完毕后关闭事件循环
# print(result)
二. 创建一个task协程对象不能直接运行,在注册到事件循环的时候,其实是run_until_complete方法将协程包装成一个task对象,所谓的task对象就是Future类的子类,它保存了协程运行后的状态,用于未来获取协程的结果
创建task后,task在加入事件循环之前是pending状态
import asyncio
async def work(x): # 通过async关键字定义一个协程
for _ in range(3):
print('Work {} is running ..'.format(x))
coroutine_1 = work(1) # 协程是一个对象,不能直接运行
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine_1)
# task = asyncio.ensure_future(coroutine_1) # 这样也能创建一个task
print(task)
loop.run_until_complete(task) # run_until_complete接受的参数是一个future对象,当传人一个协程时,其内部自动封装成task
print(task)
<Task pending coro=<work() running at E:\notes_daily\THREAD.py:327>>
Work 1 is running ..
Work 1 is running ..
Work 1 is running ..
<Task finished coro=<work() done, defined at E:\notes_daily\THREAD.py:327> result=None>
[Finished in 0.2s]
三. 绑定回调:在task执行完毕的时候可以获取执行的结果,回调的最后一个参数是future对象,通过这个对象可以获取协程的返回值,如果回调函数需要多个参数,可以通过偏函数导入
从下例可以看出,coroutine执行结束时候会调用回调函数,并通过future获取协程返回return的结果,我们创建的task和回调里面的future对象,实际上是同一个对象
并发运行任务:
asyncio.gather(* aws,loop = None,return_exceptions = False ) 同时在aws 序列中运行等待对象
asyncio.shield(aw, * , loop=None) 保护一个 可等待对象 防止其被 取消。如果 aw 是一个协程,它将自动作为任务加入日程
asyncio.wait_for(aw, timeout, * , loop=None) 等待 aw 可等待对象 完成,指定 timeout 秒数后超时。
asyncio.wait(aws,* , loop = None,timeout = None,return_when = ALL_COMPLETED )
asyncio.as_completed(aws, * , loop=None, timeout=None) 并发地运行 aws 集合中的 可等待对象。返回一个 Future 对象的迭代器。返回的每个 Future 对象代表来自剩余可等待对象集合的最早结果。
# 并发运行任务的案例
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...") # python3.7新语法,了解一波
await asyncio.sleep(1) # await后面是 可等待对象
f *= i
print(f"Task {name}: factorial({number}) = {f}")
return f"Task {name}: Finished!"
async def main():
# Schedule three calls *concurrently*:
results = await asyncio.gather( # results包含所有任务的返回结果,是一个列表,按执行顺序返回结果
factorial("A", 2), # 协程,会自动调度为任务
factorial("B", 3),
factorial("C", 4),
)
print(results)
asyncio.run(main()) # 协程的嵌套,后面有详解
# 超时的案例
import asyncio
async def eternity():
# Sleep for one hour
# await asyncio.sleep(0.5)
await asyncio.sleep(3600)
print('yay!')
async def main():
# Wait for at most 1 second
try:
await asyncio.wait_for(eternity(), timeout=1.0) # 等待 可等待对象 完成,超过timeout秒后,抛出asyncio.TimeoutError异常
except asyncio.TimeoutError:
print('timeout!')
asyncio.run(main())
简单等待的案例
import asyncio
async def foo():
return 42
task = asyncio.create_task(foo())
# 注意:1、这里传递的要是一个任务组,而不能是单个task,如果只有一个任务,可以这样传递:[task](task,){task}
# 2、直接传递协程对象的方式已弃用 即:done, pending = await asyncio.wait([foo()])
done, pending = await asyncio.wait((task, ))
if task in done:
print(f"The task's result is {task.result()}")
# 使用事件循环和asyncio.wait、asyncio.gather实现并发运行任务
import asyncio, time
async def work_1(x):
print(f"Starting {x}")
time.sleep(1)
print(f"Starting {x}")
for _ in range(3):
print(f"Work {x} is running ..")
await asyncio.sleep(2) # 耗时操作,此时挂起该协程,执行其他协程
return f"Work {x} is finished"
async def work_2(x):
print(f"Starting {x}")
for _ in range(3):
await asyncio.sleep(1) # 耗时操作,此时挂起该协程,执行其他协程
print(f"Work {x} is running ..")
return f"Work {x} is finished"
coroutine_1 = work_1(1)
coroutine_2 = work_2(2)
loop = asyncio.get_event_loop() # 创建一个事件循环
# 方式一,asyncio.wait(tasks)接受一个task列表 执行的顺序与列表里的任务顺序有关
tasks = [
asyncio.ensure_future(coroutine_1),
asyncio.ensure_future(coroutine_2),
]
# 注册到事件循环中,并执行
dones, pendings = loop.run_until_complete(asyncio.wait(tasks)) # loop.run_until_complete(asyncio.wait(tasks))的作用相当于:await asyncio.wait(tasks)
for task in dones:
print(task.result())
# 方式二,使用asyncio.gather(*tasks),接受一堆tasks,tasks也可以是一个列表,使用*解包
# task_1 = asyncio.ensure_future(coroutine_1)
# task_2 = asyncio.ensure_future(coroutine_2)
# task_result_list = loop.run_until_complete(asyncio.gather(task_1, task_2)) # 返回一个列表,里面包含所有task的result()的结果
# print(task_result_list)
协程嵌套:使用async可以定义协程,协程用于耗时的IO操作,我们也可以封装更多的IO操作过程,在一个协程中await另外一个协程,实现协程的嵌套
import asyncio, time
async def work(x):
for _ in range(3):
print("Work {} is running ..".format(x))
await asyncio.sleep(1) # 当执行某个协程时,在任务阻塞的时候用await挂起
return "Work {} is finished!".format(x)
async def main_work():
coroutine_1 = work(1)
coroutine_2 = work(2)
coroutine_3 = work(3)
tasks = [
asyncio.ensure_future(coroutine_1),
asyncio.ensure_future(coroutine_2),
asyncio.ensure_future(coroutine_3),
]
dones, pendings = await asyncio.wait(tasks)
for task in dones:
print("The task's result is : {}".format(task.result()))
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main_work())