首先是线程与协程的对比。在文中作者通过一个实例分别采用线程实现和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包处理并发(一)的更多相关文章

  1. 流畅python学习笔记第十八章:使用asyncio编写服务器

    在这一章中,将使用asyncio写一个TCP服务器.这个服务器的作用是通过规范名称查找Unicode字符,来看下代码: import asyncio from charfinder import Un ...

  2. 流畅python学习笔记第十八章:使用asyncio包处理并发(二)

    前面介绍了asyncio的用法.下面我们来看下如何用协程的方式来实现之前的旋转指针的方法 @asyncio.coroutine def spin(msg): write,flush=sys.stdou ...

  3. 流畅的python第十八章使用asyncio包处理并发

    对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关系asyncio.Future 类与 concurrent.futures.Future 类之间的区别摒弃线程或进程, ...

  4. Python学习笔记(十四)

    Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...

  5. Python学习笔记(十)

    Python学习笔记(十): 装饰器的应用 列表生成式 生成器 迭代器 模块:time,random 1. 装饰器的应用-登陆练习 login_status = False # 定义登陆状态 def ...

  6. Python学习笔记(十五):类基础

    以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-24 23:59 学习笔记 1,Python中的大多 ...

  7. Python学习笔记(十四):模块高级

    以Mark Lutz著的<Python学习手册>为教程,每天花1个小时左右时间学习,争取两周完成. --- 写在前面的话 2013-7-23 21:30 学习笔记 1,包导入是把计算机上的 ...

  8. 流畅python学习笔记:第十七章:并发处理二

    本章讨论python3.2引入的concurrent.futures模块.future是中文名叫期物.期物是一种对象,表示异步执行的操作 在很多任务中,特别是处理网络I/O.需要使用并发,因为网络有很 ...

  9. 流畅python学习笔记:第十五章:上下文管理器

    在开始本章之前,我们首先来谈谈try-excep..final模块.在Python中,进行异常保护的最多就是用try..except..final.首先来看下下面的代码.进行一个简单的除法运算.为了防 ...

随机推荐

  1. Windows远程命令执行0day漏洞安全预警

      网站安全云检测这不是腾讯公司的官方邮件. 为了保护邮箱安全,内容中的图片未被显示. 显示图片 信任此发件人的图片   一.概要 Shadow Brokers泄露多个Windows 远程漏洞利用工具 ...

  2. java程序容错

    程序最怕出错的方式就是直接闪退 编程应该以这种方式进行,保证结构不出错,数据可容错的方式 比如 fungetsonmfrominternet(){变量 a a=从网络返回数据 return a } 在 ...

  3. Code signing is required for product type Unit Test Bundle in SDK iOS 8.0

    I fixed the issue (temporarily) by going to Edit Scheme, then in the Build section, removing my unit ...

  4. Working With Push Buttons In Oracle Forms

    Managing push buttons at run time in Oracle Forms is very simple and in this tutorial you will learn ...

  5. UNIX网络编程卷1 server程序设计范式8 预先创建线程,由主线程调用accept

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.程序启动阶段创建一个线程池之后仅仅让主线程调用 accept 并把客户连接传递给池中某个 ...

  6. Maven引入Hadoop依赖报错:Missing artifact jdk.tools:jdk.tools:jar:1.6

    Maven引入Hadoop依赖报错:Missing artifact jdk.tools:jdk.tools:jar:1.6 原因是缺少tools.jar的依赖,tools.jar在jdk的安装目录中 ...

  7. TP框架模板中ifelse

    {if $_username}<ul class="afterLogin">    <li class="username"><a ...

  8. 翻翻git之---一个丰富的通知工具类 NotifyUtil

    转载请注明出处王亟亟的大牛之路 P1(废话板块.今天还加了个小广告) 昨天出去浪,到家把麦麦当当放出来玩一会就整到了12点多..早上睡过头了. .简直心酸. ... 近期手头上有一些职位能够操作,然后 ...

  9. Win8 恢复传统启动菜单 for 多系统

    如果你安装了Win7和Win8,启动的时候得先启动到Win8然后再启动到Win7,很怀念原来的启动选择器,这里给你方法了~ 当前系统是Win8的输入 bcdedit /set {current} bo ...

  10. c#中使用ABCpdf处理PDF,so easy

    QQ交流群:276874828  (ABCpdf ) 这几天项目中需要将页面导成PDF,刚开始使用iTextSharp,觉得在分页处理上比较复杂,后来无意中看到了ABCpdf,使用非常简单,并将一些常 ...