您的位置:首页 > 编程语言

并发编程 - 协程 - 总结

2018-04-04 20:29 387 查看
协程:
单线程下实现并发 并发 = 切换 + 保存状态
1.遇到IO切, 提高效率
2.遇到计算切, 并没有提高效率
1.协程本质:
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
2.强调:
1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
3.优点:
1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2. 单线程内就可以实现并发的效果,最大限度地利用cpu
4.缺点:
1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
5.总结:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

单线程下实现并发:
1.yield # yield 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
2.greenlet 模块 # greenlet 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
pip3 install greenlet
greenlet 比yield 好 但是还是不好 遇到io不会切
g1 = greenlet(eat)
g1.switch('alice')
g1.switch()
3.gevent 模块
pip3 install gevent
gevent:封装了greenlet模块,但是他能检测到io 自动切
加了补丁:from gevent import monkey;monkey.patch_all()
遇到time.sleep(2) 才会切,否则只有遇到gevent.sleep(2) 才会切,所以必须加补丁
g1 = gevent.spawn(eat,'alice')
g1.join() # 等待g1结束
gevent.joinall([g1,g2])
g1.value # 拿到func1的返回值

补丁说明:
from gevent import monkey;monkey.patch_all()
1.必须放到被打补丁者的前面,如time,socket模块之前
2.要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

# 通过gevent实现单线程下的socket并发(from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,
否则gevent无法识别socket的阻塞)
import time
def consumer(res):
'''任务1:接收数据,处理数据'''
pass

def producer():
'''任务2:生产数据'''
res=[]
for i in range(10000000):
res.append(i)
return res

start=time.time()
#串行执行
res=producer()
consumer(res) #写成consumer(producer())会降低执行效率
# consumer(producer())
stop=time.time()
print(stop-start) #1.5536692142486572
计算型的切换降低运行效率
import time
def producter():  # 并发的去执行
g = consumer()
next(g)
for i in range(10):
g.send(i)
time.sleep(2)

def consumer():
while True:
res = yield
print(res)
start = time.time()
producter()
stop = time.time()
print(stop-start)

import time
def producter():  # 串行  计算型的 切换 会降低运行效率
res = []
for i in range(10000000):
res.append(i)
return res

def consumer(res):
pass

start = time.time()
res = producter()  # 串行 执行  写成 consumer(producter()) 会降低执行效率
consumer(res)
stop = time.time()
print(stop-start)
yield,不能实现遇到io切换
from greenlet import greenlet
import time

def eat(name):
print('%s eat 1'%name)

g2.switch('alice')
time.sleep(4)  # 遇到io 不会立即s切
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('alice')  # 第一次切 需要传参数
greenlet模块,不能实现遇到io切换
#顺序执行
import time
def f1():
res=1
for i in range(100000000):
res+=i

def f2():
res=1
for i in range(100000000):
res*=i

start=time.time()
f1()
f2()
stop=time.time()
print('run time is %s' %(stop-start)) #10.985628366470337

#切换
from greenlet import greenlet
import time
def f1():
res=1
for i in range(100000000):
res+=i
g2.switch()

def f2():
res=1
for i in range(100000000):
res*=i
g1.switch()

start=time.time()
g1=greenlet(f1)
g2=greenlet(f2)
g1.switch()
stop=time.time()
print('run time is %s' %(stop-start)) # 52.763017892837524
计算型的使用greenlet,单纯的切换会降低运行效率
from gevent import monkey;monkey.patch_all()
import gevent
import time

def eat(name):
print('%s eat 1'%name)
time.sleep(2)
# gevent.sleep(2)
print('%s eat 2'%name)

def play(name):
print('%s play 1'%name)
# gevent.sleep(1)
time.sleep(1)
print('%s play 2'%name)

start = time.time()
g1 = gevent.spawn(eat,'alice')
g2 = gevent.spawn(play,'alice')
# g1.join()
# g2.join()
gevent.joinall([g1,g2])
stop = time.time()
print('主',stop-start)
"""
alice eat 1  # 遇到 time.sleep() io 并不会切  加个补丁才会切
alice eat 2
alice play 1
alice play 2
主 3.000598430633545
"""
"""
alice eat 1   # 加了补丁后,可识别time.sleep() io 会切
alice play 1  # 补丁:from gevent import monkey;monkey.patch_all()
alice play 2
alice eat 2
主 2.000128984451294
"""
"""
alice eat 1   # 遇到 gevent.sleep() 会切
alice play 1
alice play 2
alice eat 2
主 2.0026285648345947
"""
gevent模块,可以实现遇到io切换
# -*- coding:utf-8 -*-
from gevent import spawn,monkey;monkey.patch_all()
import socket

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket
# from gevent import socket
# s=socket.socket()

def talk(conn):
while True:
try:
res = conn.recv(1024)
if not res:break
conn.send(res.upper())
except Exception as e:
break
conn.close()

def server(ip,port):
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind((ip,port))
server.listen(5)
while True:
conn, addr = server.accept()
spawn(talk,conn)

if __name__ == '__main__':
g = spawn(server,'127.0.0.1',8080)
g.join()
gevent实现单线程下的socket并发 server
# -*- coding:utf-8 -*-
import socket
from threading import Thread,currentThread

def client(ip,port):
c = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c.connect((ip,port))

while True:
c.send(('%s hello '%currentThread().getName()).encode('utf-8'))
data = c.recv(1024)
print(data.decode('utf-8'))

if __name__ == "__main__":
for i in range(500):
t = Thread(target=client,args=('127.0.0.1',8080))
t.start()
gevent实现单线程下的socket并发 client




内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: