原文链接:https://www.cnblogs.com/zhangxinqi/p/8337207.html
协程,又称微线程,纤程。英文名Coroutine。
线程是系统级别的它们由操作系统调度,而协程则是程序级别的由程序根据需要自己调度。在一个线程中会有很多函数,我们把这些函数称为子程序,在子程序执行过程中可以中断去执行别的子程序,而别的子程序也可以中断回来继续执行之前的子程序,这个过程就称为协程。也就是说在同一线程内一段代码在执行过程中会中断然后跳转执行别的代码,接着在之前中断的地方继续开始执行,类似与yield操作。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的优点:
(1)无需线程上下文切换的开销,协程避免了无意义的调度,由此可以提高性能(但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力)
(2)无需原子操作锁定及同步的开销
(3)方便切换控制流,简化编程模型
(4)高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
协程的缺点:
(1)无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
(2)进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
(1)yield实现协程效果
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 10:46
# @Author : Py.qi
# @File : yield_xiecheng.py
# @Software: PyCharm
def consumer(name):
print('开始吃包子...')
while True:
print('\033[31;1m[consumer]%s需要包子\033[0m'%name)
bone = yield #接收send发送的数据
print('\033[31;1m[%s]吃了%s个包子\033[0m'%(name,bone))
def producer(obj1):
obj1.send(None) #必须先发送None
for i in range(3):
print('\033[32;1m[producer]\033[0m正在做%s个包子'%i)
obj1.send(i)
if __name__ == '__main__':
con1 = consumer('消费者A') #创建消费者对象
producer(con1)
#output:
开始吃包子...
[consumer]消费者A需要包子
[producer]正在做0个包子
[消费者A]吃了0个包子
[consumer]消费者A需要包子
[producer]正在做1个包子
[消费者A]吃了1个包子
[consumer]消费者A需要包子
[producer]正在做2个包子
[消费者A]吃了2个包子
[consumer]消费者A需要包子
(2)greenlet模块实现程序间切换执行
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 15:25
# @Author : Py.qi
# @File : greenlet_now.py
# @Software: PyCharm
import greenlet
def A():
print('a.....')
g2.switch() #切换至B
print('a....2')
g2.switch()
def B():
print('b.....')
g1.switch() #切换至A
print('b....2')
g1 = greenlet.greenlet(A) #启动一个线程
g2 = greenlet.greenlet(B)
g1.switch()
(3)gevent实现协程
Gevent 是一个第三方库,可以轻松通过gevent实现协程程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
gevent会主动识别程序内部的IO操作,当子程序遇到IO后,切换到别的子程序。如果所有的子程序都进入IO,则阻塞。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 15:59
# @Author : Py.qi
# @File : gevent_noe.py
# @Software: PyCharm
import gevent
def foo():
print('running in foo')
gevent.sleep(2)
print('com back from bar in to foo')
def bar():
print('running in bar')
gevent.sleep(2)
print('com back from foo in to bar')
gevent.joinall([ #创建线程并行执行程序,碰到IO就切换
gevent.spawn(foo),
gevent.spawn(bar),
])
线程函数同步与异步比较:
import gevent
def task(pid):
gevent.sleep(1)
print('task %s done'%pid)
def synchronous(): #同步一个线程执行函数
for i in range(1,10):
task(i)
def asynchronous(): #异步一个线程执行函数
threads = [gevent.spawn(task,i) for i in range(10)]
gevent.joinall(threads)
print('synchronous:')
synchronous() #同步执行时要等待执行完后再执行
print('asynchronous:')
asynchronous() #异步时遇到等待则会切换执行
爬虫异步IO阻塞切换:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 17:00
# @Author : Py.qi
# @File : gevent_urllib.py
# @Software: PyCharm
from urllib import request
import gevent,time
from gevent import monkey
monkey.patch_all() #将程序中所有IO操作做上标记使程序非阻塞状态
def url_request(url):
print('get:%s'%url)
resp = request.urlopen(url)
data = resp.read()
print('%s bytes received from %s'%(len(data),url))
async_time_start = time.time() #开始时间
gevent.joinall([
gevent.spawn(url_request,'https://www.python.org/'),
gevent.spawn(url_request,'https://www.nginx.org/'),
gevent.spawn(url_request,'https://www.ibm.com'),
])
print('haoshi:',time.time()-async_time_start) #总用时
协程实现多并发链接socket通信:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 17:22
# @Author : Py.qi
# @File : gevent_sock.py
# @Software: PyCharm
import socket,gevent
from gevent import monkey
monkey.patch_all()
def server_sock(port):
s = socket.socket()
s.bind(('',port))
s.listen(10)
while True:
conn,addr = s.accept()
gevent.spawn(handle_request,conn)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
if not data: conn.shutdown(socket.SHUT_WR)
print('recv:',data.decode())
conn.send(data)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == '__main__':
server_sock(8888)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/1/24 17:35
# @Author : Py.qi
# @File : gevent_sockclient.py
# @Software: PyCharm
import socket
HOST = 'localhost' # The remote host
PORT = 8888 # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
#msg = bytes(input(">>:"), encoding="utf8")
for i in range(50):
s.send('dddd'.encode())
data = s.recv(1024)
# print(data)
print('Received', repr(data))
s.close()