线程和协程

https://blog.csdn.net/tjcwt2011/article/details/80885410

https://blog.csdn.net/m0_38011218/article/details/81938261

https://www.runoob.com/w3cnote/python-dynamic-var.html

https://blog.csdn.net/k_koris/article/details/82903363

https://blog.csdn.net/w18306890492/article/details/82872571

https://www.liaoxuefeng.com/wiki/897692888725344/966405998508320

https://www.cnblogs.com/houdj/p/12100159.html

import datetime

def run(arg):
    # for i in range(3):
    #     time.sleep(1)
    msg = "I'm ok %d" % arg
    print(msg, threading.currentThread())

l=[]
names=locals()
for j in range(5):
	t = threading.Thread(target=run, args=(j,))
	dateWithTime = str(datetime.datetime.now())
	names['t'+dateWithTime] = t
	l.append(names['t'+dateWithTime])


print('lllllllllllllll', l)
for i in l:
	i.start()
import threading
import time

def listen_music(name):
    while True:
        time.sleep(1)
        print(name,"正在播放音乐")


def download_music(name):
    while True:
        time.sleep(2)
        print(name,"正在下载音乐")
    p2 = threading.Thread(target=download_music,args=("网易云音乐",))


if __name__ == '__main__':
    p1 = threading.Thread(target=listen_music,args=("网易云音乐",))
    p2 = threading.Thread(target=download_music,args=("网易云音乐",))
    p1.start()
    p2.start()

协程相关:

https://www.cnblogs.com/cheyunhua/p/11017057.html

CPU密集型也叫计算密集型,指的是系统的硬盘,内存性能相对CPU要好很多,特点是要进行大量的计算,消耗CPU资源,比如计算圆周率,对视频进行高清解码等等,全靠CPU的运算能力,这种计算密集型的任务虽然也可以用多任务来完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就会越低,所以,要更高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数.计算密集型任务由于主要消耗CPU资源,所以,代码运行效率至关重要,python这样的脚本语言运行效率很低,所以,对于计算密集型任务,最好是用C语言来编写

IO密集型指的是系统的CPU相对于硬盘,内存要好很多,涉及到网络,磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成,因为IO的速度远远低于CPU和内存的速度,对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度,常见的大部分任务都是IO密集型任务,比如web应用. 因此,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差.

IO操作一般是指网络请求,文件读取等

python通过yield提供了对协程的基本支持,但是不完全,而第三方的gevent为python提供了比较完善的协程支持。gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其它的let,等到IO操作完成,再在适当的时候切换回来继续执行,由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO.所以在进行IO密集型程序时(如爬虫),使用gevent可以提高效率,有效利用网络延迟的时间去执行其他任务.

注意:使用gevent,可以获得极高的并发性能,但gevent只能在Unix/Linux下运行,在windows下不保证可以正常安装和运行.

学习异步IO模型之前,需要了解协程

协程又称为微线程, 纤程, Coroutine

子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用C,C执行完毕返回,B 执行完毕返回,最后A执行完毕

所以子程序调用是通过栈实现的,一个线程就是执行一个子程序

子程序调用总是一个入口一次返回,调用顺序是明确的,而协程的调用和子程序不同

协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行

注意:在一个子程序中中断,去执行其它子程序,不是函数调用,有点类似CPU的中断,比如子程序A. B

import time

def task_1():
    while True:
        print("--This is task 1!--before")
        yield
        print("--This is task 1!--after")
        time.sleep(0.5)

def task_2():
    while True:
        print("--This is task 2!--before")
        yield
        print("--This is task 2!--after")
        time.sleep(0.5)
        
if __name__ == "__main__":
    t1 = task_1()  # 生成器对象
    t2 = task_2()
    
    # print(t1, t2)
    while True:
        next(t1)  # 1、唤醒生成器t1,执行到yield后,保存上下文,挂起任务;下次再次唤醒之后,从yield继续往下执行
        print("\nThe main thread!\n")  # 2、继续往下执行
        next(t2)  # 3、唤醒生成器t2,....

协程的特点在于是一个线程执行,那和多线程比较,协程有何优势:

  1. 最大的优势在于协程有极高的执行效率,因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显
  2. 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多
  3. 协程是一个线程执行,要想利用多核CPU,最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,而获得极高的性能

python对协程的支持是通过generator来实现的,在generator中,我们不但可以通过for循环来迭代,还可以不断调用next()函数获取由yield语句返回的下一个值

但是python的yield不但可以返回一个值,它还可以接收调用者发出的参数

def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        # print b
        a, b = b, a + b
        n = n + 1

for i in fab(5):
	print('iiiiiiiiiii', i)
    
print(isgeneratorfunction(fab))
iiiiiiiiiii 1
iiiiiiiiiii 1
iiiiiiiiiii 2
iiiiiiiiiii 3
iiiiiiiiiii 5

yield的作用就是将一个函数变成generator,带有yield的函数不再是一个普通函数,python解释器会将其视为一个generator,调用fab(5)不会执行fab函数,而是返回一个iterable对象,在for循环执行时,每次循环都会执行fab函数内部的代码,执行到yield b时,fab函数就会返回一个迭代值,下次迭代时,代码从yield b的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前完全一样,于是函数继续执行,知道再次遇到yield

注意:fab和fab(5)是不一样的,fab是一个generator function,而fab(5)是调用fab返回的一个generator,好比类的定义和类的实例的区别

也可以手动调用fab(5)的next()方法,因为fab(5)是一个generator对象,该对象具有next()方法,这样我们就可以清楚地看到fab的执行流程。

每次调用fab函数都会生成一个新的generator实例,各实例互不影响:

f1=fab(3)
f2.fab(5)
print(f1.next())
print(f2.next())

如果直接对文件对象调用read()方法,会导致不可预测的内存占用,好的方法是利用固定长度的缓冲区来不断读取文件内容,通过yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

def read_file(fpath):
	BLOCK_SIZE = 1024
	with open(fpath, 'rb') as f:
		while True:
			block = f.read(BLOCK_SIZE)
			if block:
				yield block
			else:
				return
ff=read_file('./deve.txt')
print(ff.__next__())

print(ff.__next__())

print(ff.__next__())

print(ff.__next__())

理解yield:

  1. 通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,它的缺陷是所有的数据都在内存中,如果由海量数据的话将会非常耗内存

  2. 生成器是可以迭代的,但只可以读取它一次,因为用的时候才生成,比如mygenerator = (x*x for x in range(3)), 注意这里用到了(),它就不是数组,而上面的例子是[]

  3. send(msg)和next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数

  4. send(msg)与next()区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值,换句话说,就是send可以强行修改上一个yield表达式值,比如函数中有一个yield赋值 a=yield 5,第一次迭代到这里会返回5,a还没有赋值,第二次迭代时,使用send(10), 那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10

  5. 第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield,可以任务next()等同于send(None)

以下是通过生成器实现生产者-消费者模型,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高

def consumer():
    print('--4、开始执行生成器代码--')
    response = None
    while True:
        print('--5、yield,中断,保存上下文--')
        n = yield response  # 4、yield,中断,保存上下文
        print('--8、获取上下文,继续往下执行--')
        if not n:
            return 
        print("[Consumer]: consuming {} ..".format(n))
        response = "OK"

def produce(c):
    print("--3、启动生成器,开始执行生成器consumer--")
    c.send(None)  # 3、启动生成器,开始执行生成器consumer
    print("--6、继续往下执行--")
    n = 0
    while n < 5:
        n += 1
        print("[Producer]: producing {} ..".format(n))
        print("--7、第{}次唤醒生成器,从yield位置继续往下执行!--".format(n+1))
        r = c.send(n)  #第二次唤醒生成器
        print("--9、从第8步往下--")
        print("[Producer]: consumer return {} ..".format(r))
        
    c.close()

if __name__ == "__main__":
    c = consumer()  # 1、定义生成器,consumer并不执行
    produce(c)  # 2、运行produce函数

python2.x协程

类库: yield gevent

Gevent是第三方库,通过greenlet实现,其基本思想是:当一个greenlet遇到IO操作时,就自动切换到其他的greenlet,等到IO操作完成,再适当的时候切换回来继续执行,gevent自动切换协程,保证总有greenlet在运行,而不是等待IO

import gevent
import requests
from gevent import monkey

monkey.patch_all()

def get_body(i):
	print(f'start-{i}')
	requests.get('http://www.baidu.com').content.decode()
	print(f'end-{i}')


# tasks = [gevent.spawn(get_body, i) for i in range(3)]
tasks = [gevent.spawn(get_body, 1), gevent.spawn(get_body, 2), gevent.spawn(get_body, 4)]
gevent.joinall(tasks)

python3.x协程

ayncio

import asyncio

@asyncio.coroutine
def get_body(i):
	print(f'start-{i}')
	yield from asyncio.sleep(1)
	print(f'end-{i}')

loop = asyncio.get_event_loop()
tasks = [get_body(i) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
start-1
start-3
start-0
start-2
start-4
end-1
end-0
end-4
end-3
end-2
  • 它的作用是和Gevent一样,遇到IO操作的时候,自动切换上下文
  • 不同的是,它在tasks的操作
  • tasks先把这5个参数不同的函数全部加载进来,在执行任务,在这个过程中,任务执行时无序的
  • @asyncio.coroutine把一个generator标记为coroutine类型,然后把这个coroutine扔到eventloop中执行
  • yield from语法让我们方便地调用另一个generator,由于asyncio.sleep()也是一个coroutine,县城不会等待,直接中断执行下一个消息循环,当asyncio.sleep()返回时,线程可以从yield from 拿到返回值(此处是None) 然后接着执行下一行语句

async/await

为了更好地表示异步IO,python3.5开始引入了新语法,async和await,让coroutine语法更简洁,用async代替@asyncio.coroutine替换成async, await替换yield from.

import asyncio
async def get_body(i):
	print(f'start{i}')
	await asyncio.sleep(1)
	print(f'end{i}')

loop = asyncio.get_event_loop()
tasks = [get_body(i) for i in range(4)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
import time
import requests
import aiohttp
import asyncio


targets = [
    "https://lightless.me/archives/python-coroutine-from-start-to-boom.html",
    "https://github.com/aio-libs",
    "https://www.python.org/dev/peps/pep-0380/",
    "https://www.baidu.com/",
    "https://www.zhihu.com/",
]

final_results = {}


async def get_content(url):
	async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=64,verify_ssl=False)) as session:
		async with session.get(url) as resp:
			content = await resp.read()
			return len(content)


async def spider(url):
	length = await get_content(url)
	final_results[url]=length
	return True

def main():
	loop = asyncio.get_event_loop()
	cor =[spider(url) for url in targets]
	start_time = time.time() 
	result = loop.run_until_complete(asyncio.gather(*cor))
	print("Use time: {:.2f}s".format(time.time()-start_time))
	show_results(final_results)
	print("loop:result:",result)

def show_results(results):
    for url, length in results.items():
        print("Length: {:^7d} URL: {}".format(length, url))


if __name__ =='__main__':
	main()
Use time: 1.41s
Length:   227   URL: https://www.baidu.com/
Length:  52905  URL: https://www.zhihu.com/
Length:  44435  URL: https://lightless.me/archives/python-coroutine-from-start-to-boom.html
Length:  60201  URL: https://www.python.org/dev/peps/pep-0380/
Length: 313224  URL: https://github.com/aio-libs
loop:result: [True, True, True, True, True]
[Finished in 1.9s]

在main()函数中,我们先获取一个可用的事件循环,紧接着将生成好的协程任务添加到这个循环中,并且等待执行完成,在每个spider()中,执行到await的时候,会交出控制权,并且切到其它的协程继续运行,等到get_content()执行完成返回后,那么恢复spider()协程的执行,get_content()函数中只是通过async with调用aiohttp库的最基本方法获取页面内容,并且返回了长度,仅此而已.

为什么要用aiohttp,可以换成requests吗?答案是不能,在协程中,一切操作都是需要避免阻塞,禁止所有的阻塞型调用,因为所有的协程都是运行在同一个线程中的,requests库是阻塞型的调用,当在等待IO时,并不能将控制权转交给其他协程,甚至还会将当前线程阻塞,其他的协程也无法运行,如果在异步编程的时候需要用到一些其他的异步组件,可以在https:?/github.com/aio-libs/这里找找看,也许就会有需要的异步库.

关于asyncio的异步编程资料目前来说还不算很多,官方文档应该算是相当不错的参考文献了,其中非常推荐的两部分是:Develop with asyncioTasks and coroutines,各位同学有兴趣的话可以自行阅读。asyncio这个异步框架中包含了非常多的内容,甚至还有TCP Server/Client的相关内容,如果想要掌握asyncio这个异步编程框架,还需要多加练习。顺带一提,asyncio非常容易与其他的框架整合,例如tornado已经有实现了asyncio.AbstractEventLoop的接口的类AsyncIOMainLoop,还有人将asyncio集成到 QT 的事件循环中了,可以说是非常的灵活了。

aiohttp: https://www.cnblogs.com/well-666/p/12854588.html

from gevent import monkey
import gevent
import random
import time

def task_1(name):
    for i in range(5):
        print(name, i)
        time.sleep(1)  # 协程遇到耗时操作后会自动切换其他协程运行
        
def task_2(name):
    for i in range(3):
        print(name, i)
        time.sleep(1)
        
if __name__ == "__main__":
    monkey.patch_all()  # 给所有的耗时操作打上补丁

    gevent.joinall([  # 等到协程运行完毕
        gevent.spawn(task_1, "task_1"),  # 创建协程
        gevent.spawn(task_2, "task_2")
    ])
    print("the main thread!")
task_1 0
task_2 0
task_1 1
task_2 1
task_1 2
task_2 2
task_1 3
task_1 4
the main thread!
[Finished in 5.5s]

异步协程

python中使用协程最常用的库就是asyncio,

  1. event_loop事件循环:相当于一个无限循环,我们可以把一些函数注册在这个事件循环上,当满足条件时,就会调用对应的处理方法
  2. coroutine协程:协程对象,只一个使用async关键字定义的函数,他的调用不会立即执行函数,而是返回一个协程对象,协程对象需要注册在事件循环中,由事件循环调用
  3. task任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程的进一步封装,其中包含任务的各种状态
  4. future:代表将来执行或者没有执行的任务结果,它与task没有本质的区别
  5. async/await关键字:async定义一个协程,await用于挂起阻塞的异步调用接口

一. 定义一个协程 :通过async定义一个协程,协程是一个对象,不能直接运行,需要把协程加入到事件循环中,由loop在适当的时候调用协程,asyncio.get_event_loop()方法可以创建一个事件循环,然后由run_until_complete(协程对象)将协程注册到事件循环中并启动事件循环

run_until_complete根据传递的参数的不同,返回的结果也有所不同

  • run_until_complete()传递的是一个协程对象或者task对象,则返回他们finished的返回结果(前提是他们得有return的结果,否则返回None)
  • run_until_complete(asyncio.wait(多个协程对象或任务)),函数会返回一个元组包括(done, pending),通过访问done里面的task对象,获取返回值
  • run_until_complete(asyncio.gather(多个协程对象或任务)),函数会返回一个列表,列表里面包括各个任务的返回结果,按顺序排列

python3.7以前版本调用异步函数的步骤:

  • 调用asyncio.get_event_loop()函数获取事件循环loop对象
  • 通过不同的策略调用loop.run_forever()方法或者loop.run_until_complete()方法执行异步函数

python3.7以后的版本使用asyncio.run即可,此函数总是会创建一个新的事件循环并在结束时关闭之.它应当被用作asyncio程序的主入口点,理想情况下应当只被调用一次

import asyncio
 
async def work(x):  # 通过async关键字定义一个协程
    for _ in range(3):
        print('Work {} is running ..'.format(x))

coroutine_1 = work(1)  # 协程是一个对象,不能直接运行

# 方式一:
# loop = asyncio.get_event_loop()  # 创建一个事件循环
# result = loop.run_until_complete(coroutine_1)  # 将协程对象加入到事件循环中,并执行
# print(result)  # 协程对象并没有返回结果,打印None
# 方式二:
asyncio.run(coroutine_1)  #创建一个新的事件循环,并以coroutine_1为程序的主入口,执行完毕后关闭事件循环
# print(result)

二. 创建一个task协程对象不能直接运行,在注册到事件循环的时候,其实是run_until_complete方法将协程包装成一个task对象,所谓的task对象就是Future类的子类,它保存了协程运行后的状态,用于未来获取协程的结果

创建task后,task在加入事件循环之前是pending状态

import asyncio

async def work(x):  # 通过async关键字定义一个协程
    for _ in range(3):
        print('Work {} is running ..'.format(x))

coroutine_1 = work(1)  # 协程是一个对象,不能直接运行
 
loop = asyncio.get_event_loop()
task = loop.create_task(coroutine_1)
# task = asyncio.ensure_future(coroutine_1)  # 这样也能创建一个task
print(task)
loop.run_until_complete(task)  # run_until_complete接受的参数是一个future对象,当传人一个协程时,其内部自动封装成task
print(task)
<Task pending coro=<work() running at E:\notes_daily\THREAD.py:327>>
Work 1 is running ..
Work 1 is running ..
Work 1 is running ..
<Task finished coro=<work() done, defined at E:\notes_daily\THREAD.py:327> result=None>
[Finished in 0.2s]

三. 绑定回调:在task执行完毕的时候可以获取执行的结果,回调的最后一个参数是future对象,通过这个对象可以获取协程的返回值,如果回调函数需要多个参数,可以通过偏函数导入

从下例可以看出,coroutine执行结束时候会调用回调函数,并通过future获取协程返回return的结果,我们创建的task和回调里面的future对象,实际上是同一个对象


并发运行任务:

asyncio.gather(* aws,loop = None,return_exceptions = False ) 同时在aws 序列中运行等待对象
asyncio.shield(aw, * , loop=None) 保护一个 可等待对象 防止其被 取消。如果 aw 是一个协程,它将自动作为任务加入日程
asyncio.wait_for(aw, timeout, * , loop=None) 等待 aw 可等待对象 完成,指定 timeout 秒数后超时。
asyncio.wait(aws,* , loop = None,timeout = None,return_when = ALL_COMPLETED )
asyncio.as_completed(aws, * , loop=None, timeout=None) 并发地运行 aws 集合中的 可等待对象。返回一个 Future 对象的迭代器。返回的每个 Future 对象代表来自剩余可等待对象集合的最早结果。
# 并发运行任务的案例

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")  # python3.7新语法,了解一波
        await asyncio.sleep(1)  # await后面是 可等待对象
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    
    return f"Task {name}: Finished!"

async def main():
    # Schedule three calls *concurrently*:
    results = await asyncio.gather(  # results包含所有任务的返回结果,是一个列表,按执行顺序返回结果
        factorial("A", 2),  # 协程,会自动调度为任务
        factorial("B", 3),
        factorial("C", 4),
    )
    print(results)

asyncio.run(main())  # 协程的嵌套,后面有详解
# 超时的案例

import asyncio

async def eternity():
    # Sleep for one hour
    # await asyncio.sleep(0.5)
    await asyncio.sleep(3600)
    print('yay!')

async def main():
    # Wait for at most 1 second
    try:
        await asyncio.wait_for(eternity(), timeout=1.0)  # 等待 可等待对象 完成,超过timeout秒后,抛出asyncio.TimeoutError异常
    except asyncio.TimeoutError:
        print('timeout!')

asyncio.run(main())
 简单等待的案例

import asyncio

async def foo():
    return 42

task = asyncio.create_task(foo())
# 注意:1、这里传递的要是一个任务组,而不能是单个task,如果只有一个任务,可以这样传递:[task](task,){task}
#       2、直接传递协程对象的方式已弃用 即:done, pending = await asyncio.wait([foo()])
done, pending = await asyncio.wait((task, ))

if task in done:
    print(f"The task's result is {task.result()}")
# 使用事件循环和asyncio.wait、asyncio.gather实现并发运行任务

import asyncio, time

async def work_1(x):
    print(f"Starting {x}")
    time.sleep(1)
    print(f"Starting {x}")
    for _ in range(3):
        print(f"Work {x} is running ..")
        await asyncio.sleep(2)  # 耗时操作,此时挂起该协程,执行其他协程
    return f"Work {x} is finished"

async def work_2(x):
    print(f"Starting {x}")
    for _ in range(3):
        await asyncio.sleep(1)  # 耗时操作,此时挂起该协程,执行其他协程
        print(f"Work {x} is running ..")
    return f"Work {x} is finished"

coroutine_1 = work_1(1)
coroutine_2 = work_2(2)

loop = asyncio.get_event_loop()  # 创建一个事件循环

# 方式一,asyncio.wait(tasks)接受一个task列表  执行的顺序与列表里的任务顺序有关
tasks = [
    asyncio.ensure_future(coroutine_1),
    asyncio.ensure_future(coroutine_2),
]
# 注册到事件循环中,并执行
dones, pendings = loop.run_until_complete(asyncio.wait(tasks))  # loop.run_until_complete(asyncio.wait(tasks))的作用相当于:await asyncio.wait(tasks)
for task in dones:
    print(task.result())

# 方式二,使用asyncio.gather(*tasks),接受一堆tasks,tasks也可以是一个列表,使用*解包
# task_1 = asyncio.ensure_future(coroutine_1)
# task_2 = asyncio.ensure_future(coroutine_2)
# task_result_list = loop.run_until_complete(asyncio.gather(task_1, task_2))  # 返回一个列表,里面包含所有task的result()的结果
# print(task_result_list)

协程嵌套:使用async可以定义协程,协程用于耗时的IO操作,我们也可以封装更多的IO操作过程,在一个协程中await另外一个协程,实现协程的嵌套

import asyncio, time

async def work(x):
    for _ in range(3):
        print("Work {} is running ..".format(x))
        await asyncio.sleep(1)  # 当执行某个协程时,在任务阻塞的时候用await挂起
    return "Work {} is finished!".format(x)

async def main_work():
    coroutine_1 = work(1)
    coroutine_2 = work(2)
    coroutine_3 = work(3)
    
    tasks = [
        asyncio.ensure_future(coroutine_1),
        asyncio.ensure_future(coroutine_2),
        asyncio.ensure_future(coroutine_3),
    ]
    
    dones, pendings = await asyncio.wait(tasks)
    
    for task in dones:
        print("The task's result is : {}".format(task.result()))
        
if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main_work())
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值