协程
首先要明确,线程和进程都是系统帮咱们开辟的,不管是thread还是process他内部都是调用的系统的API,而对于协程来说它和系统毫无关系;
协程不同于线程的是,线程是抢占式的调度,而协程是协同式的调度,也就是说,协程需要自己做调度。
他就和程序员有关系,对于线程和进程来说,调度是由CPU来决定调度的;
对于协程来说,程序员就是上帝,你想让谁执行到哪里他就执行到哪里;
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。(当然协程也可以配合多线程实现异步)
适用场景:其实在其他语言中,协程的其实是意义不大的多线程即可已解决I/O的问题,但是在python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,所以:如果一个线程里面I/O操作特别多,协程就比较适用;
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的好处:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 方便切换控制流,简化编程模型
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
一:使用yield实现协程操作例子
#!/usr/bin/env python3
#yield实现协程
import time,queue
def consumer(name):
print("开始吃包子。。。")
while True:
new_baozi = yield #函数暂时在这里停止
print("%s 开始吃包子 %s " % (name, new_baozi))
def producer():
c1 = con.__next__() #consumer通过yield变成了迭代器,要通过__next__方法来执行
c2 = con2.__next__()
n = 0
while n < 5:
n += 1
con.send(n) #向con中的yield发送n,即把函数中的yield换成n
con2.send(n)
print("制作了 包子 %s " % n)
if __name__ == "__main__":
con = consumer('lalala')
con2 = consumer('hahaha')
producer()
开始吃包子。。。
开始吃包子。。。
lalala 开始吃包子 1
hahaha 开始吃包子 1
制作了 包子 1
lalala 开始吃包子 2
hahaha 开始吃包子 2
制作了 包子 2
lalala 开始吃包子 3
hahaha 开始吃包子 3
制作了 包子 3
lalala 开始吃包子 4
hahaha 开始吃包子 4
制作了 包子 4
lalala 开始吃包子 5
hahaha 开始吃包子 5
制作了 包子 5
- 协程的定义:
- 必须只有在单个线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流上的上下文
- 一个协程遇到IO操作自动切换到其他协程
greenlet
greenlet是用C实现的协程模块,相比与python自带的yield,他可以使你在任意函数之随意切换。
#!/usr/bin/env python3
#通过greenlet来实现协程
from greenlet import greenlet
def test1():
print(12)
gr2.switch() #在这里实现跳转,转到test2
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch() #在这里实现跳转,转到test1
print(78)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
gevent
gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是greenlet,它是以C扩展模块形式介入Python的轻量级协程。在greenlet全部运行在主程序操作系统进程的内部,但它们被协作式调度。
#!/usr/bin/env python3
#通过gevent来实现协程
import gevent
def func1():
print(" the fun1 start...")
gevent.sleep(2) #遇到堵塞就跳转(根据堵塞的时间长短)
print(' the fun1 end...')
def func2():
print("\033[31;1m the func2 start \033[0m")
gevent.sleep(1)
print('\033[31;1m the func2 end \033[0m')
def func3():
print("\033[32;1m the func3 start \033[0m")
gevent.sleep(5)
print('\033[32;1m the func3 end \033[0m')
def func4():
print("\033[33;1m the func4 start \033[0m")
gevent.sleep(4)
print('\033[33;1m the func4 end \033[0m')
gevent.joinall(
[
gevent.spawn(func1),
gevent.spawn(func2),
gevent.spawn(func3),
gevent.spawn(func4),
]
)
同步可异步的性能区别
#!/usr/bin/env python3
#同步和异步性能的区分
import gevent
def tasf(n):
gevent.sleep(1)
print("The func print %s" % n)
def tongbu():
for i in range(10):
tasf(i)
def yibu():
threads = [gevent.spawn(tasf, i) for i in range(10)]
gevent.joinall(threads)
print('tongbu')
tongbu()
print('yibu')
yibu()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
上面的程序的主要部分时间tasf函数封装到greenlet内部线程的gevent.spawn。初始化的greenlet列表存放在threads列表中,次数组被传给gevent.joinall函数后,后者阻塞当前的流程,并执行所有给定的greenlet。执行流程只会在所有greenlet执行完成后才会继续向下走。
当遇到阻塞是会自动切换任务
#!/usr/bin/env python3
#遇到阻塞是自动区分
from gevent import monkey;monkey.patch_all()
import gevent
from urllib.request import urlopen
def f(url):
print('GET: %s' % url)
resp = urlopen(url)
data = resp.read()
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(f,'https://www.baidu.com'),
gevent.spawn(f,'https://www.sina.com'),
])
通过gevent实现单线程下的多socket并发
#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#server端
import gevent
from gevent import socket,monkey
monkey.patch_all()#将python标准库中的网络借口不阻塞
def server(port):
s = socket.socket()
s.bind(('127.0.0.1',port))
s.listen(500)
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli) #执行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(8001)
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
#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#client端
import socket
host = "127.0.0.1"
port = 8001
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host,port))
while True:
user_input = bytes(input(),encoding = "utf8")
s.sendall(user_input)
data = s.recv(1024)
print(repr(data))
s.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
100个并发的socket链接
#!/usr/bin/env python3
#通过gevent实现单线程下的多socket并发
#client端2
import socket
import threading
def sock_conn():
client = socket.socket()
client.connect(('127.0.0.1',8001))
count = 0
while True:
client.send(('hello %s % count').encode('utf-8'))
data = client.recv(1024)
print('[%s]recv from server:' % threading.get_ident(),data.decode())
count += 1
client.close()
for i in range(100):
t = threading.Thread(target=sock_conn)
t.start()