当前位置 : 主页 > 编程语言 > python >

python 协程

来源:互联网 收集:自由互联 发布时间:2021-06-25
协程 一、协程的本质: 单线程实现并发,在应用程序里控制多个任务的切换+保存状态 二、协程的目的: 想要在单线程下实现并发 并发指的是多个任务看起来是同时运行的 并发=切换

协程

一、协程的本质:

单线程实现并发,在应用程序里控制多个任务的切换+保存状态

二、协程的目的:

  • 想要在单线程下实现并发
  • 并发指的是多个任务看起来是同时运行的
  • 并发=切换+保存状态

三、补充:

  • yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
  • send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
  • 如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制)

四、优点

  • 应用程序级别速度要远远高于操作系统的切换

五、缺点

  • 多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了
  • 一旦引入协程,就需要检测单线程下所有的IO行为,实现遇到IO就切换,少一个都不行,因为如果一个任务阻塞了,整个线程就阻塞了,其他的任务即便是可以计算,但是也无法运行了

注意:单纯地切换反而会降低运行效率

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #并发执行 import time   def producer():      g = consumer()      next (g)      for i in range ( 100 ):          g.send(i)   def consumer():      while True :          res = yield   start_time = time.time() producer() stop_time = time.time() print (stop_time - start_time)   #串行 import time   def producer():      res = []      for i in range ( 10000000 ):          res.append(i)      return res     def consumer(res):      pass   start_time = time.time() res = producer() consumer(res) stop_time = time.time() print (stop_time - start_time)

greenlet

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

注意:单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #pip3 install greenlet from greenlet import greenlet import time   def eat(name):      print ( ‘%s eat 1‘ % name)      time.sleep( 2 )      g2.switch( ‘tom‘ )      print ( ‘%s eat 2‘ % name)      g2.switch()   def play(name):      print ( ‘%s play 1‘ % name )      g1.switch()      print ( ‘%s play 2‘ % name )   g1 = greenlet(eat) g2 = greenlet(play)   g1.switch( ‘tom‘ )   """ tom eat 1 tom play 1 tom eat 2 tom play 2 """

gevent

遇到IO阻塞时会自动切换任务

一、用法:

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

二、补充:

  • gevent.sleep(2)模拟的是gevent可以识别的io阻塞,
  • 而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了
  • from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #pip3 install gevent from gevent import monkey;monkey.patch_all() import gevent import time   def eat(name):      print ( ‘%s eat 1‘ % name)      time.sleep( 3 )      print ( ‘%s eat 2‘ % name)   def play(name):      print ( ‘%s play 1‘ % name)      time.sleep( 2 )      print ( ‘%s play 2‘ % name)   start_time = time.time() g1 = gevent.spawn(eat, ‘tom‘ ) g2 = gevent.spawn(play, ‘rose‘ )   g1.join() g2.join() stop_time = time.time() print (stop_time - start_time) """ tom eat 1 rose play 1 rose play 2 tom eat 2 3.003171920776367 """       from gevent import monkey;monkey.patch_all() import gevent import time   def eat(name):      print ( ‘%s eat 1‘ % name)      time.sleep( 3 )      print ( ‘%s eat 2‘ % name)   def play(name):      print ( ‘%s play 1‘ % name)      time.sleep( 2 )      print ( ‘%s play 2‘ % name)   g1 = gevent.spawn(eat, ‘tom‘ ) g2 = gevent.spawn(play, ‘rose‘ )   # g1.join() # g2.join() gevent.joinall([g1,g2])

三、通过gevent实现单线程下的socket并发

from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 """ 服务端 #基于gevent实现 """ from gevent import monkey,spawn;monkey.patch_all() from socket import *   def communicate(conn):      while True :          try :              data = conn.recv( 1024 )              if not data: break              conn.send(data.upper())          except ConnectionResetError:              break        conn.close()   def server(ip,port):      server = socket(AF_INET, SOCK_STREAM)      server.bind((ip,port))      server.listen( 5 )        while True :          conn, addr = server.accept()          spawn(communicate,conn)        server.close()   if __name__ = = ‘__main__‘ :      g = spawn(server, ‘127.0.0.1‘ , 8090 )      g.join()             """ 客户端 """ from socket import * from threading import Thread,currentThread   def client():      client = socket(AF_INET,SOCK_STREAM)      client.connect(( ‘127.0.0.1‘ , 8090 ))        while True :          client.send(( ‘%s hello‘ % currentThread().getName()).encode( ‘utf-8‘ ))          data = client.recv( 1024 )          print (data.decode( ‘utf-8‘ ))        client.close()   if __name__ = = ‘__main__‘ :      for i in range ( 500 ):          t = Thread(target = client)          t.start()
网友评论