一.协程Coroutine:主要面向IO操作
1.概念:又称微线程/纤程,非抢占式,用户态的切换自己规定
协程切换没有消耗硬件资源;OS级别上无法识别协程,只将其视为一个进程中的一个线程,协程只是一个名字,利用yield等进行切换;本质上就是一个线程,没有物理层面上的切换
2.优缺点:
- 执行效率高:子程序切换不是线程切换,而由程序自身控制;因此没有线程切换的开销;和多线程比,线程数越多,优势越大
- 不需要多线程的锁机制:只有一个线程,不存在同时写变量的冲突,在协程中控制共享资源不用加锁,只需判断状态即可
- 只有协程无法利用多核,可以利用多进程+协程解决
3.yield的简单实现:底层实现机制
import queue,time
def consumer(name):
print("--->ready to eat baozi...")
while True:
new_baozi = yield #利用yield保存状态
print("[%s] is eating baozi %s" % (name,new_baozi))
#time.sleep(1)
def producer():
r = con.__next__()
r = con2.__next__()
n = 0
while 1:
time.sleep(1)
print("producer is making baozi %s and %s" %(n,n+1) )
con.send(n) #send()之前必须经过__next__()
con2.send(n+1)
n +=2
if __name__ == '__main__':
con=consumer("c1") #创建生成器,不执行
con2=consumer("c2")
p=producer()
#结果:
#--->ready to eat baozi...
#--->ready to eat baozi...
#producer is making baozi 0 and 1
#[c1] is eating baozi 0
#[c2] is eating baozi 1
#producer is making baozi 2 and 3
#[c1] is eating baozi 2
#[c2] is eating baozi 3
4.Greenlet模块:一个用C实现的协程模块(第三方模块)
相比与python自带的yield,可以在任意函数间随意切换而不需把这个函数先声明为generator
from greenlet import greenlet
def test1():
print(12)
gr2.switch() #执行test2();test1()挂起
print(34)
gr2.switch()
def test2():
print(56)
gr1.switch()
print(78)
gr1 = greenlet(test1) #将函数封装进greenlet对象
gr2 = greenlet(test2)
gr1.switch() #执行gr1(封装进gr1的test1()函数)
5.Gevent模块(第三方模块)
#一个爬虫:
import requests,time,gevent
start=time.time()
def f(url):
print('GET: %s' % url)
resp = requests.get(url) #爬取网页内容;resp即为网页内容
data = resp.text
print('%d bytes received from %s.' % (len(data), url))
#利用协程:
gevent.joinall([gevent.spawn(f,'https://www.python.org/'),gevent.spawn(f,'https://www.yahoo.com/'),gevent.spawn(f,'https://www.baidu.com/'),gevent.spawn(f,'https://www.sina.com.cn/'),])
#单进程:
# f('https://www.python.org/')
# f('https://www.yahoo.com/')
# f('https://baidu.com/')
# f('https://www.sina.com.cn/')
print("cost time:",time.time()-start)
二.上下文管理器(contextlib模块)
1.作用:代码块执行前准备,代码块执行后收拾
2.如何使用上下文管理器:
#打开一个文件,并写入"hello world":
filename="my.txt"
mode="w"
f=open(filename,mode)
f.write("hello world")
f.close()
当发生异常时(如磁盘写满),就没有机会执行第5行
#采用try-finally语句块进行包装:
writer=open(filename,mode)
try:
writer.write("hello world")
finally:
writer.close()
当我们进行复杂的操作时,try-finally语句就会变得丑陋
#采用with语句重写:
with open(filename,mode) as writer:
writer.write("hello world")
as指代了从open()函数返回的内容,并把它赋给了新值
with完成了try-finally的任务
3.自定义上下文管理器
with语句的作用类似于try-finally,提供一种上下文机制
要应用with语句的类,其内部必须提供两个内置函数__enter__和__exit__
前者在主体代码执行前执行,后者在主体代码执行后执行
as后面的变量是在__enter__函数中返回的
class echo():
def output(self):
print("hello world")
def __enter__(self):
print("enter")
return self #可以返回任何希望返回的东西
def __exit__(self,exception_type,value,trackback):
print("exit")
if exception_type==ValueError:
return True
else:
return False
with echo() as e:
e.output()
#结果:
#enter #执行__enter__()
#hello world #执行主体代码
#exit #执行__exit__()
完备的__exit__函数如下:
def __exit__(self,exc_type,exc_value,exc_tb)
#exc_type:异常类型
#exc_value:异常值
#exc_tb:异常追踪信息
当__exit__返回True时,异常不传播
4.contextlib模块:提供更易用的上下文管理器,无需创建类及__enter__和__exit__这两个方法
通过Generator实现
contextlib中的contextmanager作为装饰器来提供一种针对函数级别的上下文管理机制,常用框架如下:
from contextlib import contextmanager
@contextmanager
def make_context():
print('enter')
try:
yield "ok"
except RuntimeError:
print('error',err)
finally:
print('exit')
with make_context() as value:
print(value)
#结果:
#enter
#ok #执行主体代码
#exit
yield写入try-finally中是为了保证异常安全(能处理异常)
as后的变量的值由yield返回
yield前的语句可看作代码块执行前操作
yield后的操作可以看作在__exit__函数中的操作
以线程锁为例:
@contextlib.contextmanager
def loudLock():
lock=Lock()
print('Locking')
lock.acquire()
yield
print('Releasing')
lock.release()
with loudLock():
print('Doing something that needs locking')
#结果:
#Locking
#Doing something that needs locking
#Releasing
5.contextlib.nested:减少嵌套
with open(filename,mode) as reader:
with open(filename1,mode1) as writer:
writer.write(reader.read())
可以通过contextlib.nested进行简化:
with contextlib.nested(open(filename,mode),open(filename1,mode1)) as (reader,writer):
writer.write(reader.read())
在python 2.7及以后,被一种新的语法取代:
with open(filename,mode) as reader,open(filename1,mode1) as writer:
writer.write(reader.read())
6.contextlib.closing()
file类直接支持上下文管理器API,但有些表示打开句柄的对象并不支持,如urllib.urlopen()返回的对象;还有些遗留类,使用close()方法而不支持上下文管理器API
为了确保关闭句柄,需要使用closing()为这些对象创建一个上下文管理器(调用类的close方法)