Python并发编程之协程(coroutine)

30 篇文章 8 订阅

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

与线程相比,协程更轻量。一个Python线程大概占用8M内存,而一个协程只占用1KB不到内存。协程更适用于IO密集型的应用。

实例:

def jumping_range(N):
    index = 0
    while index < N:
        # 将send()发送的信息赋值给jump
        jump = yield index    
        if jump is None:
            jump = 1
        index += jump

if __name__ == '__main__':
    itr = jumping_range(5)     #生成器函数,遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值
    print(next(itr))      #index为0
    print(itr.send(2))    #send()方法将值赋给jump
    print(next(itr))      #index = 1+2
    print(itr.send(-1))   #index = 3+(-1)

执行输出结果如下:

0
2
3
2

为什么要使用协程?

举个例子:
假如我们做一个爬虫。我们要爬取多个网页,这里简单举例两个网页(两个spider函数),获取HTML(耗IO),然后再对HTML对行解析取得我们感兴趣的数据。

如果能在get_html()这里暂停一下,不用傻乎乎地去等待网页返回,而是去做别的事。等过段时间再回过头来到刚刚暂停的地方,接收返回的html内容,然后还可以接下去解析parse_html(html)

试着思考一下,假如没有协程,我们要写一个并发程序。可能有以下问题

1)使用最常规的同步编程要实现异步并发效果并不理想,或者难度极高。
2)由于GIL锁的存在,多线程的运行需要频繁的加锁解锁,切换线程,这极大地降低了并发性能;

而协程的出现,刚好可以解决以上的问题。它的特点有

  1. 协程是在单线程里实现任务的切换的
  2. 利用同步的方式去实现异步
  3. 不再需要锁,提高了并发性能

yield from 

yield from 是在Python3.3才出现的语法,yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。

yield和yield from 对比:

使用yield:

# 字符串
astr='ABC'

# 列表
alist=[1,2,3]

# 字典
adict={"name":"wangbm","age":18}

# 生成器
agen=(i for i in range(4,8))

def gen(*args, **kw):
    for item in args:
        for i in item:
            yield i

new_list=gen(astr, alist, adict, agen)
print(list(new_list))

#输出结果:
 ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

使用yield from:

#字符串
astr='ABC'

# 列表
alist=[1,2,3]

# 字典
adict={"name":"wangbm","age":18}

# 生成器
agen=(i for i in range(4,8))

def gen(*args, **kw):
    for item in args:
        yield from item

new_list=gen(astr, alist, adict, agen)
print(list(new_list))

#输出结果:
 ['A', 'B', 'C', 1, 2, 3, 'name', 'age', 4, 5, 6, 7]

yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。

当 yield from 后面加上一个生成器后,就实现了生成的嵌套。如果自己用yield去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。

生成器嵌套相关概念:

1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数

举个计算平均数的例子:

# 子生成器(平均数)
def average_gen():
    total = 0
    count = 0
    average = 0
    while True:
        new_num = yield average
        count += 1
        total += new_num
        average = total/count

# 委托生成器
def proxy_gen():
    while True:
        yield from average_gen()
  

if __name__ == '__main__':
    calc_average = proxy_gen()    # 调用方
    next(calc_average)            # 预激活生成器,保存average为0
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0

输出结果如下:
10.0
20.0
30.0

 委托生成器的作用是:在调用方与子生成器之间建立一个双向通道。即调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。

还可以在yield from前面看到可以赋值。

实例:

# 子生成器
def average_gen():
    total,count,average = 0,0,0
    while True:
        new_num = yield average
        if new_num is None:
            break
        count += 1
        total += new_num
        average = total/count

    # 每一次return,都意味着当前协程结束。
    return total,count,average

# 委托生成器
def proxy_gen():
    while True:
        # 只有子生成器return了,yield from左边的变量才会被赋值,后面的代码才会执行。
        total, count, average = yield from average_gen()
        print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))    

if __name__ == '__main__':
    calc_average = proxy_gen()
    next(calc_average)            # 预激活协程
    print(calc_average.send(10))  # 打印:10.0
    print(calc_average.send(20))  # 打印:15.0
    print(calc_average.send(30))  # 打印:20.0
    calc_average.send(None)      # 结束协程
    # 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程

输出结果如下:
10.0
15.0
20.0
计算完毕!!
总共传入 3 个数值, 总和:60,平均数:20.0

yield from 有什么过人之处,让我们非要用它不可————因为它可以帮我们处理异常

具体yield from为我们做了哪些事?结合以下说明。

  1. 迭代器(即可指子生成器)产生的值直接返还给调用者
  2. 任何使用send()方法发给委派生产器(即外部生产器)的值被直接传递给迭代器。如果send值是None,则调用迭代器next()方法;如果不为None,则调用迭代器的send()方法。如果对迭代器的调用产生StopIteration异常,委派生产器恢复继续执行yield from后面的语句;若迭代器产生其他任何异常,则都传递给委派生产器。
  3. 子生成器可能只是一个迭代器,并不是一个作为协程的生成器,所以它不支持.throw()和.close()方法,即可能会产生AttributeError 异常。
  4. 除了GeneratorExit 异常外的其他抛给委派生产器的异常,将会被传递到迭代器的throw()方法。如果迭代器throw()调用产生了StopIteration异常,委派生产器恢复并继续执行,其他异常则传递给委派生产器。
  5. 如果GeneratorExit异常被抛给委派生产器,或者委派生产器的close()方法被调用,如果迭代器有close()的话也将被调用。如果close()调用产生异常,异常将传递给委派生产器。否则,委派生产器将抛出GeneratorExit 异常。
  6. 当迭代器结束并抛出异常时,yield from表达式的值是其StopIteration 异常中的第一个参数。
  7. 一个生成器中的return expr语句将会从生成器退出并抛出 StopIteration(expr)异常。

 

asyncio

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

1. asyncio的几个概念

  • event_loop 事件循环:程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。
  • coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。
  • future 对象: 代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
  • task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将 coroutine 封装成一个 Future 对象。
  • async/await 关键字:python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。

 

2. 如何定义/创建协程

在一个函数前面加上 async 关键字,这个函数对象是一个协程(通过isinstance函数可以判断确实是Coroutine类型。)

生成器是协程的基础,如何将一个生成器,直接变成协程使用呢?

import asyncio
from collections.abc import Generator, Coroutine

'''只要在一个生成器函数头部用上 @asyncio.coroutine 装饰器
就能将这个函数对象,【标记】为协程对象。注意这里是【标记】,划重点。
实际上,它的本质还是一个生成器。
标记后,它实际上已经可以当成协程使用。'''

@asyncio.coroutine
def hello():
    # 异步调用asyncio.sleep(1):
    yield from asyncio.sleep(1)


if __name__ == '__main__':
    coroutine = hello()    # 生成协程对象,并不会运行函数内的代码
    print(isinstance(coroutine, Generator))  # True
    print(isinstance(coroutine, Coroutine))  # False

 

协程完整的工作流程:

  • 定义/创建协程对象
  • 将协程转为task任务
  • 定义事件循环对象容器
  • 将task任务扔进事件循环对象中触发

那我们就按照流程来实现协程,举例:

import asyncio

async def hello(name):
    print('Hello,', name)

# 定义协程对象
coroutine = hello("World")

# 定义事件循环对象容器
loop = asyncio.get_event_loop()

# 将协程转为task任务
task = loop.create_task(coroutine)

# 将task任务扔进事件循环对象中并触发
loop.run_until_complete(task)

输出结果如下:
Hello, World

 

3. await与yield对比

前面我们说,await用于挂起阻塞的异步调用接口。其作用在一定程度上类似于yield。

值得注意的是在一定程度上,意思是效果上一样(都能实现暂停的效果),但是功能上却不兼容。

就是你不能在生成器中使用await,也不能在async 定义的协程中使用yield

普通函数(生成器)中不能使用 await:

普通函数中 不能使用 await

async 中 不能使用yield:

 

async 中 不能使用yield

除此之外呢,还有一点很重要的。

  • yield from 后面可接 可迭代对象,也可接future对象/协程对象;
  • await 后面必须要接 future对象/协程对象

如何验证呢?

之前说过yield from 后面可接 可迭代对象。接下来,就只要验证,yield fromawait都可以接future对象/协程对象就可以了。

验证之前呢,要先介绍一下函数asyncio.sleep(n),这是asyncio自带的工具函数,他可以模拟IO阻塞,他返回的是一个协程对象。

func = asyncio.sleep(2)
print(isinstance(func, Future))      # False
print(isinstance(func, Coroutine))   # True

还有,要学习如何创建Future对象,前面概念里说过,Task是Future的子类,这么说,我们只要创建一个task对象即可:

import asyncio
from asyncio.futures import Future

async def hello(name):
    await asyncio.sleep(2)
    print('Hello, ', name)

coroutine = hello("World")

# 将协程转为task对象
task = asyncio.ensure_future(coroutine)

print(isinstance(task, Future))   # True

好了,接下来,开始验证:

验证通过

 

4. 绑定回调函数

异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。

在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调函数了。

回调的实现,有两种:

一种是绝大部分程序员喜欢的,利用的同步编程实现的回调,这要求我们要能够有办法取得协程的await的返回值:

import asyncio
import time

async def _sleep(x):
    time.sleep(2)
    return '暂停了{}秒!'.format(x)


coroutine = _sleep(2)
loop = asyncio.get_event_loop()

task = asyncio.ensure_future(coroutine)
loop.run_until_complete(task)

# task.result() 可以取得返回结果
print('返回结果:{}'.format(task.result()))


输出:
返回结果:暂停了2秒!

还有一种是通过asyncio自带的添加回调函数功能来实现:

import time
import asyncio


async def _sleep(x):
    time.sleep(2)
    return '暂停了{}秒!'.format(x)

def callback(future):
    print('这里是回调函数,获取返回结果是:', future.result())

coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)

# 添加回调函数
task.add_done_callback(callback)

loop.run_until_complete(task)

输出:
这里是回调函数,获取返回结果是: 暂停了2秒!

 

5、协程中的并发

协程的并发,和线程一样。asyncio实现并发,就需要多个协程来完成任务,每当有任务阻塞的时候就await,然后其他协程继续工作。

第一步,当然是创建多个协程的列表:

# 协程函数
async def do_some_work(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

# 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)

# 将协程转成task,并组成list
tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

第二步,将这些协程注册到事件循环中去:

有两种方法:

  • 使用asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
  • 使用asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))

最后,return的结果,可以用task.result()查看。

for task in tasks:
    print('Task ret: ', task.result())

完整代码如下:

import asyncio

# 协程函数
async def do_some_work(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

# 协程对象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(4)

# 将协程转成task,并组成list
tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

输出:
Waiting:  1
Waiting:  2
Waiting:  4
Task ret:  Done after 1s
Task ret:  Done after 2s
Task ret:  Done after 4s

for task in tasks:
    print('Task ret: ', task.result())

 

6、协程中的嵌套

使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。

举个栗子:

import asyncio

# 用于内部的协程函数
async def do_some_work(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

# 外部的协程函数
async def main():
    # 创建三个协程对象
    coroutine1 = do_some_work(1)
    coroutine2 = do_some_work(2)
    coroutine3 = do_some_work(4)

    # 将协程转为task,并组成list
    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]

    # 【重点】:await 一个task列表(协程)
    # dones:表示已经完成的任务
    # pendings:表示未完成的任务
    dones, pendings = await asyncio.wait(tasks)

    for task in dones:
        print('Task ret: ', task.result())

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

如果这边,使用的是asyncio.gather(),是这么用的:

# 注意这边返回结果,与await不一样

results = await asyncio.gather(*tasks)
for result in results:
    print('Task ret: ', result)

输出还是一样的:

Waiting:  1
Waiting:  2
Waiting:  4
Task ret:  Done after 1s
Task ret:  Done after 2s
Task ret:  Done after 4s

可以发现这个例子完全是由 上面「协程中的并发」例子改编而来。

结果完全一样。只是把创建协程对象,转换task任务,封装成在一个协程函数里而已。外部的协程,嵌套了一个内部的协程

其实你如果去看下asyncio.await()的源码的话,你会发现下面这种写法看似没有嵌套,实际上内部也是嵌套的:

loop.run_until_complete(asyncio.wait(tasks))

7、协程中的状态

对于生成器有生成器的状态,同样,协程(准确的说,应该是Future对象,或者Task任务)有以下四种状态:

  • Pending:创建future,还未执行
  • Running:事件循环正在调用执行任务
  • Done:任务执行完毕
  • Cancelled:Task被取消后的状态

可在终端下用命令 python3 xx.py 执行下面这段代码:

import asyncio
import threading
import time

async def hello():
    print("Running in the loop...")
    flag = 0
    while flag < 1000:
        with open("F:\\test.txt", "a") as f:
            f.write("------")
        flag += 1
    print("Stop the loop")

if __name__ == '__main__':
    coroutine = hello()
    loop = asyncio.get_event_loop()
    task = loop.create_task(coroutine)

    # Pending:未执行状态
    print(task)
    try:
        t1 = threading.Thread(target=loop.run_until_complete, args=(task,))
        # t1.daemon = True
        t1.start()

        # Running:运行中状态
        time.sleep(1)
        print(task)
        t1.join()
    except KeyboardInterrupt as e:
        # 取消任务
        task.cancel()
        # Cacelled:取消任务
        print(task)
    finally:
        print(task)

顺利执行的话,将会打印 Pending -> Pending:Runing -> Finished 的状态变化。假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 Pending -> Cancelling -> Cancelling 的状态变化。

 

8、gather与wait的对比

把多个协程注册进一个事件循环中有两种方法:

  • 使用asyncio.wait()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
  • 使用asyncio.gather()
# 千万注意,这里的 「*」 不能省略
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks))

asyncio.gather 和 asyncio.wait 在asyncio中用得的比较广泛,现在对这两种方法进行对比研究。

 

我们用例子来说明,先定义一个协程函数:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number+1):
        print("Task %s: Compute factorial(%s)..." % (name, i))
        await asyncio.sleep(1)
        f *= i
    print("Task %s: factorial(%s) = %s" % (name, number, f))

(1)asyncio.wait()

接收的tasks,必须是一个list(列表)对象,这个list(列表)对象里,存放多个的task。

可以用asyncio.ensure_future转为task对象:

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

也可以不转为task对象。

tasks=[
       factorial("A", 2),
       factorial("B", 3),
       factorial("C", 4)
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

asyncio.wait返回

asyncio.wait 返回donespendings

  • dones:表示已经完成的任务
  • pendings:表示未完成的任务

如果我们需要获取,运行结果,需要自己去收集获取:

dones, pendings = await asyncio.wait(tasks)

for task in dones:
    print('Task ret: ', task.result())

wait还具有控制功能

import asyncio
import random


async def coro(tag):
    await asyncio.sleep(random.uniform(0.5, 5))

loop = asyncio.get_event_loop()

tasks = [coro(i) for i in range(1, 11)]


# 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(
    asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))
print("第一次完成的任务数:", len(dones))


# 【控制时间】:运行一秒后,就返回
dones2, pendings2 = loop.run_until_complete(
    asyncio.wait(pendings, timeout=1))
print("第二次完成的任务数:", len(dones2))


# 【默认】:所有任务完成后返回
dones3, pendings3 = loop.run_until_complete(asyncio.wait(pendings2))

print("第三次完成的任务数:", len(dones3))

loop.close()

输出结果如下:

第一次完成的任务数: 1
第二次完成的任务数: 4
第三次完成的任务数: 5

(2)asyncio.gather()

接收的就比较广泛了,他可以接收list对象,但是 * 不能省略

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(*tasks))

因为asyncio.gather()的第一个参数是 *coros_or_futures,它叫 非命名键值可变长参数列表,可以整合所有没有命名的变量,就可以下面这样:

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(
    factorial("A", 2),
    factorial("B", 3),
    factorial("C", 4),
))

甚至还可以这样:

loop = asyncio.get_event_loop()

group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)])

loop.run_until_complete(asyncio.gather(group1, group2, group3))

asyncio.gather()会把值直接返回给我们,不需要自己去收集获取。

results = await asyncio.gather(*tasks)

for result in results:
    print('Task ret: ', result)

 

 

下面我们将通过一个小实战,来对这些内容进行巩固。

在实战中,将会用到以下知识点:

  • 多线程的基本使用
  • Queue消息队列的使用
  • Redis的基本使用
  • asyncio的使用

9、动态添加协程

在实战之前,我们必须首先要学会在asyncio中如何将协程动态的添加到事件循环中的。

这如何实现呢,有两种方法:

  • 主线程是同步
import time
import asyncio
from queue import Queue
from threading import Thread

def start_loop(loop):
    # 一个在后台永远运行的事件循环
    asyncio.set_event_loop(loop)
    loop.run_forever()

def do_sleep(x, queue, msg=""):
    time.sleep(x)
    queue.put(msg)

queue = Queue()

new_loop = asyncio.new_event_loop()

# 定义一个线程,并传入一个事件循环对象
t = Thread(target=start_loop, args=(new_loop,))
t.start()

print(time.ctime())

# 动态添加两个协程
# 这种方法,在主线程是同步的
new_loop.call_soon_threadsafe(do_sleep, 6, queue, "第一个")
new_loop.call_soon_threadsafe(do_sleep, 3, queue, "第二个")

while True:
    msg = queue.get()
    print("{} 协程运行完..".format(msg))
    print(time.ctime())



输出:
Thu May 31 22:11:16 2018
第一个 协程运行完..
Thu May 31 22:11:22 2018
第二个 协程运行完..
Thu May 31 22:11:25 2018

由于是同步的,所以总共耗时6+3=9秒.

  • 主线程是异步的,这是重点,一定要掌握。
import time
import asyncio
from queue import Queue
from threading import Thread

def start_loop(loop):
    # 一个在后台永远运行的事件循环
    asyncio.set_event_loop(loop)
    loop.run_forever()

async def do_sleep(x, queue, msg=""):
    await asyncio.sleep(x)
    queue.put(msg)

queue = Queue()

new_loop = asyncio.new_event_loop()

# 定义一个线程,并传入一个事件循环对象
t = Thread(target=start_loop, args=(new_loop,))
t.start()

print(time.ctime())

# 动态添加两个协程
# 这种方法,在主线程是异步的
asyncio.run_coroutine_threadsafe(do_sleep(6, queue, "第一个"), new_loop)
asyncio.run_coroutine_threadsafe(do_sleep(3, queue, "第二个"), new_loop)

while True:
    msg = queue.get()
    print("{} 协程运行完..".format(msg))
    print(time.ctime())


输出:
Thu May 31 22:23:35 2018
第二个 协程运行完..
Thu May 31 22:23:38 2018
第一个 协程运行完..
Thu May 31 22:23:41 2018

由于是异步的,所以总共耗时max(6, 3)=6

 

实战:利用redis实现动态添加任务

对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg消息,worker用户来处理消息。

为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。

这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。

先到 https://github.com/MicrosoftArchive/redis/releases 下载 redis 并解压到你的路径。在当前路径运行cmd,相继运行Redis服务器(命令.\redis-server.exe)和Redis客户端(命令.\redis-cli.exe)。

 

 

并依次输入key=queue,value=5,3,1的消息。

 

一切准备就绪之后,我们就可以运行我们的代码了。

import time
import redis
import asyncio
from queue import Queue
from threading import Thread

def start_loop(loop):
    # 一个在后台永远运行的事件循环
    asyncio.set_event_loop(loop)
    loop.run_forever()

async def do_sleep(x, queue):
    await asyncio.sleep(x)
    queue.put("ok")

def get_redis():
    connection_pool = redis.ConnectionPool(host='127.0.0.1', db=0)
    return redis.Redis(connection_pool=connection_pool)

def consumer():
    while True:
        task = rcon.rpop("queue")
        if not task:
            time.sleep(1)
            continue
        asyncio.run_coroutine_threadsafe(do_sleep(int(task), queue), new_loop)


if __name__ == '__main__':
    print(time.ctime())
    new_loop = asyncio.new_event_loop()

    # 定义一个线程,运行一个事件循环对象,用于实时接收新任务
    loop_thread = Thread(target=start_loop, args=(new_loop,))
    loop_thread.setDaemon(True)
    loop_thread.start()
    # 创建redis连接
    rcon = get_redis()

    queue = Queue()

    # 子线程:用于消费队列消息,并实时往事件对象容器中添加新任务
    consumer_thread = Thread(target=consumer)
    consumer_thread.setDaemon(True)
    consumer_thread.start()

    while True:
        msg = queue.get()
        print("协程运行完..")
        print("当前时间:", time.ctime())


输出:
Thu May 31 23:42:48 2018
协程运行完..
当前时间: Thu May 31 23:42:49 2018

协程运行完..
当前时间: Thu May 31 23:42:51 2018

协程运行完..
当前时间: Thu May 31 23:42:53 2018

我们看下上面这部分代码,稍微介绍一下:

  • loop_thread:单独的线程,运行着一个事件对象容器,用于实时接收新任务。
  • consumer_thread:单独的线程,实时接收来自Redis的消息队列,并实时往事件对象容器中添加新任务。

 

我们在Redis,分别发起了5s3s1s的任务。

从结果来看,这三个任务,确实是并发执行的,1s的任务最先结束,三个任务完成总耗时5s

运行后,程序是一直运行在后台的,我们每一次在Redis中输入新值,都会触发新任务的执行。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 你可以使用 asyncio 库中的 async 和 await 关键字来运行协程。 例如: ```python import asyncio async def my_coroutine(param): # 协程函数体 result = do_something(param) return result async def main(): # 在主函数中运行协程 result = await my_coroutine(42) print(result) # 运行主函数 asyncio.run(main()) ``` 你也可以使用 asyncio.gather() 函数来同时运行多个协程,并获取它们的返回值: ```python import asyncio async def my_coroutine_1(param): # 协程函数体 result = do_something(param) return result async def my_coroutine_2(param): # 协程函数体 result = do_something_else(param) return result async def main(): # 并发运行协程 results = await asyncio.gather( my_coroutine_1(42), my_coroutine_2(43), ) print(results) # 运行主函数 asyncio.run(main()) ``` 在上面的示例中, my_coroutine_1 和 my_coroutine_2 会同时运行,并在它们都完成后,将它们的返回值放在一个列表中返回。 ### 回答2: 在Python中,我们可以使用协程Coroutine)来实现并发运行并获取返回值。协程是一种轻量级的线程,可以在运行过程中暂停和恢复,可以在一个线程中执行多个任务,实现并发效果。 使用Python的asyncio库可以轻松实现协程,并发运行协程的方式有多种,以下是其中一种常见的方式,使用asyncio.gather()函数来同时运行多个协程,并获取其返回值。 首先,我们需要定义多个协程函数,这些函数可以是普通的函数加上@asyncio.coroutine装饰器,也可以是async def定义的异步函数。这些协程函数可以是耗时的任务,如网络请求、IO读写等。 然后,我们使用asyncio.gather()函数来并发运行这些协程函数,该函数接受一个或多个协程对象作为参数,并返回一个包含所有协程返回值的列表。 最后,我们可以使用事件循环(Event Loop)来运行整个程序,并获取协程函数的返回值。事件循环是协程并发运行的引擎,负责调度和执行协程任务。 以下是一个示例代码: import asyncio # 定义协程函数 async def coro1(): return "Hello" async def coro2(): return "World" # 创建事件循环 loop = asyncio.get_event_loop() # 并发运行多个协程 tasks = [coro1(), coro2()] results = loop.run_until_complete(asyncio.gather(*tasks)) # 打印结果 print(results) 在上述示例代码中,我们定义了两个协程函数coro1()和coro2(),分别返回"Hello"和"World"。然后,我们在事件循环中使用asyncio.gather()函数并发运行这两个协程函数,并将返回值存储在results列表中。最后,我们打印出结果。 通过这种方式,我们可以方便地实现Python中的协程并发运行,并获取其返回值。这种方式非常适合处理并发任务,提高程序的效率。 ### 回答3: Python中的协程是一种轻量级的并发编程方式,可以在单线程中同时运行多个任务,提高程序的执行效率。在Python中有许多支持协程的库,如asyncio和gevent。下面是使用asyncio库实现并发运行协程并获取返回值的步骤: 1. 首先,导入asyncio库和相应的模块。创建一个asyncio的事件循环对象。 2. 接下来,定义一个协程函数,使用async关键字声明。在协程函数中编写你的业务逻辑代码,并使用await关键字等待异步任务完成。 3. 在主函数中,使用asyncio的gather()函数来运行多个协程任务。将协程任务作为gather()函数的参数传入,以创建一个任务列表。 4. 调用事件循环对象的run_until_complete()方法,将任务列表作为参数传入,执行任务。 5. 在run_until_complete()方法返回后,可以通过调用协程对象的result()方法来获取协程任务的返回值。result()方法是一个属性,如果任务成功完成,它将返回任务的结果。 下面是一个简单的示例代码: ```python import asyncio async def task(name): # 模拟耗时任务 await asyncio.sleep(1) return f"Hello, {name}" async def main(): tasks = [task("Alice"), task("Bob") ] results = await asyncio.gather(*tasks) for result in results: print(result) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) ``` 在上面的示例中,定义了一个名为task的协程函数,它会模拟一个耗时任务并返回一个字符串。main()函数中创建了两个任务,并使用asyncio.gather()将它们组合成一个任务列表。最后通过循环打印出每个任务的返回值。 运行以上代码,你将看到类似如下输出: ``` Hello, Alice Hello, Bob ``` 这是两个协程任务返回的结果。这里使用asyncio库实现协程的并发运行并获取返回值,不同的库和框架可能具有类似或不同的方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的猫96

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值