流畅python学习笔记第十八章:使用asyncio包处理并发(一)
首先是线程与协程的对比。在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别。在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用。
知识点一:
当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元,当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是setDaemon(False)),主线程执行完自己的任务以后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束,例子如下:。
def run():
time.sleep(2)
print('当前线程名称是:%s\n' % threading.currentThread().name)
time.sleep(2)
if __name__=="__main__":
start_time=time.time()
print('这是主线程:%s' % threading.current_thread().name)
thread_list=[]
for i in range(5):
t=threading.Thread(target=run)
thread_list.append(t)
for t in thread_list:
t.start()
print('主线程结束:%s' % threading.current_thread().name)
print('一共用时:%f' % float(time.time()-start_time))
运行结果:
我们的计时是针对主线程的计时,主线程结束,计时也随之结束,打印出主线程的用时
主线程的任务完成之后,主线程随之结束,子线程继续执行自己的任务,直到全部的子线程的任务全部结束,程序结束。
/usr/bin/python2.7 /home/zhf/py_prj/function_test/asy_try.py
这是主线程:MainThread
主线程结束:MainThread
一共用时:0.000893
当前线程名称是:Thread-4
当前线程名称是:Thread-1
当前线程名称是:Thread-2
当前线程名称是:Thread-3
当前线程名称是:Thread-5
知识点二:
当我们使用setDaemon(True)方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行,可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止,例子如下。设置t.setDaemon(True)
for t in thread_list:
t.setDaemon(True)
t.start()
结果如下:主线程结束后,所有的线程都结束
/usr/bin/python2.7 /home/zhf/py_prj/function_test/asy_try.py
这是主线程:MainThread
主线程结束:MainThread
一共用时:0.000569
知识点三:
此时join的作用就凸显出来了,join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止,代码中添加如下。
for t in thread_list:
t.join()
运行结果:线程运行完后主线程才退出。
/usr/bin/python2.7 /home/zhf/py_prj/function_test/asy_try.py
这是主线程:MainThread
当前线程名称是:Thread-1
当前线程名称是:Thread-2
当前线程名称是:Thread-4
当前线程名称是:Thread-3
当前线程名称是:Thread-5
主线程结束:MainThread
一共用时:4.008916
介绍完了线程的用法。现在来看用多线程实现书中的例子。这个例子主要是产生一个|\-/构成的旋转指针。代码如下:
class Signal:
go=True
def spin(msg,signal):
write,flush=sys.stdout.write,sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status=char+''+msg
write(status)
flush()
write('\x08'*len(status))
time.sleep(1)
if not signal.go:
break
write(''*len(status)+'\0x8'*len(status))
def slow_function():
time.sleep(3)
return 42
def supervisor():
result=0
signal=Signal()
spinner=threading.Thread(target=spin,args=('thinking!',signal))
print('spinner object:',spinner)
spinner.start()
result=slow_function()
signal.go=False
spinner.join()
return result
def main():
result=supervisor()
print('Answer:',result)
if __name__=="__main__":
main()
运行结果如下:在这个例子中,没有直接关闭线程,而是通过signal.go的方式来退出。
接下来介绍asyncio. asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步。来看下几个基本的概念:
- event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
- coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
- task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。
- future: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
- async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口
我们先不看书中用asychio改造上面的例子。先来看下asyncio的工作原理。
代码如下
@asyncio.coroutine
def hello():
print('hello world')
r=yield from asyncio.sleep(1)
print('hello again')
代码也可以写成如下的形式:两种不同的书写方式。
async def hello():
print('hello world')
await asyncio.sleep(1)
print('hello again')
if __name__=="__main__":
loop=asyncio.get_event_loop()
task=loop.create_task(hello())
print(datetime.datetime.now())
print(task)
loop.run_until_complete(task)
print(task)
print(datetime.datetime.now())
loop.close()
运行结果:
/usr/bin/python3.6 /home/zhf/py_prj/function_test/asy_try.py
2018-03-23 22:00:59.179893
<Task pending coro=<hello() running at /home/zhf/py_prj/function_test/asy_try.py:46>>
hello world
hello again
<Task finished coro=<hello() done, defined at /home/zhf/py_prj/function_test/asy_try.py:46> result=None> None
2018-03-23 22:01:00.181498
协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。所谓task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。在通过loop.create_task(hello())的时候,任务其实是处于pending状态。在hello中通过asyncio.sleep(1)耗时一秒最后任务执行完后状态变为done.
asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,task是Future的子类
通过上面的例子,我们可以总结下asyncio的应用场景了。譬如说你有一个脚本向3个不同服务器请求数据。 有时,谁知什么原因,发送给其中一个服务器的请求可能意外地执行了很长时间。想象一下从第二个服务器获取数据用了10秒钟。在你等待的时候,整个脚本实际上什么也没干。如果你可以写一个脚本可以不去等待第二个请求而是仅仅跳过它,然后开始执行第三个请求,然后回到第二个请求,执行之前离开的位置通过这种切换任务最小化了空转时间。这就是asyncio的主要应场景。这和前面将到的多线程是不一样的。多线程其实是多个任务并行处理。而在asyncio中其实只有一个时间轴。但是对于在这条时间轴上执行任务来说,又不是顺序式的触发。这种触发方式类似与中断。当执行了任务A的时候,产生一个中断,执行任务B,B任务执行完后回到A的中断处继续执行剩余的代码。代码中的yield from就可以想成是一个中断。因此异步代码都是在一个线程中执行的。整个流程可以用下图来表示:
从上图可知:
1.消息循环是在线程中执行
2.从队列中取得任务
3.每个任务在协程中执行下一步动作
4.如果在一个协程中调用另一个协程(await <coroutine_name>),会触发上下文切换,挂起当前协程,并保存现场环境(变量,状态),然后载入被调用协程
5.如果协程的执行到阻塞部分(阻塞I/O,Sleep),当前协程会挂起,并将控制权返回到线程的消息循环中,然后消息循环继续从队列中执行下一个任务...以此类推
6.队列中的所有任务执行完毕后,消息循环返回第一个任务
接下来我们就来看下在多个任务运行的时候,asyncio是如何进行任务阻塞的。
@asyncio.coroutine
def do_work(x):
print('doing work',x)
yield from asyncio.sleep(1)
return 'Done after {}s'.format(x)
if __name__=="__main__":
start=time.time()
loop=asyncio.get_event_loop()
tasks=[asyncio.ensure_future(do_work(1)),asyncio.ensure_future(do_work(2)),asyncio.ensure_future(do_work(3))]
tasks1=[do_work(1),do_work(2),do_work(3)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end=time.time()
print("Total time:{}".format(end-start))
运行结果如下:在代码中有三个任务do_work(1),do_work(2),do_work(3).当在do_work中执行asyncio.sleep(1)的时候将会将选择权交给主循环,主循环会去队列中查找下一个任务继续执行。
/usr/bin/python3.6 /home/zhf/py_prj/function_test/asy_try.py
doing work 1
doing work 2
doing work 3
Total time:1.0022118091583252
可以看到在采用asyncio的方式下。总共的运行方式为1秒。如果采用顺序执行的方式,执行的时间将是1+2+3=6秒。可以看出asyncio的方式能够大大缩短时间。注意这里必须使用asyncio.sleep(1)而不能使用time.sleep(1),使用time.sleep()的方式将会挂起什么都不做。
我们用多线程的方式来做个对比:
def run():
tasks=[]
start=time.time()
t1=threading.Thread(target=do_work_thread,args=(1,))
t2 = threading.Thread(target=do_work_thread, args=(2,))
t3 = threading.Thread(target=do_work_thread, args=(3,))
tasks.append(t1)
tasks.append(t2)
tasks.append(t3)
for t in tasks:
t.start()
for t in tasks:
t.join()
end=time.time()
print("The time is :{}s".format(end-start))
运行结果:可以看到asyncio的运行时间和多线程是一样的。
/usr/bin/python3.6 /home/zhf/py_prj/function_test/asy_try.py
doing work 1
doing work 2
doing work 3
Done after 1s
Done after 3s
Done after 2s
The time is :1.0017800331115723s
流畅python学习笔记第十八章:使用asyncio包处理并发(一)的更多相关文章
- 流畅python学习笔记第十八章:使用asyncio编写服务器
在这一章中,将使用asyncio写一个TCP服务器.这个服务器的作用是通过规范名称查找Unicode字符,来看下代码: import asyncio from charfinder import Un ...
- 流畅python学习笔记第十八章:使用asyncio包处理并发(二)
前面介绍了asyncio的用法.下面我们来看下如何用协程的方式来实现之前的旋转指针的方法 @asyncio.coroutine def spin(msg): write,flush=sys.stdou ...
- 流畅的python第十八章使用asyncio包处理并发
对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关系asyncio.Future 类与 concurrent.futures.Future 类之间的区别摒弃线程或进程, ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
- Python学习笔记(十)
Python学习笔记(十): 装饰器的应用 列表生成式 生成器 迭代器 模块:time,random 1. 装饰器的应用-登陆练习 login_status = False # 定义登陆状态 def ...
- Python学习笔记(十五):类基础
以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-24 23:59 学习笔记 1,Python中的大多 ...
- Python学习笔记(十四):模块高级
以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...
- 流畅python学习笔记:第十七章:并发处理二
本章讨论python3.2引入的concurrent.futures模块.future是中文名叫期物.期物是一种对象,表示异步执行的操作 在很多任务中,特别是处理网络I/O.需要使用并发,因为网络有很 ...
- 流畅python学习笔记:第十五章:上下文管理器
在开始本章之前,我们首先来谈谈try-excep..final模块.在Python中,进行异常保护的最多就是用try..except..final.首先来看下下面的代码.进行一个简单的除法运算.为了防 ...
随机推荐
- 常见指令与功能介绍-java之JSP学习第二天(非原创)
文章大纲 一.JSP 指令二.JSP 动作元素三.JSP 隐式对象四.JSP 客户端请求五.JSP 服务器响应六.JSP HTTP 状态码七.JSP 表单处理八.JSP 过滤器九.JSP Cookie ...
- Jenkins里邮件插件触发器配置和Send to Developers到底是什么意思(转)
邮件触发类型介绍(Triggers) By default, the onlytrigger configured is the "Failure" trigger. To ad ...
- windows上,python安装非官方包,提示error: Unable to find vcvarsall.bat
在windows机器上安装python非官方包,如果环境只是用于开发,不作任何测试的话,最好的解决办法是: 在Linux上pip安装好之后,把python根目录lib/python3.6/site-p ...
- 在C#中使用C++编写的类——用托管C++进行封装[转]
现在在Windows下的应用程序开发,VS.Net占据了绝大多数的份额.因此很多以前搞VC++开发的人都转向用更强大的VS.Net.在这种情况 下,有很多开发人员就面临了如何在C#中使用C++开发好的 ...
- MySQL触发器 trigger学习
触发器:一类特殊的事物.可监视某种数据操作,并触发相关操作(insert/update/delete).表中的某些数据改变,希望同一时候能够引起其他相关数据改变的需求. 作用:变化自己主动完毕某些语句 ...
- 【MVC2】发布到IIS7.5上后Session为null
MVC2代码「Session.IsNewSession」在VS中可以正常执行,发布到IIS7.5上之后Session为null导致出错. if (Session.IsNewSession) { ... ...
- Webpack DLL
参考自:https://www.jianshu.com/p/a5b3c2284bb6 在用 Webpack 打包的时候,对于一些不经常更新的第三方库,比如 react,lodash,我们希望能和自己的 ...
- FileUpload控件预览图片
HTML代码: <tr> <td class="auto-style1">上传图片:</td> <td> <asp:FileU ...
- 利用php调用so库文件中的代码
某个功能被编译到so文件中,那么如何通过php来调用它?一个方法是写一个php模块(php extension),在php中调用该模块内的函数,再通过该模块来调用so中的函数.下面做一个简单的例子,使 ...
- tomcat报错: Error parsing HTTP request header
Error parsing HTTP request header 在服务器上面集成项目的时候,tomcat报错,在往上面查找是因为eclipse运行的tomcat和服务器上面的tomcat版本不一致 ...