协程:基于单线程来实现并发 ,又称微程,纤程(Coroutine)
并发的本质 :切换 + 保存状态 即由用户程序自己控制调度的
(1 协程的本质就是在单线程下由用户控制一个任务遇到阻塞就切换到另外一个任务执行以此来提升效率
2 python线程属于内核级别的,即由操作系统控制(遇io或时间过长被迫交出cpu执行权限)
3 单线程开启协程,一旦遇io就会从应用程序级别(而非操作系统)控制切换,以此来提升效率
协程的优点:
1 协程的切换开销小,属于程序级别的切换操作系统感知不到
2 单线程内就可以实现并发的效果,最大限度地利用cpu
缺点:
1 协程的本质是单线程下,无法利用多核,程序 ——》进程———》》线程————》》协程
2协程是单个线程,因而一旦协和出现阻塞,将会阻塞整个线程
协程的特点:
1,单线程并发 2修改共享数据不需要加锁 3 在自己的的程序里保存多个控制上下文栈
上下文栈 :(每一次代码执行和函数调用都会产生一个执行环境,称为执行上下文。
一个执行上下文(caller)又可以激活(调用)另一个执行上下文(callee),这时caller会暂停自身的执行把控制权交给callee进入callee的执行上下文,callee执行完毕后将控制权交回caller,callee可以用return或者抛出Exception来结束自己的执行。)
多个执行上下文会形成执行上下文栈,最顶层是当前执行上下文,底层是全局执行上下文。
切换的状态:1 发生阻塞(IO) 2计算时间过长
对于第二种纯计算的切换则会降低效率:
第一种任务在遇到io切,在利用阻塞的时间完成任务二的计算,效率的提升就在于此
yield 的状态保存属于代码级别的
yield生成器需要初始化一次生成器然后再调用 send /
greenlet模块可以非常简单的实现多个任务直接的切换 ,但当切换任务遇到 io 则原地阻塞仍没有解决
遇到io 自动切换来提升效率的问题
Gevent模块:对于既有计算操作又有阻塞操作可以在执行任务1遇到阻塞就利用阻塞的时间去执行任务2来提升效率 可以实现并发同步或异步编程
用法:
g1=gevent.spawn() #创建一个协程对象g1 ,spawn() 第一个参数是函数名
g2 = gevent.spawn()
g1.join() #等待g1结果
g2.join() #等待g1结果
#或者上述两者合作: gevent.joinall([g1,g2])
g1.value #拿到func1 的返回值
send 可以把一个函数的结果传给另外一个函数,以此来实现单线程之间的切换
对于单线程下,我们不可避免的出现io操作,但如果我们能在自己的程序中控制单线程下的多个任务
能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够在最大限度下处于
就绪状态(随时都可以被cpu执行的状态)
多线程并发套接字:
服务端:
from gevent import monkey;monkey.patch_all()
import gevent
from multiprocessing import Process
from socket import *
def talk(conn,addr):
while True:
try:
data=conn.recv(1024)
print('%s:%s [%s]' %(addr[0],addr[1],data))
if not data:break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
if __name__ == '__main__':
# server('127.0.0.1',8091)
s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1',8090))
s.listen(5)
while True:
conn, addr = s.accept()
print('%s:%s' % (addr[0], addr[1]))
g1 = gevent.spawn(talk, conn, addr)
客户端:
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8090))
while True:
msg=input('>>: ').strip()
if not msg:continue
c.send(msg.encode('utf-8'))
data=c.recv(1024)
print(data.decode('utf-8'))