协程
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:线程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置
优点:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高拓展+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分引用都没有这个必要,除非是CPU密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
使用yield实现协程操作
import time
import queue
def consumer(name): #consumer是一个生成器只有在调用next()函数时才执行,遇到yield语句返回
print("--->starting eating baozi...")
while True:
new_baozi = yield #yield不但可以返回一个值,它还可以接收调用者发出的参数
print("[%s] is eating baozi %s" % (name, new_baozi))
time.sleep(1)
def producer():
con.__next__() #因为consumer已经是一个生成器,首先要生成一个生成器对象,然后用next()函数不断获取下一个返回值
con2.__next__()
n = 0
while n < 5:
n += 1
print("\033[32;1m[producer]\033[0m is making baozi %s" % n)
con.send(n) #唤醒生成器的同时,还给它传一个值,这个值就是yield接收的值
con2.send(n)
if __name__ == "__main__":
con = consumer("c1")
con2 = consumer("c2")
p = producer()
使用greenlet实现协程
from greenlet import greenlet
def test1():
print(12)
gr2.switch() #2、切换到test2
print(34)
gr2.switch() #4、切换到test2
def test2():
print(56)
gr1.switch() #3、又切换到test1上次听下来的地方继续执行,print(34)
print(78)
gr1 = greenlet(test1) #启动一个协程
gr2 = greenlet(test2)
gr1.switch() #1、运行test1
#-------------------------------------------------
12
56
34
78
使用gevent实现协程
gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是greenlet,它是以C拓展模块形式接入python的轻量级协程。greenlet全部运行在主程序操作系统的内部,但它们被协作式地调度
import gevent
def foo():
print("Running in foo")
gevent.sleep(2) #触发切换
print("Explicit context switch to foo again") #再次精确的切换到上下文foo
def bar():
print("Explicit context to bar") #精确的切换到上下文bar
gevent.sleep(1) #触发切换,返回到上次切换的位置(gevent.sleep(2)),直至其中一方的时间先结束,再进行下一步,更改时间试试
print("Implicit context switch back to bar") #
gevent.joinall([ #启动两个协程,遇到IO阻塞时会自动切换任务
gevent.spawn(foo), #生成
gevent.spawn(bar),
])
通过gevent实现单线程下的多socket并发
server服务端
import sys
import socket
import time
import gevent
from gevent import socket, monkey
monkey.patch_all() #识别IO
def server(port):
s = socket.socket()
s.bind(("0.0.0.0", port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request,cli)
def handle_request(conn):
try:
while True:
data = conn.recv(1024)
print("recv:", data)
conn.send(data)
if not data:
conn.shutdown(socket.SHUT_WR)
except Exception as ex:
print(ex)
finally:
conn.close()
if __name__ == "__main__":
server(8888)
client客户端
import socket
HOST = "localhost"
PORT = 8888
s = socket.socket()
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"), encoding="utf8")
s.sendall(msg)
data = s.recv(1024)
print("Received", repr(data))
s.close()