协程
首先要明确,线程和进程都是系统帮咱们开辟的,不管是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()