目录

(见右侧目录栏导航)

- 1. 前言
- 2. IO的五种模型
- 3. 协程
    - 3.1 协程的概念
- 4. Gevent 模块
    - 4.1 gevent 基本使用
    - 4.2 gevent应用一:爬虫
    - 4.3 gevent应用二:网络编程

1. 前言

CPU的速度远远快于磁盘、网络等IO。在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作。这种情况称为同步IO。在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的线程不受影响。多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法,另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。

2. IO 的五种模型

  (1)blocking IO (阻塞IO)

  (2)noblocking IO (非阻塞IO)

  (3)IO multiplexing (IO多路复用)

  (4)signal driven IO(信号驱动IO) -- 不常用

  (5)asynchronous IO (异步IO)

在理解上面五种IO模式之前需要理解以下4个概念:

  同步、异步、阻塞、非阻塞

2.1 同步和异步

  同步和异步关注的是消息通信机制

  同步:在发出一个调用时,没得到结果之前,该调用就不返回。但是一旦调用返回就得到返回值(结果)了,调用者需要主动等待这个调用的结果。

  异步:在发送一个调用时,这个调用就直接返回了,不管返回有没有结果。当一个异步过程调用发出后,被调用者通过状态,通知调用者,或者通过回调函数处理这个调用

2.2 阻塞和非阻塞

  阻塞和非阻塞关注的是程序在等待调用结果时的状态

  阻塞:调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才返回;

  非阻塞:在不能立即得到结果之前,该调用不会挂起当前线程

  有一个很好的例子说明这4者之间的关系:

    老张爱喝茶,废话不说,煮开水。 出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
          1 老张把水壶放到火上,立等水开。(同步阻塞) 老张觉得自己有点傻
          2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
          3 老张把响水壶放到火上,立等水开。(异步阻塞) 老张觉得这样傻等意义不大
          4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞) 老张觉得自己聪明了。
        
          所谓同步异步,只是对于水壶而言。 普通水壶,同步;响水壶,异步。 虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。 同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
          所谓阻塞非阻塞,仅仅对于老张而言。 立等的老张,阻塞;看电视的老张,非阻塞。 情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。

3. 协程

3.1 协程的概念

  进程是资源分配的最小单位,线程是CPU调度的基本单位, 在Cpython中,由于GIL锁的存在,一般来说,同一时间片只有一个线程在cpu中运行,为了提高单线程的效率,这里提出了协程的概念。

  协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。

  需要强调:

    1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)

    2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

  对比操作系统控制线程的切换,用户在单线程内控制协程的切换

  

  优点如下:

    1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

    2. 单线程内就可以实现并发的效果,最大限度地利用cpu

  缺点如下:

    1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

    2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

  总结协程的特点:

    1. 必须在只有一个单线程里实现并发

    2. 修改共享数据不需加锁

    3. 用户程序里自己保存多个控制流的上下文栈

    4. 一个协程遇到IO操作自动切换到其他协程

4. Gevent 模块

4.1 gevent 基本使用

  Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程。

  1.   g1=gevent.spawn(func,1,,2,3,x=4,y=5)
  2.     创建一个协程对象g1spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat
  3.   g2=gevent.spawn(func2)
  4.   g1.join()
  5.     等待g1结束
  6.   g2.join()
  7.     等待g2结束
  8.   或者上述两步合作一步:
  9.   gevent.joinall([g1,g2])
  10.   g1.value
  11.     拿到func1的返回值

  使用gevent 遇到IO就切换实例:

  1. import gevent
  2.  
  3. def eat():
  4. print('eat start...')
  5. gevent.sleep(2)
  6. print('eat end.')
  7.  
  8. def play():
  9. print('play start...')
  10. gevent.sleep(2)
  11. print('play end.')
  12.  
  13. if __name__ == '__main__':
  14. g1 = gevent.spawn(eat)
  15. g2 = gevent.spawn(play)
  16. g1.join()
  17. g2.join()
  18.  
  19. print('----主-----')

  

  上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,而time.sleep(1)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

  from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

  1. from gevent import monkey; monkey.patch_all()
  2. import gevent
  3. import time
  4.  
  5. def eat():
  6. print('eat start...')
  7. time.sleep(2)
  8. print('eat end.')
  9.  
  10. def play():
  11. print('play start...')
  12. time.sleep(2)
  13. print('play end.')
  14.  
  15. if __name__ == '__main__':
  16. g1 = gevent.spawn(eat)
  17. g2 = gevent.spawn(play)
  18. g1.join()
  19. g2.join()
  20.  
  21. print('----主-----')

  我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

  1. from gevent import monkey; monkey.patch_all()
  2. import threading
  3. import gevent
  4. import time
  5.  
  6. def eat():
  7. print(threading.current_thread().name)
  8. print('eat start...')
  9. time.sleep(2)
  10. print('eat end.')
  11.  
  12. def play():
  13. print(threading.current_thread().name)
  14. print('play start...')
  15. time.sleep(2)
  16. print('play end.')
  17.  
  18. if __name__ == '__main__':
  19. g1 = gevent.spawn(eat)
  20. g2 = gevent.spawn(play)
  21. g1.join()
  22. g2.join()
  23.  
  24. print('----主-----')
  25.  
  26. 执行结果:
  27. DummyThread-1
  28. eat start...
  29. DummyThread-2
  30. play start...
  31. (阻塞2秒)
  32. eat end.
  33. play end.
  34. ----主-----

4.2 gevent 应用一:爬虫

  1. from gevent import monkey; monkey.patch_all()
  2. import gevent
  3. import requests
  4.  
  5. def get(url):
  6. print('GET:', url)
  7. response = requests.get(url)
  8. if response.status_code == 200:
  9. print('%d bytes recevied from %s' % (len(response.text), url))
  10.  
  11. if __name__ == '__main__':
  12. gevent.joinall([
  13. gevent.spawn(get, 'https://www.baidu.com'),
  14. gevent.spawn(get, 'https://www.taobao.com'),
  15. gevent.spawn(get, 'https://www.jd.com')])

gevent-爬虫

4.3 gevent 应用二:网络编程

  通过gevent实现单线程下的socket并发
  注意:from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞

  1. from gevent import spawn, monkey;monkey.patch_all()
  2. import socket
  3.  
  4. def server(ip_port):
  5. sk_server = socket.socket()
  6. sk_server.bind(ip_port)
  7. sk_server.listen(5)
  8. while True:
  9. conn, addr = sk_server.accept()
  10. spawn(walk, conn)
  11.  
  12. def walk(conn):
  13. conn.send(b'welcome!')
  14. try:
  15. while True:
  16. res = conn.recv(1024)
  17. print(res)
  18. conn.send(res.upper())
  19. except Exception as e:
  20. print(e)
  21. finally:
  22. conn.close()
  23.  
  24. if __name__ == '__main__':
  25. server(('localhost', 8080))

server.py

  1. import socket
  2.  
  3. sk_client = socket.socket()
  4. sk_client.connect(('localhost', 8080))
  5. res = sk_client.recv(1024)
  6. print(res)
  7. while True:
  8. inp = input('>>>').strip()
  9. if not inp: continue
  10. sk_client.send(inp.encode())
  11. print(sk_client.recv(1024))

client.py

day41 - 异步IO、协程的更多相关文章

  1. python -- 异步IO 协程

    python 3.4 >>> import asyncio >>> from datetime import datetime >>> @asyn ...

  2. 异步IO/协程/数据库/队列/缓存(转)

    原文:Python之路,Day9 - 异步IO\数据库\队列\缓存 作者:金角大王Alex add by zhj: 文章很长 引子 到目前为止,我们已经学了网络并发编程的2个套路, 多进程,多线程,这 ...

  3. 2020.11.2 异步IO 协程

    异步IO 同步IO在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件.发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作. 在IO操作的过程中,当前线程被挂起,而其 ...

  4. python异步加协程获取比特币市场信息

    目标 选取几个比特币交易量大的几个交易平台,查看对应的API,获取该市场下货币对的ticker和depth信息.我们从网站上选取4个交易平台:bitfinex.okex.binance.gdax.对应 ...

  5. Tornado异步之-协程与回调

    回调处理异步请求 回调 callback 处理异步官方例子 # 导入所需库 from tornado.httpclient import AsyncHTTPClient def asynchronou ...

  6. python并发编程之多进程、多线程、异步、协程、通信队列Queue和池Pool的实现和应用

    什么是多任务? 简单地说,就是操作系统可以同时运行多个任务.实现多任务有多种方式,线程.进程.协程. 并行和并发的区别? 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任 ...

  7. python并发编程之多进程、多线程、异步和协程

    一.多线程 多线程就是允许一个进程内存在多个控制权,以便让多个函数同时处于激活状态,从而让多个函数的操作同时运行.即使是单CPU的计算机,也可以通过不停地在不同线程的指令间切换,从而造成多线程同时运行 ...

  8. 潭州课堂25班:Ph201805201 tornado 项目 第十课 深入应用异步和协程(课堂笔记)

    tornado 相关说明 需求: 增加 /save 的 handler,实现异步保存指定 URL 图片的功能 从网页上得到一张图片地址,由这个地址将图片保存到服务器,并将相关数据保存到数据库 impo ...

  9. python笔记-10(socket提升、paramiko、线程、进程、协程、同步IO、异步IO)

    一.socket提升 1.熟悉socket.socket()中的省略部分 socket.socket(AF.INET,socket.SOCK_STREAM) 2.send与recv发送大文件时对于黏包 ...

随机推荐

  1. [CF1105D]Kilani and the Game

    题目大意:给出一个$n\times m(n,m\leqslant10^3)$的地图,有$k(k\leqslant9)$个玩家,第$i$个玩家速度为$s_i$.地图中$\#$代表障碍:$.$ 代表空地: ...

  2. 【HEOI 2018】林克卡特树

    转载请注明出处:http://www.cnblogs.com/TSHugh/p/8776179.html 先说60分的.思路题解上很清晰: 问题似乎等价于选K+1条点不相交的链哎!F(x,k,0/1/ ...

  3. Emgu.CV.CvInvoke”的类型初始值设定项引发异常

    http://zhidao.baidu.com/link?url=VHkw3qZxp7HumQX_r-4ljPiy-N4A7yNK1Xn5q6tjPb16WvBGy6RFKrmKEhtgJ2PACAk ...

  4. css美化Div边框的样式实例

    很多时候如果不是用了很多样式,很难把边框修饰得好看,看了一篇博文,觉得真的挺漂亮,也挺好看. 转载的博文地址 将这段美化的css代码 border:1px solid #96c2f1;backgrou ...

  5. PID控制算法的C语言实现三 位置型PID的C语言实现

    上一节中已经抽象出了位置性PID和增量型PID的数学表达式,这一节,重点讲解C语言代码的实现过程,算法的C语言实现过程具有一般性,通过PID算法的C语言实现,可以以此类推,设计其它算法的C语言实现. ...

  6. Codeforces Round #385 (Div. 2)A B C 模拟 水 并查集

    A. Hongcow Learns the Cyclic Shift time limit per test 2 seconds memory limit per test 256 megabytes ...

  7. Ubuntu配置vncserver

    https://help.aliyun.com/knowledge_detail/59330.html 首先,安装桌面环境和vnc4server: sudo apt-get install gnome ...

  8. BNU-2017.7.3排位赛1总结

    比赛链接:https://www.bnuoj.com/v3/contest_show.php?cid=9146#info A题 国际象棋棋盘,黑白相间染色. B题 最大值只取决于每个连通块的大小,一个 ...

  9. jquery动态添加的元素绑定的事件不生效的问题

    我们可以通过 $(document).on('click', '#xxx', callback) 这种形式解决. 原因,一般情况下,我们是通过 $('#xxx').click(callback) 这种 ...

  10. centos 前端环境搭建

    Node.js 安装 wget 下载安装 yum -y install gcc make gcc-c++ openssl-devel wget node v6.11.0 下载 wget https:/ ...