Python3-协程
协程
引子
协程介绍
Greenlet
Gevent介绍
Gevent之应用举例
一 引子
本节的主题是基于单线程来实现并发,即只用一个主线程(很明显可利用的cpu只有一个)情况下实现并发,为此我们需要先回顾下并发的本质:切换+保存状态
cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),
一种情况是该任务发生了阻塞,
另外一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它
PS:进程的三种执行状态:运行-阻塞-就绪,线程才是执行单位,所以线程执行状态跟进程一样。
一:其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法。
- # yiled可以保存状态,yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级
- # send可以把一个函数的结果传给另外一个函数,以此实现单线程内程序之间的切换
- '''
- 、协程:
- 单线程实现并发
- 在应用程序里控制多个任务的切换+保存状态
- 优点:
- 应用程序级别速度要远远高于操作系统的切换
- 缺点:
- 多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地
- 该线程内的其他的任务都不能执行了
- 一旦引入协程,就需要检测单线程下所有的IO行为,
- 实现遇到IO就切换,少一个都不行,以为一旦一个任务阻塞了,整个线程就阻塞了,
- 其他的任务即便是可以计算,但是也无法运行了
- 、协程序的目的:
- 想要在单线程下实现并发
- 并发指的是多个任务看起来是同时运行的
- 并发=切换+保存状态
- '''
- #串行执行
- import time
- def func1():
- for i in range():
- i+
- def func2():
- for i in range():
- i+
- start = time.time()
- func1()
- func2()
- stop = time.time()
- print(stop - start)
- #基于yield并发执行
- import time
- def func1():
- while True:
- yield
- def func2():
- g=func1()
- for i in range():
- i+
- next(g)
- start=time.time()
- func2()
- stop=time.time()
- print(stop-start)
- 单纯地切换反而会降低运行效率
单纯地切换反而会降低运行效率
二:第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。
- import time
- def func1():
- while True:
- print('func1')
- yield
- def func2():
- g=func1()
- for i in range():
- i+
- next(g)
- time.sleep()
- print('func2')
- start=time.time()
- func2()
- stop=time.time()
- print(stop-start)
- yield不能检测IO,实现遇到IO自动切换
yield
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
- #. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
- #. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
二 协程介绍
协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。 协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
需要强调的是:
- #. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
- #. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
对比操作系统控制线程的切换,用户在单线程内控制协程的切换
优点:
- #. 协程的切换开销更小,在线程内完成切换,操作系统完全感知不到,因而更加轻量级
- #. 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
- #. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
- #. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
总结协程特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.在线程内完成协程的切换
4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
# 多线程是在操作系统级别的切换, 协程是在程序级别的切换
三 Greenlet
如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦),而使用greenlet模块可以非常简单地实现这20个任务直接的切换
- from greenlet import greenlet
- def eat(name):
- print('%s eat 1' %name)
- g2.switch('egon')
- 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('egon')#可以在第一次switch时传入参数,以后都不需要
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度
- import time
- def f1():
- res=
- for i in range():
- res+=i
- def f2():
- res=
- for i in range():
- res*=i
- start=time.time()
- f1()
- f2()
- stop=time.time()
- print('run time is %s' %(stop-start)) #10.985628366470337
- #切换
- from greenlet import greenlet
- import time
- def f1():
- res=
- for i in range():
- res+=i
- g2.switch()
- def f2():
- res=
- for i in range():
- res*=i
- g1.switch()
- start=time.time()
- g1=greenlet(f1)
- g2=greenlet(f2)
- g1.switch()
- stop=time.time()
- print('run time is %s' %(stop-start)) # 52.763017892837524
greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。
单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块。
四 Gevent介绍
Gevent跟Greenlet相比,Gevent遇到IO操作可以自动切换,效率提升了和代码量减少了。
- #用法
- g1=gevent.spawn(func,,,,,x=,y=)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的
- g2=gevent.spawn(func2)
- g1.join() #等待g1结束
- g2.join() #等待g2结束
# gevent是异步提交任务的,如果不join线程不会等协程执行完毕就结束了,协程也会跟着结束。- #或者上述两步合作一步:gevent.joinall([g1,g2])
- g1.value#拿到func1的返回值
遇到IO阻塞时会自动切换任务
- import gevent
- def eat(name):
- print('%s eat 1' %name)
- gevent.sleep()
- print('%s eat 2' %name)
- def play(name):
- print('%s play 1' %name)
- gevent.sleep()
- print('%s play 2' %name)
- g1=gevent.spawn(eat,'egon')
- g2=gevent.spawn(play,name='egon')
- g1.join()
- g2.join()
- #或者gevent.joinall([g1,g2])
- print('主')
上例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()放到文件的开头
- from gevent import monkey
- monkey.patch_all()
- import gevent
- import time
- def eat(name):
- print('%s eat 1' % name)
- time.sleep()
- print('%s eat 2' % name)
- def play(name):
- print('%s play 1' % name)
- time.sleep()
- print('%s play 2' %name)
- g1 = gevent.spawn(eat, 'egon')
- g2 = gevent.spawn(play, 'egon')
- g1.join()
- g2.join()
- print('main')
我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程
五 Gevent之应用举例
通过gevent实现单线程下的socket并发
- from gevent import monkey
- monkey.patch_all()
- import socket
- import gevent
- def communicate(conn):
- while True:
- data = conn.recv()
- if not data:
- break
- conn.send(data.upper())
- def server(ip,port):
- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.bind((ip,port))
- server.listen()
- while True:
- conn, addr = server.accept()
- gevent.spawn(communicate, conn)
- server.close()
- if __name__ == '__main__':
- g = gevent.spawn(server, 'localhost', )
- g.join()
server
- from threading import Thread
- import socket
- import threading
- def client(server_ip, port):
- c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 套接字对象一定要加到函数内,即局部名称空间内, 放在函数外面被所有线程共享, 则大家公用一个套接字对象,那么客户端端口就永远一样了
- c.connect((server_ip, port))
- count =
- while True:
- c.send(('%s say hello %s' % (threading.current_thread().getName(), count)).encode('utf-8'))
- msg = c.recv()
- print(msg.decode('utf-8'))
- count +=
- if __name__ == '__main__':
- for i in range():
- t = Thread(target=client, args=('localhost', ))
- t.start()
多并发多个客户端
multi_thread or multi_process or Asyncio
- if io_bound:
- if io_slow:
- print('Use Asyncio')
- else:
- print('Use multi-threading')
- else if cpu_bound:
- print('Use multi-processing')
- 如果是IO密集型,而且IO操作很慢,需要很多任务/线程同实现,那么使用Asyncio更合适。
- 如果是IO密集型,而且IO操作很快,只需有限的任务/线程,那么使用多线程就可以了。
- 如果是CPU密集型,则需要多进程来提高程序运行效率。
本文转载自:http://www.cnblogs.com/linhaifeng/articles/7429894.html
Python3-协程的更多相关文章
- Python3 协程相关 - 学习笔记
什么是协程 协程的优势 Python3中的协程 生成器 yield/send yield + send(利用生成器实现协程) 协程的四个状态 协程终止 @asyncio.coroutine和yield ...
- 带你简单了解python协程和异步
带你简单了解python的协程和异步 前言 对于学习异步的出发点,是写爬虫.从简单爬虫到学会了使用多线程爬虫之后,在翻看别人的博客文章时偶尔会看到异步这一说法.而对于异步的了解实在困扰了我好久好久,看 ...
- Python3 与 C# 并发编程之~ 协程篇
3.协程篇¶ 去年微信公众号就陆陆续续发布了,我一直以为博客也汇总同步了,这几天有朋友说一直没找到,遂发现,的确是漏了,所以补上一篇 在线预览:https://github.lesschina.c ...
- python3通过gevent.pool限制协程并发数量
协程虽然是轻量级的线程,但到达一定数量后,仍然会造成服务器崩溃出错.最好的方法通过限制协程并发数量来解决此类问题. server代码: #!/usr/bin/env python # -*- codi ...
- python3之协程
1.协程的概念 协程,又称微线程,纤程.英文名Coroutine. 线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度.在一个线程中会有很多函数,我们把这些函数称为子程序, ...
- 11.python3标准库--使用进程、线程和协程提供并发性
''' python提供了一些复杂的工具用于管理使用进程和线程的并发操作. 通过应用这些计数,使用这些模块并发地运行作业的各个部分,即便是一些相当简单的程序也可以更快的运行 subprocess提供了 ...
- python3下multiprocessing、threading和gevent性能对比----暨进程池、线程池和协程池性能对比
python3下multiprocessing.threading和gevent性能对比----暨进程池.线程池和协程池性能对比 标签: python3 / 线程池 / multiprocessi ...
- Python3的原生协程(Async/Await)和Tornado异步非阻塞
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_113 我们知道在程序在执行 IO 密集型任务的时候,程序会因为等待 IO 而阻塞,而协程作为一种用户态的轻量级线程,可以帮我们解决 ...
- 并发异步编程之争:协程(asyncio)到底需不需要加锁?(线程/协程安全/挂起/主动切换)Python3
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_208 协程与线程向来焦孟不离,但事实上是,线程更被我们所熟知,在Python编程领域,单核同时间内只能有一个线程运行,这并不是什么 ...
- 运筹帷幄决胜千里,Python3.10原生协程asyncio工业级真实协程异步消费任务调度实践
我们一直都相信这样一种说法:协程是比多线程更高效的一种并发工作方式,它完全由程序本身所控制,也就是在用户态执行,协程避免了像线程切换那样产生的上下文切换,在性能方面得到了很大的提升.毫无疑问,这是颠扑 ...
随机推荐
- 创建免密码sudo用户
创建免密码sudo用户 #!/bin/bash c1=`grep -w 'bkuser' /etc/passwd | wc -l` ]; then echo "bkuser已经存在" ...
- git中tag的使用
1.获取tags $ git tag 2.新建tag 有记录信息 $git tag -a releases-1.0.1 -m 'add i.sh file.' 没有记录信息 $git tag ...
- UDP中的sendto 与recvfrom
sendto 头文件: #include <sys/types.h> #include <sys/socket.h> 定义函数: int sendto(int s, con ...
- Kafka技术内幕 读书笔记之(六) 存储层——日志的读写
-Kafka是一个分布式的( distributed ).分区的( partitioned ).复制的( replicated )提交日志( commitlog )服务 . “分布式”是所有分布式系统 ...
- HDU - 1255 覆盖的面积 (线段树求面积交)
https://cn.vjudge.net/problem/HDU-1255 题意 给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域的面积. 分析 求面积并的题:https://www.cnbl ...
- java枚举(enum)
1. 创建枚举类型要使用 enum 关键字,隐含了所创建的类型都是 java.lang.Enum (抽象类) 类的子类. enum AccountType { SAVING, FIXED, CURRE ...
- 新浪某站CRLF Injection导致的安全问题
CRLF攻击的一篇科普:新浪某站CRLF Injection导致的安全问题(转) 转:https://www.leavesongs.com/PENETRATION/Sina-CRLF-Injectio ...
- storm+Calcite
敬请期待... http://blog.csdn.net/yu616568/article/details/49915577 https://github.com/terry-chelsea/bigd ...
- DNN网络(三)python下用Tensorflow实现DNN网络以及Adagrad优化器
摘自: https://www.kaggle.com/zoupet/neural-network-model-for-house-prices-tensorflow 一.实现功能简介: 本文摘自Kag ...
- Zipkin 分布式数据追踪系统
Zipkin 是一个分布式数据追踪系统,适用于微服务架构下的调用链路数据采集及分析工作. 可通过一个 Web 前端轻松的收集和分析数据,例如用户每次请求服务的处理时间等,可方便的监测系统中存在的瓶颈. ...