并发编程之协程
什么是协程?
协程:是单线程下的并发,又称为微线程,纤程。协程是由用户程序自身控制的。
ps:1、python的线程属于内核级别的,是由操作系统调度
2、单线程内开启协程,一旦遇到io,就会从应用程序级别控制切换,而不是由操作系统来进行切换,(如果不是io操作而进行切换,并不会提升效率)
协程的优点:
1、协程的开销更小,是属于程序级别的切换,操作系统完全感知不到。
2、在单线程下便可以实现并发的效果,最大限度的利用cpu
缺点:
1、无法利用到多核(多个cpu)的优势,要想利用,可以在程序中开启多个进程,在每个进程中开启多个线程,在线程中开启多个携程
2、协程指的是单个线程,所以一旦协程出现了阻塞,那么便会导致 整个线程被阻塞。
greenlet模块
使用greenlet模块可以很轻松的实现多个任务之间进行来回的切换
from greenlet import greenlet def make_girlfriend(name): print("%s is making old girlfriend" % name) g2.switch(name) print("%s is making new girlfriend" % name) g2.switch() def play_computer_game(name): print("%s is playing lol" % name) g1.switch() print("%s is playing cf" % name) g1.switch() g1 = greenlet(make_girlfriend) g2 = greenlet(play_computer_game) g1.switch("xu") # 传了一次参数过后,下一次在switch的时候便不需要在传递参数了 ‘‘‘ 打印结果: xu is making old girlfriend xu is playing lol xu is making new girlfriend xu is playing cf ‘‘‘
但是greenlet模块只是进行了对任务的单纯的切换,在遇到了io阻塞时,还是会原地的阻塞住,并没有在io阻塞时对cpu进行调度。
在单线程里多个任务中,并且任务中含有io阻塞,要想在阻塞状态时,对cpu进行调度,便需要使用到gevent模块
gevent模块
pip3 install gevent 对gevent模块进行安装
gevent模块的使用方法
g1 = gevent.spawn(函数名,函数参数...) # 创建gevent对象
g2 = gevent.spawn(函数名,函数参数...) # 创建gevent对象
g1.join() # 等待g1结束
g2.join() # 等待g2结束
或者 gevent.joinall(g1, g2)
g1.value():拿到函数的返回值
gevent遇到阻塞时会自动进行任务切换
import gevent def make_girlfriend(name): print("%s is making old girlfriend" % name) gevent.sleep(2) print("%s is making new girlfriend" % name) def play_computer_game(name): print("%s is playing lol" %name) gevent.sleep(2) print("%s is playing cf" % name) g1 = gevent.spawn(make_girlfriend,"xu") g2 = gevent.spawn(play_computer_game,"xu") gevent.joinall([g1,g2]) ‘‘‘ 打印结果: xu is making old girlfriend xu is playing lol xu is making new girlfriend xu is playing cf ‘‘‘
但是上面的只能捕捉到gevent的阻塞,并不能对其他io阻塞进行捕捉,这时就需要打补丁
from gevent import monkey;monkey.patch_all() # 打补丁,必须放在最前面 import gevent import time def make_girlfriend(name): print("%s is making old girlfriend" % name) time.sleep(2) print("%s is making new girlfriend" % name) def play_computer_game(name): print("%s is playing lol" %name) time.sleep(2) print("%s is playing cf" % name) start_time = time.time() g1 = gevent.spawn(make_girlfriend,"xu") g2 = gevent.spawn(play_computer_game,"xu") gevent.joinall([g1,g2]) consumption_time = time.time() - start_time print(consumption_time) ‘‘‘ 打印结果: xu is making old girlfriend xu is playing lol xu is making new girlfriend xu is playing cf 2.004627227783203 ‘‘‘
由此可以看出,当任务遇到io阻塞后,便会切换到其他任务,再遇到io阻塞时,便会再次切换。这样便实现了看起来并发的作用,对cpu的利用效率也提高了。
练习:通过协程实现套接字通信
服务端 from gevent import monkey;monkey.patch_all() import gevent import socket def server(sever_ip, port): sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.bind((sever_ip, port)) sk.listen(5) while True: conn, addr = sk.accept() gevent.spawn(connect_user, conn) def connect_user(conn): while True: try: data = conn.recv(1024) if not data:break send_data = data.upper() conn.send(send_data) except ConnectionError as e: conn.close() break if __name__ == ‘__main__‘: server("127.0.0.1",8080) 客户端 import socket def client(server_ip, port): conn= socket.socket(socket.AF_INET, socket.SOCK_STREAM) conn.connect((server_ip, port)) while True: data = input(">>>:").strip() if data == "q":break conn.send(bytes(data, ‘utf-8‘)) recv_data = conn.recv(1024) print(str(recv_data, "utf-8")) conn.close() if __name__ == ‘__main__‘: client(‘127.0.0.1‘, 8080)