有了进程、线程,为什么还有个协程呢?
因为进程或线程的切换都是由操作系统决定的,存在切换开销问题,而协程的切换是由程序决定的,没有线程切换开销问题,执行性能更高些。当线程数量越多,协程优势更明显。
另外,协程是单线程的(也就无法利用多核CPU),操作共享变量不需要加锁,所以执行效率比多线程高。
Python的协程是通过生成器generator实现的,asyncio是Python 3.4版本引入的标准库,提供异步IO的支持。
以前的协程定义方式是在方法上加一个装饰器@asyncio.coroutine
,新语法是在方法前加一个关键字async
即可。相应的,方法里的yield from
也要变成await
。
协程执行分以下4个步骤:
- 定义协程
async def get_test(url): pass
- 获取事件循环
loop = asyncio.get_event_loop()
- 获取tasks
tasks = [get_test(url) for url in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
- 执行协程 协程并发时,
loop.run_until_complete(asyncio.wait(tasks))
,当只有一个协程时,loop.run_until_complete(get_test(url))
直接上代码:
- 装饰器方式:
import asyncio
@asyncio.coroutine
def get_test(url):
conn = asyncio.open_connection(url, 80)
reader, writer = yield from conn
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % url
writer.write(header.encode('utf-8'))
yield from writer.drain()
text = yield from reader.read()
print('%s text > %s' % (url, text))
writer.close()
loop = asyncio.get_event_loop()
tasks = [get_test(url) for url in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
# run_until_complete接受future,task是future的子类,接受coroutine时,会自动封装成task
loop.run_until_complete(asyncio.wait(tasks))
# 事件循环不再接受新的协程任务
loop.close()
- async方式:
import asyncio
async def get_test(url):
conn = asyncio.open_connection(url, 80)
reader, writer = await conn
header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % url
writer.write(header.encode('utf-8'))
await writer.drain()
text = await reader.read()
print('%s text > %s' % (url, text))
writer.close()
loop = asyncio.get_event_loop()
tasks = [get_test(url) for url in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
# run_until_complete接受future,task是future的子类,接受coroutine时,会自动封装成task
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
- asyncio比较麻烦,需要自己定义报头,可以安装aiohttp库。aiohttp默认最大支持1024个协程同时进行,因此下面的代码加入了并发限制:
import asyncio
import aiohttp
async def get_test(url, semaphore):
async with semaphore:
async with aiohttp.ClientSession() as session:
async with session.get(url) as res:
print('%s text > %s' % (url, res.status))
# 需要睡一会,才能体现并发限制效果,不能用time.sleep(2)
await asyncio.sleep(2)
return await res.read()
# aiohttp默认同时最大支持1024个协程的进行
semaphore = asyncio.Semaphore(2)
loop = asyncio.get_event_loop()
# 返回future对象
tasks = [asyncio.ensure_future(get_test(url, semaphore)) for url in
['https://www.sina.com.cn', 'https://www.sohu.com', 'https://www.163.com', 'https://www.sohu.com',
'https://www.163.com', 'https://www.sohu.com', 'https://www.163.com']]
# run_until_complete接受future,task是future的子类,接受coroutine时,会自动封装成task
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
for v in tasks:
print(v.result())
执行时,控制台每2s输出2个结果。