Python-asyncio的使用-2

asyncio模块提供了使用协程构建并发应用的工具。它使用一种单线程单进程的方式实现并发,应用的各个部分彼此合作,可以显示的切换任务,一般会在程序阻塞I/O操作的时候发生上下文切换如等待读写文件,或者请求网络。同时asyncio也支持调度代码在将来的某个特定事件运行,从而支持一个协程等待另一个协程完成,以处理系统信号和识别其他一些事件。

对于其他的并发模型大多数采用的都是线性的方式编写。并且依赖于语言运行时系统或操作系统底层的底层线程或进程来适当的改变上下文,而基于asyncio的应用要求应用代码显示的处理上下文切换。asyncio提供的框架以事件循环为中心,程序开启一个无限的循环,程序会把一些函数注册到事件循环当中,当满足事件发生的时候,调用相应的协程函数。

锁(Lock)

锁可以用来保护一个共享资源的访问,只有锁的持有者可以使用这个资源。如果有多个请求需要用到这个锁,那么会将其阻塞,以保证一次只有一个持有者。它是在解锁状态下创建的,它有两个基本方法:acquire()release()。当状态解除锁定时,acquire()将状态更改为locked并立即返回。当状态被锁定时,acquire()阻塞,直到另外一个协同程序中的release()调用将其改为解锁,然后acquire()调用将其重置为锁定并返回。

release()方法只能在锁定状态下调用,它将状态更改为解锁并立即返回。如果试图释放一个未锁定的锁,将引发一个RuntimeError。此类不是线程安全的!请看下面例子:

import asyncio
from functools import partial

async def release(lock):
    print("relase-1: {}".format(lock.locked()))
    lock.release()
    print("relase-2: {}".format(lock.locked()))
    
async def coroutine_1(lock):
    print("进入coroutine_1: {}".format(lock.locked()))
    await lock.acquire()
    print("coroutine_1此时锁的状态: {}".format(lock.locked()))
    lock.release()
    print("coroutine_1此时锁的状态-2: {}".format(lock.locked()))
    
async def coroutine_2(lock):
    print("进入coroutine_2: {}".format(lock.locked()))
    async with lock:
        print("coroutine_2此时锁的状态: {}".format(lock.locked()))
        
    print("coroutine_2此时锁的状态-2: {}".format(lock.locked()))
    
async def coroutine_3(lock):
    print("进入coroutine_3: {}".format(lock.locked()))
    try:
        lock.release()
    except RuntimeError as e:
        print("触发RuntimeError错误")
        
async def main(loop):
    print("进入主协程")
    # 创建一个锁
    lock = asyncio.Lock()
    loop.call_later(0.1, partial(release, lock))
    await asyncio.wait([coroutine_1(lock), coroutine_2(lock), coroutine_3(lock)])
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main(loop))
    finally:
        loop.close()

可能输出的结果如下:

进入主协程
进入coroutine_1: False
coroutine_1此时锁的状态: True
coroutine_1此时锁的状态-2: False
进入coroutine_2: False
coroutine_2此时锁的状态: True
coroutine_2此时锁的状态-2: False
进入coroutine_3: False
触发RuntimeError错误

通过上面代码可以得出以下结论:

  • lock.acquire()需要加await
  • lock.release()不需要加await
  • 在加锁之前coroutine_1coroutine_2coroutine_3是并发执行的
  • 锁有两种使用方式,一是像cortouine_1中使用await lock.acquire()加锁,这种方式需要在结束的使用调用lock.release()来释放锁;二是像coroutine_2中使用async with lock异步上下文进行锁定,这种方式不需要手动进行释放操作
  • 如果没有使用acquire进行加锁,却试图使用release去释放锁,那么将会引发RuntimeError异常

常用方法如下:

  • locked(): 如果获得了锁,则返回True,否则返回False
  • acquire(): 获取锁。此方法将一直锁定直到解锁,将其设置为locked,并返回True
  • release(): 释放锁。当锁被锁定,重置为解锁。无返回值

事件(Event)

asyncio.Event基于threading.Event,允许多个消费者等待某个事件发生,而不必寻找一个特定值与通知关联!事件管理一个标识,该标识可以通过set()方法设置为True,通过clear()方法重置为False,wait()方法标阻塞,直到标记为True。该标识最初为False,此类不是线程安全的!

import asyncio
from functools import partial

def callback(event):
    print("callback当前Event状态:{}".format(event.is_set()))
    event.set()
    print("callback当前Event状态-2:{}".format(event.is_set()))
    
async def child(name, event):
    print("{}当前Event状态: {}".format(name, event.is_set()))
    await event.wait()
    print("{}当前Event状态: {}".format(name, event.is_set()))
    
async def main(loop):
    event = asyncio.Event()
    print("Event创建之后的状态为: {}".format(event.is_set()))
    loop.call_later(0.1, partial(callback, event))
    tasks = [child("name{}".format(i), event) for i in range(5)]
    await asyncio.wait(tasks)
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main(loop))
    finally:
        loop.close()

可能输出的结果如下:

Event创建之后的状态为: False
name2当前Event状态: False
name3当前Event状态: False
name4当前Event状态: False
name0当前Event状态: False
name1当前Event状态: False
callback当前Event状态:False
callback当前Event状态-2:True
name2当前Event状态: True
name3当前Event状态: True
name4当前Event状态: True
name0当前Event状态: True
name1当前Event状态: True

从结果可以看出,一个触发了事件,child就会立即启动,不需要得到事件对象上唯一的锁!

常用方法如下:

  • clear(): 将内部标识重置为False
  • is_set(): 返回内部标识状态
  • set(): 将内部标识设置为True,所有等待它为True的协程将被唤醒
  • wait(): 阻塞,直到内部标识设置为True

条件(condition)

Condition的做法与Event类似,只不过不是通知所有的协程等待的协程,被唤醒的等待协程数目由notify()的一个参数控制,此类不是线程安全的。请看如下代码:

import asyncio

async def child(cond, name):
    print("{}进入child, cond状态为: {}".format(name, cond.locked()))
    async with cond:
        await cond.wait()
        print("{}在child开始执行, cond状态为: {}".format(name, cond.locked()))
        
    print("{}在child执行完毕, cond状态为: {}".format(name, cond.locked()))

async def coroutine_1(cond):
    print("进入coroutine_1, cond状态为: {}".format(cond.locked()))
    await asyncio.sleep(2)
    for i in range(1, 4):
        async with cond:
            print("coroutine_1-资源变得可用")
            cond.notify(n=i)
            
        await asyncio.sleep(1)
        
async def coroutine_2(cond):
    print("进入coroutine_2, cond状态为: {}".format(cond.locked()))
    await asyncio.sleep(2)
    async with cond:
        print("coroutine_2-资源变得可用")
        cond.notify_all()
        
async def main(loop):
    print("进入main协程")
    cond = asyncio.Condition()
    print("condition的状态为:{}".format(cond.locked()))
    loop.create_task(coroutine_1(cond))
    tasks = [child(cond, "name{}".format(i)) for i in range(1, 6)]
    await asyncio.wait(tasks)
    loop.create_task(coroutine_2(cond))
    tasks = [child(cond, "name{}".format(i)) for i in range(6, 11)]
    await asyncio.wait(tasks)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main(loop))
    finally:
        loop.close()

输出结果如下:

进入main协程
condition的状态为:False
进入coroutine_1, cond状态为: False
name3进入child, cond状态为: False
name4进入child, cond状态为: False
name1进入child, cond状态为: False
name5进入child, cond状态为: False
name2进入child, cond状态为: False
coroutine_1-资源变得可用
name3在child开始执行, cond状态为: True
name3在child执行完毕, cond状态为: False
coroutine_1-资源变得可用
name4在child开始执行, cond状态为: True
name4在child执行完毕, cond状态为: False
name1在child开始执行, cond状态为: True
name1在child执行完毕, cond状态为: False
coroutine_1-资源变得可用
name5在child开始执行, cond状态为: True
name5在child执行完毕, cond状态为: False
name2在child开始执行, cond状态为: True
name2在child执行完毕, cond状态为: False
进入coroutine_2, cond状态为: False
name10进入child, cond状态为: False
name9进入child, cond状态为: False
name8进入child, cond状态为: False
name6进入child, cond状态为: False
name7进入child, cond状态为: False
coroutine_2-资源变得可用
name10在child开始执行, cond状态为: True
name10在child执行完毕, cond状态为: False
name9在child开始执行, cond状态为: True
name9在child执行完毕, cond状态为: False
name8在child开始执行, cond状态为: True
name8在child执行完毕, cond状态为: False
name6在child开始执行, cond状态为: True
name6在child执行完毕, cond状态为: False
name7在child开始执行, cond状态为: True
name7在child执行完毕, cond状态为: False

通过输出结果进行分析,使用notify方法每次通知n个child,notify_all方法一次性的通知全部child。

常用方法如下:

  • acquire(): 获取锁。此方法将一直锁定直到解锁,将其设置为locked,并返回True
  • notify(n=1): 默认情况下,唤醒一个在此条件下等待的协程(如果有的话)。如果调用的协程在调用此方法时没有获得锁,则会引发RuntimeError异常。此方法最多唤醒n个等待条件变量的协程
  • locked(): 如果获得了锁,则返回True,否则返回False
  • notify_all(): 唤醒所有此条件下等待的协程。如果调用的协程在调用此方法时没有获得锁,则会引发RuntimeError异常。
  • release(): 释放锁。当锁被锁定,重置为解锁。无返回值
  • wait(): 阻塞,直到内部标识设置为True
  • wait_for(predicate): 阻塞,直到predicate的返回值为True

wait_for使用如下:

async def child(cond, name):
    print("{}进入child, cond状态为: {}".format(name, cond.locked()))
    async with cond:
        await cond.wait_for(coroutine)
        print("{}在child开始执行, cond状态为: {}".format(name, cond.locked()))

    print("{}在child执行完毕, cond状态为: {}".format(name, cond.locked()))

def coroutine():
    time.sleep(2)
    return True

Condition对锁的一些操作与Lock一样!

队列

Queue

Queue(maxsize=0, *, loop=None)先进先出队列,如果maxsize小于或等于0,则队列大小为无穷大。如果它是一个大于0的整数,那么当队列达到maxsize时,put()的yield将阻塞,直到get()删除一个项目为止。此类是线程不安全的!

与标准库Queue不同,qsize()可以可靠的直到此队列的大小,因为在调用qsize()和队列执行操作之间不会中断单线程异步应用程序。请看如下列子:

import asyncio

async def get_item(queue):
    item = await queue.get()
    print("get_item: {}".format(item))
    return item
    
async def main():
    queue = asyncio.Queue()
    [await queue.put("name{}".format(i)) for i in range(5)]
    tasks = [get_item(queue) for _ in range(5)]
    await asyncio.wait(tasks)
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()

输出结果如下:

get_item: name0
get_item: name1
get_item: name2
get_item: name3
get_item: name4

LifoQueue

LifoQueue(maxsize=0, *, loop=None)Queue的子类,先进后出队列。将上面代码修改为使用LifoQueue,如下:

import asyncio

async def get_item(queue):
    item = await queue.get()
    print("get_item: {}".format(item))
    return item
    
async def main():
    queue = asyncio.LifoQueue()
    [await queue.put("name{}".format(i)) for i in range(5)]
    tasks = [get_item(queue) for _ in range(5)]
    await asyncio.wait(tasks)
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()

输出结果如下:

get_item: name4
get_item: name3
get_item: name2
get_item: name1
get_item: name0

PriorityQueue

PriorityQueue(maxsize=0, *, loop=None)Queue的子类,优先级队列,优先级的值越小,越先执行。put()操作与上面的两种队列有所不同,"项目"的形式是一个元组:(priority_number, data),priority_number为优先级数值!将上面代码修改为使用PriorityQueue,如下:

import asyncio
import random

async def get_item(queue):
    item = await queue.get()
    print("get_item: {}".format(item))
    return item
    
async def main():
    queue = asyncio.PriorityQueue()
    [await queue.put((random.randint(1, 6), "name{}".format(i))) for i in range(5)]
    tasks = [get_item(queue) for _ in range(5)]
    await asyncio.wait(tasks)
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()

输出结果如下:

get_item: (3, 'name4')
get_item: (4, 'name1')
get_item: (6, 'name0')
get_item: (6, 'name2')
get_item: (6, 'name3')

Queue常用方法如下:

  • empty(): 如果队列为空返回True,否则返回False
  • full(): 如果队列中有maxsize个项目,则返回True,否则返回False
  • get(): 从队列中删除并返回一个项目,如果队列为空,则会阻塞直到有一个项目可用。需使用await阻塞
  • get_nowait(): 从队列中删除并返回一个项目,如果队列为空,则会引发QueueEmpty异常
  • join(): 阻塞直到队列中的所有项目都已获得并处理,需使用await阻塞
  • put(item): 将项目放入队列,如果队列已满,则会阻塞直到有可用插槽为止。需使用await阻塞
  • put_nowait(item): 将项目放入队列而不会阻塞
  • qsize(): 返回队列中的项目数
  • task_done(): 表示先前排队的任务已完成
  • maxsize: 队列中允许的项目数

信号量(Semaphore)

Semaphore(value=1, *, loop=None)通过并发量可以控制协程的并发数,爬虫操作中使用该方法减少并发量,可以减少对服务器的压力。请看如下代码:

import time
import asyncio

async def run(sem, name):
    async with sem:
        print("{}进入run协程, {}".format(name, time.time()))
        await asyncio.sleep(1)
        
async def main():
    sem = asyncio.Semaphore(5)
    tasks = [run(sem, "name{}".format(i)) for i in range(18)]
    await asyncio.wait(tasks)

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()

输出结果如下:

name3进入run协程, 1585126139.9968233
name9进入run协程, 1585126139.9968233
name15进入run协程, 1585126139.9968233
name4进入run协程, 1585126139.9968233
name10进入run协程, 1585126139.9968233
name16进入run协程, 1585126140.9972842
name1进入run协程, 1585126140.9972842
name5进入run协程, 1585126140.9972842
name11进入run协程, 1585126140.9972842
name17进入run协程, 1585126140.9972842
name0进入run协程, 1585126141.9978023
name6进入run协程, 1585126141.9978023
name12进入run协程, 1585126141.9978023
name7进入run协程, 1585126141.9978023
name13进入run协程, 1585126141.9978023
name2进入run协程, 1585126142.9983144
name8进入run协程, 1585126142.9983144
name14进入run协程, 1585126142.9983144

从结果可以看到,并发量为5!

常用方法如下:

  • locked(): 如果获得了锁,则返回True,否则返回False
  • acquire(): 获取锁。此方法将一直锁定直到解锁,将其设置为locked,并返回True
  • release(): 释放锁。当锁被锁定,重置为解锁。无返回值

多线程与队列相关知识:https://blog.csdn.net/y472360651/article/details/73495122

自此,Over~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章