目录

  • 概念上下文:

  • 同步的方式:

  • 异步加回调的方式:

  • 异步协程方式:

  • 总结:

这里分享一个 协程原理到实现,全局分析丨协程的切换与调度视频点击查看:「链接」

正文

  本文主要介绍在网络请求中的同步与异步,以及异步的表现形式: 回调与协程,并通过python代码展示各自的优缺点。

概念上下文:

回到顶部

  当提到同步与异步,大家不免会想到另一组词语:阻塞与非阻塞。通常,同时提到这个这几个词语一般实在讨论network io的时候,在《unix network programming》中有详尽的解释,网络中也有许多讲解生动的文章。

  本文所讨论的同步与异步,是指对于请求的发起者,是否需要等到请求的结果(同步),还是说请求完毕的时候以某种方式通知请求发起者(异步)。在这个语义环境下,阻塞与非阻塞,是指请求的受理者在处理某个请求的状态,如果在处理这个请求的时候不能做其它事情(请求处理时间不确定),那么称之为阻塞,否则为非阻塞。

  举个例子,我去柜台办理业务,那我是请求者,柜员时受理者。如果我在柜台一直等着柜员办理,直到办理完毕,那么对于我来说,就是同步的;如果我只是在柜员那里登记,然后到一边歇着,等柜员办理完毕之后告诉我结果,那么就是异步的。对于柜员,办理业务的时候可能需要等待打印机打印,如果在这个时候柜员去处理其他人的业务,那么就是非阻塞的,如果一定得到把我的业务办完再接待下一位顾客,那么就是阻塞的。

  本文站在请求发起者的角度来思考同步与异步,在实际开发中,一个最简单的例子就是http请求。假设这么一个场景,程序需要访问两个网址(通过url),如果只有一个线程。那么同步与异步分别怎么处理呢

同步的方式:

回到顶部

  python的urllib2提供了http请求的功能,我们来看看代码:

1 def http_blockway(url1, url2):2     import urllib2 as urllib3     import time4     begin = time.time()5     data1 = urllib.urlopen(url1).read()6     data2 = urllib.urlopen(url2).read()7     print len(data1), len(data2)8     print 'http_blockway cost', time.time() - begin9
10 url_list = [ 'http://xlambda.com/gevent-tutorial/','https://www.bing.com']
11 if __name__ == '__main__':
12     http_blockway(*url_list)

运行结果:

  45706 121483   http_blockway cost 2.22000002861

注意:对代码运行的时间应该多次执行求平均值,这里只是简单作为参考。下同。

  python的urllib是同步的,即当一个请求结束之后才能发起下一个请求,我们知道http请求基于tcp,tcp又需要三次握手建立连接(https的握手会更加复杂),在这个过程中,程序很多时候都在等待IO,CPU空闲,但是又不能做其他事情。但同步模式的优点是比较直观,符合人类的思维习惯: 那就是一件一件的来,干完一件事再开始下一件事。在同步模式下,要想发挥duo多喝CPU的威力,可以使用多进程或者多线程。

异步加回调的方式:

回到顶部

如果发出了请求就立即返回,这个时候程序可以做其他事情,等请求完成了时候通过某种方式告知结果,然后请求者再继续再来处理请求结果,那么我们称之为异步,最常见的就是回调(callback),在python中,tornado提供了异步的http请求。对于上面的场景,我们来看看代码:

1 import tornado2 from tornado.httpclient import AsyncHTTPClient3 import time, sys4 5 def http_callback_way(url1, url2):6     http_client = AsyncHTTPClient()7     begin = time.time()8     count = [0]9     def handle_result(response, url):
10         print('%s : handle_result with url %s' % (time.time(), url))
11         count[0] += 1
12         if count[0] == 2:
13             print 'http_callback_way cost', time.time() - begin
14             sys.exit(0)
15
16     http_client.fetch(url1,lambda res, u = url1:handle_result(res, u))
17     print('%s here between to request' % time.time())
18     http_client.fetch(url2,lambda res, u = url2:handle_result(res, u))
19
20 url_list = [ 'http://xlambda.com/gevent-tutorial/','https://www.bing.com']
21 if __name__ == '__main__':
22     http_callback_way(*url_list)
23     tornado.ioloop.IOLoop.instance().start()  

运行结果:

  1487292402.45 here between to request

  1487292403.09 : handle_result with url http://xlambda.com/gevent-tutorial/

  1487292403.21 : handle_result with url https://www.bing.com

  http_callback_way cost 0.759999990463

从代码可以看到,对请求的结果是放在一个额外的函数(handle_result)中进行的,这个处理函数在发出请求的时候注册的,这就是回调。从运行结果可以看到,在发出第一个请求之后就立即返回了(先于请求结果),而且运行时间大为缩短,这就是异步的优势所在:不用在IO上等待,在单核CPU上就有更好的性能。但是callback这种形式,导致代码逻辑因为异步请求分离,不符合人类的思维习惯,因为不直观。再来看一个很常见的例子:请求一个网址A,根据网页的内容来确定是接着访问B还是C,如果使用异步,那代码是这样的:   

1 import tornado2 from tornado.httpclient import AsyncHTTPClient3 http_client = AsyncHTTPClient()4 def handle_request_final(response):5     print('finally we got the result, do sth here')6 7 def handle_request_first(response, another1, another2):8     if response.error or 'some word' in response.body:9         target = another1
10     else:
11         target = another2
12     http_client.fetch(target, handle_request_final)
13
14 def http_callback_way(url, another1, another2):
15     http_client.fetch(url, lambda res, u1 = another1, u2 = another2:handle_request_first(res, u1, u2))
16
17 url_list = [ 'https://www.baidu.com', 'https://www.google.com','https://www.bing.com']
18 if __name__ == '__main__':
19     http_callback_way(*url_list)
20     tornado.ioloop.IOLoop.instance().start()

代码表达的逻辑是连贯的,但代码的变现形式却是割裂的,在这里分散到了三个函数里面。对于程序员来说,这样的代码难以阅读,不容易看一眼就大概明白其意图,而且这样的代码是难以维护的,更糟糕的是,编程实现中还得保存或者传递上下文(比如函数中的参数 another1, another2)。

  在python中,由于lambda的功能还是比较弱,所以回调一般都是另外命令一个函数。而在javascript,特别是以异步IO为基石的NodeJS中,由于匿名函数的强大,很轻易嵌套实现callback,这也就出现了让编码者沉默、维护者流泪的callback hell。当然promise对callback hell有一定的改善,但还有没有更好的办法呢?

异步协程方式:

回到顶部

  这个时候协程就出马了,本来是异步调用,但是程序上看上去变成了“同步”。关于协程,python中可以用原声的generator,也可以使用更严格的greenlet。首先来看看tornado封装的协程:

1 import tornado, sys, time2 from tornado.httpclient import AsyncHTTPClient3 from tornado import gen4 5 def http_generator_way(url1, url2):6     begin = time.time()7     count = [0]8     @gen.coroutine9     def do_fetch(url):
10         http_client = AsyncHTTPClient()
11         response = yield http_client.fetch(url, raise_error = False)
12         print url, response.error
13         count[0] += 1
14         if count[0] == 2:
15             print 'http_generator_way cost', time.time() - begin
16             sys.exit(0)
17
18     do_fetch(url1)
19     do_fetch(url2)
20
21 url_list = [ 'http://xlambda.com/gevent-tutorial/','https://www.bing.com']
22 if __name__ == '__main__':
23     http_generator_way(*url_list)
24     tornado.ioloop.IOLoop.instance().start()

运行结果:

  http://xlambda.com/gevent-tutorial/ None

  https://www.bing.com None

  http_generator_way cost 1.05999994278

  从运行结果可以看到,效率还是优于同步的,而代码看起来是顺序执行的,但事实上有某种意义上的并行。代码中使用了decorator 和 yield关键字,在看看使用gevent的代码:

1 def http_coroutine_way(url1, url2):2     import gevent, time3     from gevent import monkey4     monkey.patch_all()5     begin = time.time()6 7     def looks_like_block(url):8         import urllib2 as urllib9         data = urllib.urlopen(url).read()
10         print url, len(data)
11
12     gevent.joinall([gevent.spawn(looks_like_block, url1), gevent.spawn(looks_like_block, url2)])
13     print('http_coroutine_way cost', time.time() - begin)
14
15 url_list = [ 'http://xlambda.com/gevent-tutorial/','https://www.bing.com']
16 if __name__ == '__main__':
17     http_coroutine_way(*url_list)

  代码中没有特殊的关键字,使用的API也是跟同步方式一样的,这就是gevent的牛逼之处,通过monkey_patch就是原来的同步API变成异步,gevent的介绍可以参见《gevent调度流程解析》。

  协程与回调对比,优势一目了然:代码更清晰直观,更加复合思维习惯。但协程也不是银弹,首先,协程是个新概念,需要花时间去理解;其次,程序员心里也得牢牢记住,在看似在一起的两句代码中间可能插入了很大其它逻辑(即使是在单线程)。但总体来说,协程给出了人们解决问题的新思路,利大于弊,在其他语言(C# Lua golang)中也有支持,还是值得程序员去了解和学习

总结:

回到顶部

  最后,简单总结一下,同步比较直观,编码和维护都比较容易,但是效率低,绝大多数时间都是不能忍的。异步效率高,对于callback这种形式逻辑容易被代码割裂,代码不直观,而异步协程的方式既看起来直观,又在效率上有保证。

看完的都是真爱,点个赞再走呗?您的「三连」就是我的最大动力!

  1. 关注公众号「Linux C后台服务器开发」,第一时间获取最新Linux干货!

  1. +qun720209036 获取精选一线大厂面试、自我提升、简历等资料。

同步与异步,回调与协程相关推荐

  1. 关于C10K、异步回调、协程、同步阻塞

    2019独角兽企业重金招聘Python工程师标准>>> 最近到处在争论这些话题,发现很多人对一些基础的常识并不了解,在此发表一文做一下解释.此文未必能解答所有问题,各位能有一个大致的 ...

  2. 并发编程之进程池,线程池 和 异步回调,协程

    1.进程池和线程池 2.异步回调 3.协程 4.基于TCP使用多线程实现高并发 一.进程池和线程池 什么是进程池和线程池: ''' 池 Pool 指的是一个容器 线程池就是用来存储线程对象的 容器创建 ...

  3. Python|线程和进程|阻塞|非阻塞|同步|异步|生成器和协程|资源竞争|进程间通信|aiohttp库|daemon属性值详解|语言基础50课:学习(11)

    文章目录 系列目录 原项目地址 第34课:Python中的并发编程-1 线程和进程 多线程编程 使用 Thread 类创建线程对象 继承 Thread 类自定义线程 使用线程池 守护线程 资源竞争 G ...

  4. Python 异步 IO 、协程、asyncio、async/await、aiohttp

    From :廖雪峰 异步IO :https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152 Python Async/Awai ...

  5. 服务端异步IO配合协程浅析

    服务端异步IO配合协程浅析 代码如下 #coding:utf-8 import socket from selectors import DefaultSelector, EVENT_READ, EV ...

  6. 【Kotlin 协程】协程简介 ( 协程概念 | 协程作用 | 创建 Android 工程并进行协程相关配置开发 | 异步任务与协程对比 )

    文章目录 一.协程概念 二.协程作用 三.创建 Android 工程并进行协程相关配置 1.创建 Android 工程 2.配置协程环境 3.布局文件 4.异步任务代码示例 5.协程代码示例 6.完整 ...

  7. 动手实现Kotlin协程同步切换线程,以及Kotlin协程是如何实现线程切换的

    前言 突发奇想想搞一个同步切换线程的Kotlin协程,而不用各种withContext(){},可以减少嵌套且逻辑更清晰,想实现的结果如下图: 分析 实现我们想要的结果,首先需要知道协程为什么可以控制 ...

  8. python3异步编程_协程 Python异步编程(asyncio)

    协程(Coroutine) 也可以被称为微线程,是一种用户态内的上下文切换技术.简而言之,其实就是通过一个线程实现代码块相互切换执行. 直接上代码,例如: 同步编程 import time def f ...

  9. 异步IO(协程,消息循环队列)

    同步是CPU自己主动查看IO操作是否完成,异步是IO操作完成后发出信号通知CPU(CPU是被通知的) 阻塞与非阻塞的区别在于发起IO操作之后,CPU是等待IO操作完成再进行下一步操作,还是不等待去做其 ...

最新文章

  1. 浅析Struts 体系结构与工作原理(图)
  2. DARPA发布产业振兴计划,继承摩尔智慧
  3. B树、B-树、B+树、B*树(转)
  4. [Vue源码分析] 模板的编译
  5. 鸿蒙os2.0公测机型,鸿蒙OS2.0第二期第三期公测机型陆续公布 麒麟980和麒麟820将登场...
  6. java—三大框架详解,其发展过程及掌握的Java技术慨括
  7. magrittr | R语言的管道操作符
  8. zookeeper-3.6.0安装报错:找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain
  9. Springboot使用FastJson后,接口返回中文乱码的问题解决。
  10. HTML-CSS线性渐变
  11. access 数据库模糊查询
  12. 人行横道——步行管理(交通运行管理)
  13. 容积卡尔曼滤波算法 CKF
  14. 倒计时小插件,懒人专用
  15. 乡下人生活录——程序员给自己买份保险吧
  16. cocos2dx 3.17海外sdk接入填坑全纪录 Appodeal(广告) SDK 接入(5)
  17. 电脑解锁后黑屏有鼠标_电脑开机黑屏只有鼠标怎么办
  18. 企业微信推送消息延迟_微信接收消息总是延迟?没有提醒,这3个功能赶紧设置一下...
  19. 深入浅出SQL读书笔记
  20. Java Data truncation:Incorrect xxx value: ‘xxx‘ for column ‘xxx‘ at row 1问题

热门文章

  1. 【计算机网络技术】三网是指电信网络、有线电视网和计算机网络。
  2. 高性能、高可靠分布式文件系统 go-fastdfs v1.2.1 发布
  3. 编译SPECCPU2006
  4. 学习编程,只跟对的人
  5. 《Clean Code》读后感之选择一家好的公司至关重要
  6. 手把手教你使用Js实现前后台传送Json
  7. 深入浅出JavaScript运行机制
  8. 基于Python实现的药店|药房管理系统
  9. imindmap10的更新日志
  10. Confluence 6 配置 Windows 服务