为了避免多线程竞争导致的数据安全性问题。CPython 引入GIL全局解释器锁,使得任何时刻仅有一个线程在执行。即便在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程。缺点就是使得多核处理器退化成单核处理器,多线程的执行效率很不好
使用了yield关键字的函数不再是函数,而是生成器。
协程可以简单为轻量级线程,由于是在用户态下实现的,也叫用户态线程
内核并没有实现协程,协程是语言层面自己实现的,不同语言实现方案不一样,py中是利用yeild实现
具体实现如下:
多线程之间的同步靠GIL,而多个协程在一个线程内,同步依靠的是生成器,你执行时我等待,我执行时你等待、
协程相对线程的优势:
最大的优势就是协程极高的执行效率。因为多个协程在同一个线程内,所以协程之间的切换开销很小。相比线程之间的切换开销要小的多。
第二大优势就是不需要加锁, 因为多个协程在同一个线程内,所以无需加锁。
怎么选择?
IO密集型适合用多协程
cpu密集型适合多进程(因为进程可以利用多核,py里面线程和协程都不能利用)
协程的使用:
import asyncio
async def worker_1():
print('worker_1 start')
await asyncio.sleep(1)
print('worker_1 done')
async def worker_2():
print('worker_2 start')
await asyncio.sleep(2)
print('worker_2 done')
async def main():
task1 = asyncio.create_task(worker_1())
task2 = asyncio.create_task(worker_2())
print('before await')
await task1
print('awaited worker_1')
await task2
print('awaited worker_2')
%time asyncio.run(main())
########## 输出 ##########
before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
Wall time: 2.01 s
剖析上面代码的运行原理:
1、asyncio.run(main()),程序进入 main() 函数,事件循环开启;
2、task1 和 task2 任务被创建,并进入事件循环等待运行;运行到 print,输出 ‘before await’;
3、await task1 执行,用户选择从当前的主任务中切出,事件调度器开始调度 worker_1;
4、worker_1 开始运行,运行 print 输出’worker_1 start’,然后运行到 await asyncio.sleep(1), 从当前任务切出,事件调度器开始调度 worker_2;
5、worker_2 开始运行,运行 print 输出 ‘worker_2 start’,然后运行 await asyncio.sleep(2) 从当前任务切出;
6、以上所有事件的运行时间,都应该在 1ms 到 10ms 之间,甚至可能更短,事件调度器从这个时候开始暂停调度;
7、一秒钟后,worker_1 的 sleep 完成,事件调度器将控制权重新传给 task_1,输出 ‘worker_1 done’,task_1 完成任务,从事件循环中退出;
8、await task1 完成,事件调度器将控制器传给主任务,输出 ‘awaited worker_1’,·然后在 await task2 处继续等待;
9、两秒钟后,worker_2 的 sleep 完成,事件调度器将控制权重新传给 task_2,输出 ‘worker_2 done’,task_2 完成任务,从事件循环中退出;
10、主任务输出 ‘awaited worker_2’,协程全任务结束,事件循环结束。
import asyncio
import random
async def consumer(queue, id):
while True:
val = await queue.get()
print('{} get a val: {}'.format(id, val))
await asyncio.sleep(1)
async def producer(queue, id):
for i in range(5):
val = random.randint(1, 10)
await queue.put(val)
print('{} put a val: {}'.format(id, val))
await asyncio.sleep(1)
async def main():
queue = asyncio.Queue()
consumer_1 = asyncio.create_task(consumer(queue, 'consumer_1'))
consumer_2 = asyncio.create_task(consumer(queue, 'consumer_2'))
producer_1 = asyncio.create_task(producer(queue, 'producer_1'))
producer_2 = asyncio.create_task(producer(queue, 'producer_2'))
await asyncio.sleep(10)
consumer_1.cancel()
consumer_2.cancel()
await asyncio.gather(consumer_1, consumer_2, producer_1, producer_2, return_exceptions=True)
asyncio.run(main())