协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。
一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
缺点:
#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程 #2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
协程特点:
必须在只有一个单线程里实现并发 修改共享数据不需加锁 用户程序里自己保存多个控制流的上下文栈 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
greenlet模块
属于第三方模块需要安装,pip3 install greenlet
# from greenlet import greenlet # import time # def eat(name): # print('%s eat 1'%name) # time.sleep(100) #遇到阻塞不会切换 # g2.switch('egon') # print('%s eat 2' % name) # g2.switch() # # # def play(name): # print('%s play 1'%name) # g1.switch() # print('%s play 2' % name) # # g1=greenlet(eat) # g2=greenlet(play) # # g1.switch('egon')
gevent模块
gevent模块本质用的是greenlet,比他多了个i/o监测功能
import gevent,time def eat(name): print('%s eat 1'%name) gevent.sleep(3) #此时是不识别time.sleep的 print('%s eat 2' % name) def play(name): print('%s play 1'%name) gevent.sleep(2) print('%s play 2' % name) start=time.time() g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,'alex') # gevent.sleep(10) # g1.join() # g2.join() gevent.joinall([g1,g2]) print(time.time()-start) #3.0121257305145264
让它识别time等指令
from gevent import monkey;monkey.patch_all() #打补丁,实现gevent模块识别time等i/0,而切换,必须顶格写 import gevent,time def eat(name): print('%s eat 1'%name) time.sleep(3) print('%s eat 2' % name) def play(name): print('%s play 1'%name) time.sleep(2) print('%s play 2' % name) start=time.time() g1=gevent.spawn(eat,'egon') g2=gevent.spawn(play,'alex') gevent.joinall([g1,g2]) print(time.time()-start)
其他注意:使用pycharm运行gevent的时候可能会出现一个回环错误,这个时候你要注意查看
run --->edit configurations... --->去掉前面的勾 Run with python console
通过gevent实现单线程下的socket并发
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from gevent import monkey;monkey.patch_all() from socket import * import gevent #如果不想用money.patch_all()打补丁,可以用gevent自带的socket # from gevent import socket # s=socket.socket() def server(server_ip,port): s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind((server_ip,port)) s.listen(5) while True: conn,addr=s.accept() gevent.spawn(talk,conn,addr) def talk(conn,addr): try: while True: res=conn.recv(1024) print('client %s:%s msg: %s' %(addr[0],addr[1],res)) conn.send(res.upper()) except Exception as e: print(e) finally: conn.close() if __name__ == '__main__': server('127.0.0.1',8080)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue client.send(msg.encode('utf-8')) msg=client.recv(1024) print(msg.decode('utf-8'))
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from threading import Thread from socket import * import threading def client(server_ip,port): c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了 c.connect((server_ip,port)) count=0 while True: c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8')) msg=c.recv(1024) print(msg.decode('utf-8')) count+=1 if __name__ == '__main__': for i in range(500): t=Thread(target=client,args=('127.0.0.1',8080)) t.start()