11.python3标准库--使用进程、线程和协程提供并发性

时间:2019-08-22
本文章向大家介绍11.python3标准库--使用进程、线程和协程提供并发性,主要包括11.python3标准库--使用进程、线程和协程提供并发性使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'''
python提供了一些复杂的工具用于管理使用进程和线程的并发操作。
通过应用这些计数,使用这些模块并发地运行作业的各个部分,即便是一些相当简单的程序也可以更快的运行
 
subprocess提供了一个API可以创建子进程并与之通信
这对于运行生产或消费文本的程序尤其有好处,因为这个API支持通过新进程的标准输入和输出通道来回传递数据。
 
signal模块提供了unix信号机制,可以向其他进程发送事件。信号会被异步处理,通常信号到来时要中断程序正在做的工作。
信号作为一个粗粒度的消息系统会很有用,不过其他进程内通信技术更可靠,而且可以传递更复杂的消息。
 
threading模块包块一个面向对象的高层API,用于处理python的并发性。Thread对象在同一个进程中并发地运行,并共享内存。
对于I/O受限而不是CPU受限的任务来说,使用线程是这些任务实现缩放的一种简单方法。
 
miltiprocessing模块是threading的镜像,只是它提供了一个Process而非一个Thread类。
每个Process都是真正的系统进程(而无共享内存),multiprocessing提供了一些特性可以共享数据并传递消息,使得很多情况下从线程转化为进程很简单,只需要修改几个import语句。
 
asyncio使用一个基于类的协议系统或协程为并发和异步I/O管理提供了一个框架。
asyncio替换了原来的asyncore和asynchat模块,这些模块仍可用,但已经废弃。
 
concurrent.futures提供了基于线程和进程的执行器实现,用来管理资源池以运行并发的任务
'''

  

(一)subprocess:创建附加进程

1
2
3
4
5
6
7
8
9
10
'''
subprocess模块提供了3个API来处理进程。
run函数时python3.5中新增的,作为一个高层API,其用于运行进程并收集它的输出。函数call、check_call、check_output是从python2沿袭来的原高层API
这些函数仍受到支持,并在现有的程序中广泛使用。
类Popen是一个用于建立其他API的底层API,对更复杂的进程交互很有用。Popen的构造函数利用参数建立新进程,使父进程可以通过管道与之通信。
它可以替换一些其他模块和函数,并能提供所替换的这些模块和函数的全部功能,甚至还更多。
在所有情况下,这个API的用法是一致的,很多开销的额外步骤(如关闭额外的文件描述符,以及确保管道关闭)都已经内置在这个API中,不需要由应用代码单独设置。
 
subprocess模块是为了替换os.system、os.spawnv,os和peopen2模块中不同形式的popen函数,以及commands模块。
'''

  

1.运行外部命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import subprocess
 
 
'''
subprocess的命令蛮多的,这里我只想介绍一个。
用好这一个,包治百病
'''
# 第一个是要执行的shell命令,下面几个固定不变
result = subprocess.Popen("xxxx",
                          stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT,
                          shell=True)
# 然后所有的结果都在result.stdout里面
# 读取出来进行解码即可,记住是操作系统的默认编码
print(result.stdout.read().decode("gbk"))
'''
2019/03/22  20:05    <DIR>          .
2019/03/22  20:05    <DIR>          ..
2019/02/27  15:54    <DIR>          1.文本
2019/03/25  17:13    <DIR>          10.使用进程线程协程提供并发性
2019/03/05  14:15    <DIR>          2.数据结构
2019/03/08  15:33    <DIR>          3.算法
2019/03/19  10:20    <DIR>          4.日期和时间
2019/03/19  17:16    <DIR>          5.数学运算
2019/03/21  13:54    <DIR>          6.文件系统
2019/03/21  16:53    <DIR>          7.数据持久存储与交换
2019/03/22  19:13    <DIR>          8.数据压缩与归档
2019/03/22  19:54    <DIR>          9.加密
2019/03/22  19:07               809 test.py
               1 个文件            809 字节
              12 个目录 59,315,806,208 可用字节
'''
 
# 如果命令不存在
'''
'xxxx' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
'''

  

(二)signal:异步处理事件

1
2
3
4
5
6
7
'''
信号是一个操作系统特性,它提供了一个途径可以通知程序这里发生了一个事件,并且异步处理这个事件。
信号可以由系统本身生成,也可以从一个进程发送到另一个进程。
由于信号会中断程序的正常控制流,如果在操作过程中间接收到信号,有些操作(特别是I/O操作)可能会产生错误
 
信号由整数表示,在操作系统C首部中定义。python在signal模块中提供了适合不同平台的多种信号。
'''

  

1.接收信号

pass

(三)threading:进程中管理并发操作

1
2
3
'''
threading模块提供了管理多个线程执行的API,允许程序哎同一个进程空间并发地执行多个操作
'''

  

1.Thread对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import threading
 
 
'''
要使用Thread,最简单的方法就是用一个目标函数实例化一个Thread对象,并调用start方法让它工作
'''
 
 
def worker(name, age):
    print(f"my name is {name}, age is {age}")
 
 
for in range(5):
    # 创建Thread对象
    # 接收两个参数,target,使我们要执行的函数。args是函数里面需要的参数,要以元组的方式传进去,即便只有一个参数,也要以元组的方式传进去,(xxx, )
    = threading.Thread(target=worker, args=(f"name{i}", f"age{i}"))
    # 调用start方法,启动
    t.start()
'''
my name is name0, age is age0
my name is name1, age is age1
my name is name2, age is age2
my name is name3, age is age3
my name is name4, age is age4
'''

  

2.确定当前线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import threading
 
 
'''
使用参数来标识或者命名线程很麻烦,也没有必要。
每个Thread实例都带有一个默认地名字,该默认值可以在创建线程时改变。
如果服务器进程中有多个服务器线程处理不同的操作,那么在这样的服务器进程中,对线程命名就很有用。
'''
 
 
def worker1():
    print(threading.current_thread(), "----", threading.current_thread().getName())
 
 
# 除了刚才说的那两个参数之外,还可以有第三个参数,就是给线程指定的名字
t1 = threading.Thread(target=worker1, name="work1")
# 这里我们不指定名字,由于函数不需要参数,所以args也不需要传
t2 = threading.Thread(target=worker1)
t1.start()
t2.start()
'''
<Thread(work1, started 6844)> ---- work1
<Thread(Thread-1, started 6888)> ---- Thread-1
'''

  

大多数的程序并不适用print来进行调试。logging模块支持将线程名嵌入到各个日志消息中(使用的格式化代码%(threadName)s)。

通过把线程名包含在日志消息中,就能跟踪这些消息的来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import threading
import logging
import time
 
 
def worker1():
    logging.debug("start")
    time.sleep(0.5)
    logging.debug("end")
 
 
logging.basicConfig(
    level=logging.DEBUG,
    format="[%(levelname)s] (%(threadName)s -10s) %(message)s"
)
 
t1 = threading.Thread(target=worker1, name="work1")
t2 = threading.Thread(target=worker1)
t1.start()
t2.start()
'''
[DEBUG] (work1 -10s) start
[DEBUG] (Thread-1 -10s) start
[DEBUG] (work1 -10s) end
[DEBUG] (Thread-1 -10s) end
'''
# logging是线程安全的,所以来自不同的线程的消息在输出中会有所区分

  

3.守护与非守护线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import threading
import time
 
 
'''
当我们开启一个单独的线程之后,主线程是会继续往下走的。如果子线程没有处理完,那么主线程会卡在最后等待着,直到所有线程处理完毕。
如果我想某些子线程要在主线程结束的时候也跟着结束,不会出现自己还没结束而让主线程等着的情况,该怎么办呢?那么可以将这些子线程设置为守护线程即可。
'''
 
 
def daemon(t):
    print(f"线程{t1}正在睡眠····")
    time.sleep(t)
    print(f"线程{t1}睡眠结束····")
 
 
# 加上daemon=True,表示将这个线程设置为守护线程
t1 = threading.Thread(target=daemon, args=(3, ), name="t1", daemon=True)
'''
当然设置守护线程还可以这么设置
t1.setDaemon(True)
同理设置线程名字也是一样
t1.setName("t1")
'''
# 当我们这样执行的时候,主线程开启一个name="t1"的线程之后,就不管了,然后往下执行
# 然而下面没有代码了,所以就结束了。当子线程不是守护线程,那么主线程在最后会等待住,当子线程执行完毕之后才结束
# 但是当我们设置为守护线程时,那么意义就不一样了,守护线程是守护主线程的,主线程结束,那么守护线程也会自杀。
t1.start()
'''
输出结果:
线程<Thread(t1, started daemon 8012)>正在睡眠····
'''

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import threading
import time
 
 
def daemon(t):
    print(f"线程{threading.current_thread().getName()}正在睡眠····")
    time.sleep(t)
    print(f"线程{threading.current_thread().getName()}睡眠结束····")
 
 
t1 = threading.Thread(target=daemon, args=(3, ), name="t1", daemon=True)
t2 = threading.Thread(target=daemon, args=(4, ), name="t2")
t1.start()
t2.start()
'''
 
线程t1正在睡眠····
线程t2正在睡眠····
线程t1睡眠结束····
线程t2睡眠结束····
'''
 
# 首先t1是守护线程,但是t2不是。主线程虽然不需要等待t1,但是需要等待t2.
# t2沉睡4秒,t1沉睡3秒,主要在等待t2的4s中,守护线程已经执行结束,所以会打印4条
# 如果守护线程t1 sleep 4秒,非守护线程t2 sleep 3秒的话,那么非守护线程先结束,主线程也就不需要再等待了。
# 因此打印的结果会变成
'''
线程t1正在睡眠····
线程t2正在睡眠····
线程t2睡眠结束····
'''

  

不过还有一个潜在的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
import time
 
 
def daemon(t):
    print(f"线程{threading.current_thread().getName()}正在睡眠····")
    time.sleep(t)
 
 
start_time = time.time()
= threading.Thread(target=daemon, args=(3, ))
t.start()
end_time = time.time()
print("总耗时:", end_time-start_time)
'''
线程Thread-1正在睡眠····总耗时:
0.0010001659393310547
'''
# 我们把函数daemon里面time.sleep(t)想象成耗时t秒的操作,但是之前说了,主线程只是开启了一个线程就不管了
# 因此最后的时间肯定是不准的,因为任务还没执行完毕就往下走了
# 如果我们想当任务执行完成之后,再让主线程往下走呢?

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import threading
import time
 
 
def daemon(t):
    print(f"线程{threading.current_thread().getName()}正在睡眠····")
    time.sleep(t)
 
 
start_time = time.time()
= threading.Thread(target=daemon, args=(3, ))
t.start()
# 可以使用join方法,这个方法会使得主线程卡在这个地方,直到使用join方法的Tread对象执行完毕之后才会往下走
# 当然join里面还可以加上参数,传入2,表示最多等待2s。2s之内,什么时候执行结束什么时候往下走,如果2s内没执行完毕,那么不管了,直接往下走
t.join()
end_time = time.time()
print("总耗时:", end_time-start_time)
'''
线程Thread-1正在睡眠····
总耗时: 3.001171827316284
'''

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import threading
import time
 
 
def daemon(t):
    print(f"线程{threading.current_thread().getName()}正在睡眠····")
    time.sleep(t)
 
 
start_time = time.time()
= threading.Thread(target=daemon, args=(3, ))
t.start()
print(f"线程是否活着:{t.is_alive()}")  # 线程是否活着:True
# 可以使用join方法,这个方法会使得主线程卡在这个地方,直到使用join方法的Tread对象执行完毕之后才会往下走
# 当然join里面还可以加上参数,传入2,表示最多等待2s。2s之内,什么时候执行结束什么时候往下走,如果2s内没执行完毕,那么不管了,直接往下走
t.join()
end_time = time.time()
print(f"线程是否活着:{t.is_alive()}")  # 线程是否活着:False
print("总耗时:", end_time-start_time)
'''
线程Thread-1正在睡眠····
总耗时: 3.001171827316284
'''
# 调用Thead对象下的is_alive可以判断该线程是否还活着,当join之后,主线程都往下走了,所以肯定死了

  

4.枚举所有线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import threading
import time
import random
 
 
'''
没有必要为所有守护线程维护一个显式句柄来确保它们在退出主进程之前已经完成。
enumerate会返回Thread实例的一个列表。
'''
 
 
def worker(t):
    print(f"我要sleep{t}秒")
    time.sleep(t)
 
 
for in range(3):
    = threading.Thread(target=worker, args=(random.randint(15), ))
    t.start()
 
# 我要让所有子线程都执行完毕之后,才继续往下走
'''
一种方法是先创建一个列表, list_t = [], 然后
for i in range(3):
    t = threading.Thread(target=worker, args=(random.randint(1, 5), ))
    list_t.append(t)
    t.start()
 
for i in list_t:
    i.join()
这是一种方法,我在启动的时候把所有Thread对象都放到一个列表里面,然后循环列表,把所有的Thread对象都join住,这样就完成当所有子线程都结束时,主线程才往下走的需求
'''
# 但是还有更好的方法
# threading.enumerate方法可以查看所有的线程
for in threading.enumerate():
    print(t.name)
'''
MainThread
Thread-1
Thread-2
Thread-3
'''
# 可以看到,这个方法能够拿到所有的线程
# 但是它把主线程也拿出来了
# 但是我们可以单独获取主线程
main_thread = threading.main_thread()
print(main_thread.name)  # MainThread
 
for in threading.enumerate():
    if is main_thread:
        continue
    t.join()
# 这样也能实现等待子线程的功能,而且更加的pythonic
print("我必须是最后一个打印的,因为所有的子线程全都join了,我必须等待它们都执行结束,我才能走到这一步")

  

5.派生线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import threading
 
 
'''
我们除了定义一个函数之外,也可以定义一个类
'''
 
 
# 必须要继承Thread方法
class MyThread(threading.Thread):
 
    def __init__(self, name, info):
        self.info = info
        # name是我们的线程名称,因此交给父类的init方法执行,但是info是我们自己的参数就不需要了
        super().__init__(name=name)
 
    # 然后重写父类的run方法,当我们调用start时,就会执行run方法
    def run(self):
        print(f"当前线程:{self.name}, info:{self.info}")
 
 
for in range(3):
    = MyThread(name=f"t{i}", info=f"这是线程t{i}")
    t.start()
'''
当前线程:t0, info:这是线程t0
当前线程:t1, info:这是线程t1
当前线程:t2, info:这是线程t2
'''

  

6.定时器线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import threading
import time
 
 
'''
有时出于某种原因需要派生Thread,Timer就是这样一个例子,Timer也包含在threading中。
Timer在一个延迟之后开始工作,而且可以在这个延迟期间内的任意时刻被取消
'''
 
 
def delayed():
    print(f"{threading.current_thread().name}:work running")
    time.sleep(4)
    print(f"{threading.current_thread().name}:work done")
 
 
# 2s之后执行delayed函数
t1 = threading.Timer(2, delayed)
# 3s之后执行delayed函数
t2 = threading.Timer(3, delayed)
t1.setName("t1")
t2.setName("t2")
t1.start()
t2.start()
# 主线程继续往下走
# 将t2取消,调用cancel方法
t2.cancel()
print("主线程走到头啦")
'''
主线程走到头啦
t1:work running
t1:work done
'''
# t2都还没来得及打印,就被取消了

  

7.线程之间传递信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import threading
import time
 
 
'''
尽管使用多线程的目的是并发地运行单独的操作,但有时也需要在两个或多个线程中同步操作。
事件对象是实现线程安全通信的一种简单方法。
Event管理一个内部标志,调用者可以用set和clear方法控制这个标志。其他线程可以使用wait暂停,直到这个标志被设置,可以有效地阻塞进程直至允许这些线程继续。
'''
 
 
def go1(event: threading.Event):
    print(f"{threading.current_thread().name}前进啦, 三秒之后遇见红灯要停下")
    time.sleep(3)
    print(f"{threading.current_thread().name}停下啦,其他人可以走了")
    event.set()
 
 
def go2(event: threading.Event):
    print(f"{threading.current_thread().name}遇到红灯啦,先不能走")
    event.wait()
    print(f"{threading.current_thread().name}前进啦")
 
 
= threading.Event()
t1 = threading.Thread(target=go1, args=(e, ))
t2 = threading.Thread(target=go2, args=(e, ))
t1.start()
t2.start()
'''
Thread-1前进啦, 三秒之后遇见红灯要停下
Thread-2遇到红灯啦,先不能走
Thread-1停下啦,其他人可以走了
Thread-2前进啦
'''
# event.wait方法会阻塞,直到调用set方法设置标志位
# 所以go1先执行,go2打印第一句话之后会卡住,当go1执行完毕之后,调用set方法之后go2可以继续执行.
 
# 注意:一旦set之后再调用wait是没有用的,所以go1set之后,go2的wait就失去效果了
# 如果想要使wait生效,那么必须clear,将标志位清空,然后才可以使用wait
# 也可以调用is_set方法来查看是否设置了标志位

  

8.控制资源访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import threading
import time
 
 
'''
除了同步线程操作,还有一点很重要,要能够控制对共享资源的访问,从而避免破坏或丢失数据。
python的内置数据结构(列表、字典等)是线程安全的,这是python使用原子字节码来管理这些数据结构的一个副作用(更新过程中不会释放保护python内部数据结构的全局解释器锁)。
 
python中实现的其他数据结构或更简单的类型(如整数和浮点数)则没有这个保护。
因此要保证同时安全地访问一个对象,可以使用一个Lock对象
'''
num = 0
 
 
def add():
    global num
    for in range(1000000):
        num += 1
 
 
def sub():
    global num
    for in range(1000000):
        num -= 1
 
 
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=sub)
t1.start()
t2.start()
t1.join()
t2.join()
 
print(num)
# 执行三次
'''
428530
73280
-435653
'''
# 为什么会出现这种结果,每次还都不一样。
# 因为python的多线程不是真正意义上的多线程,无法在同时利用多个核,而是时间片轮转。
# 有可能在num += 1的时候,比如此时num=100,本来应该得101,但是还没执行完,线程让出,执行了num-=1,num变成99了。
# 再执行num+=1操作得到的还是100,这就导致了最后的结果肯定是不对的

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import threading
 
 
'''
怎么改变这一局面呢?可以使用加锁的方式
'''
num = 0
lock = threading.Lock()
 
 
def add():
    global num
    for in range(1000000):
        # 加上锁
        lock.acquire()
        num += 1
        # 执行完毕之后释放
        lock.release()
 
 
def sub():
    global num
    for in range(1000000):
        lock.acquire()
        num -= 1
        lock.release()
 
 
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=sub)
t1.start()
t2.start()
t1.join()
t2.join()
 
print(num)  # 0
'''
这样的话,不管执行多少次,结果都是0
因为我们在对num进行相加和相减的时候,加上了锁,这个过程是不会被打断的。只有运算完毕将锁释放之后才会切换
'''

  

9.同步线程

pass

(四)multiprocessing:像线程一样管理进程

pass

(五)asyncio:异步I/O、事件循环和并发工具

1
2
3
4
5
'''
asyncio模块提供了使用协程构建并发应用的工具。
threading模块通过应用线程实现并发,multiprocessing使用系统进程实现并发,asyncio使用一种单线程、单进程模式实现并发,应用的各个部分会彼此合作,在最优的时刻显式的切换任务。
大多数情况下,会在程序阻塞等待读写数据时发生这种上下文切换,不过asyncio也支持调度代码在将来的某个特定时间运行,从而支持一个协程等待另一个协程完成,以处理系统信号和识别其他一些事件(这些事件可能导致应用改变其工作内容)
'''

  

1.异步并发概念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'''
使用其他并发模型的大多数程序都采用线性方式编写,而且依赖于语言运行时系统或操作系统的底层线程或进程管理来适当地改变上下文。
基于asyncio的应用要求应用代码显式地处理上下文切换,要正确地使用相关技术,这取决于是否能正确理解一些相关联的概念。
 
asyncio提供的框架以一个事件循环(event loop)为中心,这是一个首类对象,负责高效地处理I/O事件、系统事件、和应用上下文切换。
目前已经提供了多个循环实现来高效地利用操作系统的功能。尽管通常会自动选择一个合理的默认实现,但也完全可以在应用中选择某个特定的事件循环实现。
在很多情况下都很有用,例如:在Windows下,一些循环类增加了对外部进程的支持,这可能会以牺牲一些网络I/O效率为代价
 
与事件循环交互的应用要显式地注册将运行的代码,让事件循环在资源可用时向应用代码发出必要的调用。
例如:一个网络服务器打开套接字,然后注册为当这些套接字上出现输入事件时服务器要得到的通知。
事件循环在建立一个新的进入链接或者在数据可读取时都会提醒服务器代码。当前上下文中没有更多工作可做时,应用代码要再次短时间地交出控制权。
例如:如果一个套接字没有更多的数据可以接收,那么服务器会把控制权交给事件循环
 
所以,就是把代码注册到事件循环中,不断地循环这些事件,可以处理了那么就去处理,如果卡住了,那么把控制权交给事件循环,继续执行其他可执行的任务。
像传统的twisted、gevent、以tornado,都是采用了事件循环的方式,这种模式只适用于高I/O,低CPU的场景,一旦出现了耗时的复杂运算,那么所有任务都会被卡住。
 
将控制权交给事件循环的机制依赖于协程(coroutine),这是一些特殊的函数,可以将控制返回给调用者而不丢失其状态。
协程与生成器非常类似,实际上,在python3.5版本之前还未对协程提供原生支持时,可以用生成器来实现协程。
asyncio还为协议(protocol)和传输(transport)提供了一个基于类的抽象层,可以使用回调编写代码而不是直接编写协程。
在基于类的模型和协程模型时,可以通过重新进入事件循环显式地改变上下文,以取代python多线程实现中隐式的上线文改变
 
future是一个数据结构,表示还未完成的工作结果。事件循环可以监视Future对象是否完成,从而允许应用的一部分等待另一部分完成一些工作。
处理future,asyncio还包括其他并发原语,如锁和信号量。
 
Task是Future的一个子类,它知道如何包装和管理一个协程的执行。
任务所需要的资源可用时,事件循环会调度任务运行,并生成一个结果,从而可以由其他协程消费。
'''

  

2.利用协程合作完成多任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import asyncio
 
 
'''
协程是一个专门设计用来实现并发操作的语言构造。
调用协程函数时会创建一个协程对象,然后调用者使用协程的send方法运行这个函数的代码。协程可以使用await关键字(并提供另一个协程)暂停执行。
暂停时,这个协程的状态会保留,使得下一次被唤醒时可以从暂停的地方恢复执行
'''
 
 
# 使用async def可以直接定义一个协程
async def coroutine():
    print("in coroutine")
 
 
# 创建事件循环
loop = asyncio.get_event_loop()
try:
    print("start coroutine")
    # 协程是无法直接运行的,必须要扔到事件循环里,让事件循环驱动运行
    coro = coroutine()
    print("entering event loop")
    # 必须扔到事件循环里,这个方法的含义从名字也能看出来,直到协程运行完成
    loop.run_until_complete(coro)
finally:
    print("closing event loop")
    # 关闭事件循环
    loop.close()
'''
start coroutine
entering event loop
in coroutine
closing event loop
'''
 
# 第一步是得到事件循环的引用。
# 可以使用默认地循环类型,也可以实例化一个特定的循环类。
# run_until_complete方法启动协程,协程退出时这个方法会停止循环

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import asyncio
 
 
'''
我们也可以获取协程的返回值
'''
 
 
async def coroutine():
    print("in coroutine")
    return "result"
 
 
loop = asyncio.get_event_loop()
try:
    coro = coroutine()
    result = loop.run_until_complete(coro)
    print(result)
finally:
    loop.close()
'''
in coroutine
result
'''
# 在这里,run_until_complete还会返回它等待的协程的结果

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
 
 
'''
一个协程还可以驱动另一个协程并等待结果,从而可以更容易地将一个任务分解为可重用的部分。
'''
 
 
async def worker():
    print("worker....")
    # 使用await方法会驱动协程consumer执行,并得到其返回值
    res = await consumer()
    print(res)
 
 
async def consumer():
    return "i am consumer"
 
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(worker())
finally:
    loop.close()
'''
worker....
i am consumer
'''
# 在这里,使用await关键字,而不是向循环中增加新的协程。因为控制流已经在循环管理的一个协程中,所以没必要告诉循环管理这些协程。
# 另外,协程可以并发运行,但前提是多个协程。这个协程卡住了,可以切换到另一个协程。但是就卡住的协程本身来说,该卡多长时间还是多长时间,不可能说跳过卡住的部分执行下面的代码。

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import asyncio
 
 
'''
协程函数时asyncio设计中的关键部分。
它们提供了一个语言构造,可以停止程序某一部分的执行,保留这个调用的状态,并在以后重新进入这个状态,这些动作都是并发框架很重要的功能。
 
python3.5中引入了一些新的语言特性,可以使用async def以原生方式定义这些协程,以及使用await交出控制,asyncio的例子应用了这些新特性。
但是早期版本,可以使用asyncio.coroutine装饰器将函数装饰成一个协程并使用yield from来达到同样的效果。
'''
 
 
@asyncio.coroutine
def worker():
    print("worker....")
    res = yield from consumer()
    print(res)
 
 
@asyncio.coroutine
def consumer():
    return "i am consumer"
 
 
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(worker())
finally:
    loop.close()
'''
worker....
i am consumer
'''
 
# 尽管使用生成器可以达到同样的效果,但还是推荐使用async和await
'''
生成器既可以做生成器,又可以包装为协程,那么它到底是协程还是生成器呢?这会使得代码出现混乱
生成器应该做自己
基于async的原生协程比使用yield装饰器的协程要快,大概快10-20%
'''

  

3.调度常规函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import asyncio
from functools import partial
 
 
'''
除了管理协程和I/P回调,asyncio事件循环还可以根据循环中保存的一个定时器值来调度常规函数调用。
'''
# 如果回调的时间不重要,那么可以使用call_soon调度下一次循环迭代的调用
 
 
def callback(*args, **kwargs):
    print("callback:", args, kwargs)
 
 
async def main(loop):
    print("register callback")
    # 接收一个回调函数,和参数
    loop.call_soon(callback, "mashiro"16)
    print("********")
    # 如果是关键字参数是不能直接传的,需要使用偏函数转换一下
    wrapped = partial(callback, kwargs={"name""satori""age"16})
    loop.call_soon(wrapped, "mahsiro"16)
    print("—————————")
 
    await asyncio.sleep(0.6)
 
 
event_loop = asyncio.get_event_loop()
try:
    print("entering event loop")
    event_loop.run_until_complete(main(event_loop))
finally:
    print("closing event loop")
    event_loop.close()
 
 
'''
entering event loop
register callback
********
—————————
callback: ('mashiro', 16) {}
callback: ('mahsiro', 16) {'kwargs': {'name': 'satori', 'age': 16}}
closing event loop
'''
# 可以看到,call_soon调用callback是最后执行的

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import asyncio
from functools import partial
 
 
'''
要将回调推迟到将来的某个时间调用,可以使用call_later。这个方法的第一个参数是延迟时间(单位为秒),第二个参数是回调。
'''
 
 
def callback(cb, n):
    print(f"{cb} {n}")
 
 
async def main(loop):
    print("register callback")
    loop.call_later(0.2, callback, "call_later""0.2s")
    loop.call_later(0.1, callback, "call_later""0.1s")
    loop.call_soon(callback, "call_soon"3)
    print("-----------")
    await asyncio.sleep(0.6)
 
 
event_loop = asyncio.get_event_loop()
try:
    print("entering event loop")
    event_loop.run_until_complete(main(event_loop))
finally:
    print("closing event loop")
    event_loop.close()
 
 
'''
entering event loop
register callback
-----------
call_soon 3
call_later 0.1s
call_later 0.2s
closing event loop
'''
# 可以看到,call_soon调用callback的延迟是最小的,当我们遇见了asyncio.sleep的时候,自动切换,瞬间执行。

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import asyncio
import time
 
 
'''
除了call_soon瞬间执行,和call_later延迟执行之外,还有一个call_at在指定之间内执行。
实现这个目的的循环依赖于一个单调时钟,而不是墙上的时钟时间,以确保now时间绝对不会逆转。
要为一个调度回调选择时间,必须使用循环的time方法从这个时钟的内部开始
'''
 
 
def callback(cb, loop):
    print(f"callback {cb} invoked at {loop.time()}")
 
 
async def main(loop):
    now = loop.time()
    print("clock time:", time.time())
    print("loop time:", now)
    print("register callback")
    loop.call_at(now+0.2, callback, "call_at", loop)
    loop.call_at(now+0.1, callback, "call_at", loop)
    loop.call_soon(callback, "call_soon", loop)
    sum = 0
    for in range(99999999):
        sum += i
    print("sum ="sum)
    await asyncio.sleep(1)
 
 
event_loop = asyncio.get_event_loop()
try:
    print("entering event loop")
    event_loop.run_until_complete(main(event_loop))
finally:
    print("closing event loop")
    event_loop.close()
 
 
'''
entering event loop
clock time: 1553659076.2547848
loop time: 7238.493
register callback
sum = 4999999850000001
callback call_soon invoked at 7246.012
callback call_at invoked at 7246.012
callback call_at invoked at 7246.012
closing event loop
'''
# call_soon会立刻调用,但是如果下面还有代码的话会继续执行,当遇见asyncio.sleep的时候,自动切换执行,如果没有或者没有阻塞,那么会最后执行。
# call_at本来是在0.2s之后执行的,但是当中出现了复杂的运算,所以计算时间往后推迟了

  

4.异步的生成结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import asyncio
import time
 
 
'''
Future表示还未完成的工作的结果。事件循环可以通过监视一个Future对象的状态来指示它已经完成,从而允许应用的一部分等待另一部分完成一些工作。
'''
# Future的做法类似于协程,所以等待协程所用的技术同样可以用于等待Future。
 
 
def mark_done(future, result):
    print("setting result")
    future.set_result(result)
 
 
event_loop = asyncio.get_event_loop()
try:
    all_done = asyncio.Future()
    print("sceduling mark_done")
    event_loop.call_soon(mark_done, all_done, "the result")
    print("entering loop")
    event_loop.run_until_complete(all_done)
finally:
    print("close loop")
    event_loop.close()
 
print("future result:", all_done.result())
'''
sceduling mark_done
entering loop
setting result
close loop
future result: the result
'''
# 调用set_result时,Future的状态改为完成,Future实例会保留提供给方法的结果,以备后续获取
 
 
future = asyncio.Future()
# 设置只能设置一次
future.set_result("xxx")
# 但是取可以取多次
print(future.result())  # xxx
print(future.result())  # xxx
print(future.result())  # xxx
print(future.result())  # xxx
print(future.result())  # xxx

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import asyncio
import time
 
 
'''
Future还可以结合await关键字使用
'''
 
 
def mark_done(future, result):
    print("setting result")
    future.set_result(result)
 
 
async def main(loop):
    all_done = asyncio.Future()
    print("scheduling mark_done")
    loop.call_soon(mark_done, all_done, "the result")
    # 会等到all_done这个Future对象里面有值位置
    res = await all_done
    print("res =", res)
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()
 
'''
scheduling mark_done
setting result
res = the result
'''
# Future的结果由await返回,所以经常会让同样的代码处理一个常规的协程和一个Future实例

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import asyncio
import functools
 
 
'''
除了做法与协程类似,Future也可以调用回调,回调的顺序按照其注册的顺序调用
'''
 
 
def callback(future, n):
    print(f"future result: {future.result()} n:{n}")
 
 
async def register_callback(all_done):
    print("register callback on futures")
    all_done.add_done_callback(functools.partial(callback, n=1))
    all_done.add_done_callback(functools.partial(callback, n=2))
 
 
async def main(all_done):
    await register_callback(all_done)
    print("setting result of future")
    all_done.set_result("the result")
 
 
event_loop = asyncio.get_event_loop()
try:
    all_done = asyncio.Future()
    event_loop.run_until_complete(main(all_done))
finally:
    event_loop.close()
 
'''
register callback on futures
setting result of future
future result: the result n:1
future result: the result n:2
'''

  

5.并发地执行任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import asyncio
 
 
'''
任务是与事件循环交互的主要途径之一。
任务可以包装协程,并跟踪协程何时完成。
由于任务是Future的子类,所以其他协程可以等待任务,而且每个任务可以有一个结果,在它完成时可以获取这些结果
'''
 
 
# 启动一个任务,可以使用create_task函数创建一个Task实例。
# 只要循环还在运行而且协程没有返回,create_task得到的任务便会作为事件循环管理的并发操作的一部分运行
async def task_func():
    print("in task func")
    return "the result"
 
 
async def main(loop):
    print("creating task")
    task = loop.create_task(task_func())
    print(f"wait for {task}")
    return_value = await task
    print(f"task completed {task}")
    print(f"return value {return_value}")
     
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()
'''
creating task
wait for <Task pending coro=<task_func() running at 5.asyncio.py:13>>
in task func
task completed <Task finished coro=<task_func() done, defined at 5.asyncio.py:13> result='the result'>
return value the result
'''
 
# 一开始task是pending状态,然后执行结束变成了done
# 这个Task是Futrue的子类,await task得到的就是任务task的返回值。
# future.set_result的时候,就代表这个Future对象已经完成了,可以调用注册的回调函数了
# 那么Task对象也是一样,当这个协程已经return了,就代表这个协程完成了,那么return的值就类似于set_result设置的值
# Task在注册回调,调用相应的回调函数的时候,也可以通过task.result方法获取返回值。
# 那么同理和Future对象一样,使用await Task()也可以直接获取返回值

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import asyncio
 
 
'''
通过create_task可以创建对象,那么也可以在任务完成前取消操作
'''
 
 
async def task_func():
    print("in task func")
    return "the result"
 
 
async def main(loop):
    print("creating task")
    task = loop.create_task(task_func())
     
    print("canceling task")
    task.cancel()
    print(f"canceled task: {task}")
 
    try:
        await task
    except asyncio.CancelledError:
        print("caught error from canceled task")
    else:
        print(f"task result: {task.result()}")
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import asyncio
 
 
'''
ensure_future函数返回一个与协程执行绑定的Task。
这个Task实例再传递到其他代码,这个代码可以等待这个实例,而无须知道原来的协程是如何构造或调用的
'''
 
 
async def task_func():
    print("in task func")
    return "the result"
 
 
async def main(loop):
    print("creating task")
    task = asyncio.ensure_future(task_func())
 
    print(f"return value: {await task}")
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()
'''
creating task
in task func
return value: the result
'''

  

6.组合协程和控制结构

1.等待多个协程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import asyncio
 
'''
一系列协程之间的线性控制流用内置的await可以很容易地管理。
更复杂的结构可能允许一个协程等待多个其他协程并行完成,可以使用asyncio中的工具创建这些更复杂的结构
 
通常可以把一个操作划分为多个部分,然后分别执行,这会很有用。
例如:采用这种方法,可以高效的下载多个远程资源或者查询远程API。
有些情况下,执行顺序并不重要,而且可能有任意多个操作,这种情况下,可以使用wait函数暂停一个协程,直到其他后台操作完成
'''
 
 
async def phase(i):
    print(f"in phase {i}")
    await asyncio.sleep(0.1 * i)
    print(f"done with phase {i}")
    return f"phase {i} result"
 
 
async def main():
    print("start main")
    phases = [phase(i) for in range(3)]
    print("waiting for phases to complete")
    # 可以await 一个协程,但如果是多个协程呢?可以将其组合成一个列表,然后交给asyncio.wait函数,再对其进行await,就可以等所有的协程了
    # 会有两个返回值,一个是已完成的任务,一个是未完成的任务
    completed, pending = await asyncio.wait(phases)
    print(f"results: {[t.result() for t in completed]}")
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main())
finally:
    event_loop.close()
 
'''
start main
waiting for phases to complete
in phase 1
in phase 0
in phase 2
done with phase 0
done with phase 1
done with phase 2
results: ['phase 1 result', 'phase 0 result', 'phase 2 result']
'''
# 可以看到顺序貌似乱了,这是因为在内部,wait函数使用一个set来保存它创建的Task实例,这说明这些实例会按一种不可预知的顺序启动和完成。

  

2.从协程收集结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import asyncio
 
'''
如果后台阶段是明确的,而且这些阶段的结果很重要,那么gather方法可能对等待多个操作很有用
'''
 
 
async def phase(i):
    print(f"in phase {i}")
    await asyncio.sleep(0.1 * i)
    print(f"done with phase {i}")
    return f"phase {i} result"
 
 
async def main():
    print("start main")
    phases = [phase(i) for in range(3)]
    print("waiting for phases to complete")
    # 当使用gather的时候,内部直接传入多个任务即可
    # 所以我们还要将列表进行打散
    # 并且和wait不一样,返回值不再是任务,而是任务的返回值,而且是完成的任务的返回值。
    # 正因为是返回值,所以只有一个,并且顺序和我们添加任务的顺序是一致的
    # 不管后台是怎么执行了,返回值得顺序和我们添加任务的顺序保持一致
    completed = await asyncio.gather(*phases)
    print(f"results: {[t for t in completed]}")
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main())
finally:
    event_loop.close()
 
'''
start main
waiting for phases to complete
in phase 0
in phase 1
in phase 2
done with phase 0
done with phase 1
done with phase 2
results: ['phase 0 result', 'phase 1 result', 'phase 2 result']
'''

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
 
 
async def task1():
    return "task1"
 
 
async def task2():
    return "task2"
 
 
async def task3():
    return "task3"
 
 
tasks = [task1(), task2(), task3()]
 
event_loop = asyncio.get_event_loop()
try:
    completed, pending = event_loop.run_until_complete(asyncio.wait(tasks))
finally:
    event_loop.close()
for in completed:
    print(t.result())
'''
task2
task3
task1
'''

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
 
 
async def task1():
    return "task1"
 
 
async def task2():
    return "task2"
 
 
async def task3():
    return "task3"
 
 
tasks = [task1(), task2(), task3()]
 
event_loop = asyncio.get_event_loop()
try:
    completed = event_loop.run_until_complete(asyncio.gather(*tasks))
finally:
    event_loop.close()
for in completed:
    print(t)
'''
task1
task2
task3
'''

  

3.后台操作完成时进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import asyncio
 
 
'''
as_completed函数是一个生成器,会管理指定的一个协程列表,并生成它们的结果,每个协程结束运行时一次生成一个结果。
与wait类似,as_completed不能保证顺序,从名字也能看出来,哪个先完成哪个先返回
'''
 
 
async def task1():
    print("我是task1,我睡了3秒")
    await asyncio.sleep(3)
    print("我是task1,睡完了")
    return "task1"
 
 
async def task2():
    print("我是task2,我睡了1秒")
    await asyncio.sleep(1)
    print("我是task2,睡完了")
    return "task2"
 
 
async def task3():
    print("我是task3,我睡了2秒")
    await asyncio.sleep(2)
    print("我是task3,睡完了")
    return "task3"
 
 
async def main():
    print("start main")
    tasks = [task1(), task2(), task3()]
    for task in asyncio.as_completed(tasks):
        res = await task
        print(res)
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main())
finally:
    event_loop.close()
 
'''
start main
我是task3,我睡了2秒
我是task2,我睡了1秒
我是task1,我睡了3秒
我是task2,睡完了
task2
我是task3,睡完了
task3
我是task1,睡完了
task1
'''

  

7.同步原语

尽管asyncio应用通常作为单线程的进程运行,不过仍被构建为并发应用。

由于I/O以及其他外部事件的延迟和中断,每个协程或任务可能按照一种不可预知的顺序执行。

为了支持安全的并发执行,asyncio包含了threading和multiprocessing模块中一些底层原语的实现

1.锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import asyncio
 
 
'''
Lock可以用来保护对一个共享资源的访问,只有锁的持有者可以使用这个资源。
如果有多个请求要得到这个锁,那么其将会阻塞,以保证一次只有一个持有者
'''
 
 
def unlock(lock):
    print("回调释放锁,不然其他协程获取不到。")
    print("但我是1秒后被调用,锁又在只能通过调用我才能释放,所以很遗憾,其他协程要想执行,至少要1秒后了")
    lock.release()
 
 
async def coro1(lock):
    print("coro1在等待锁")
    # 使用async with语句很方便,是一个上下文。相当于帮我们自动实现了开始的lock.acquire和结尾lock.release
    async with lock:
        print("coro1获得了锁")
        print("coro1释放了锁")
 
 
async def coro2(lock):
    print("coro2在等待锁")
    async with lock:
        print("coro2获得了锁")
        print("coro2释放了锁")
 
 
async def main(loop):
    # 创建共享锁
    lock = asyncio.Lock()
 
    print("在开始协程之前创建一把锁")
    await lock.acquire()
    print("锁是否被获取:", lock.locked())
 
    # 执行回调将锁释放,不然协程无法获取锁
    loop.call_later(1, unlock, lock)
 
    # 运行想要使用锁的协程
    print("等待所有协程")
    await asyncio.wait([coro1(lock), coro2(lock)])
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()
'''
在开始协程之前创建一把锁
锁是否被获取: True
等待所有协程
coro2在等待锁
coro1在等待锁
回调释放锁,不然其他协程获取不到。
但我是1秒后被调用,锁又在只能通过调用我才能释放,所以很遗憾,其他协程要想执行,至少要1秒后了
coro2获得了锁
coro2释放了锁
coro1获得了锁
coro1释放了锁
'''

  

2.事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import asyncio
 
 
'''
asyncio.Event基于threading.Event。它允许多个消费者等待某个事件发生,而不必寻找一个特定值与关联
首先Event对象可以使用set,wait,clear
set:设置标志位
wait:等待,在没有set的情况下,会阻塞。如果set之后,不会阻塞。
clear:清空标志位
'''
 
 
def set_event(event):
    print("设置标志位,因为协程会卡住,只有设置了标志位才会往下走")
    print("但我是一秒后才被调用,所以协程想往下走起码也要等到1秒后了")
    event.set()
 
 
async def coro1(event):
    print("coro1在这里卡住了,快设置标志位啊")
    await event.wait()
    print(f"coro1飞起来了,不信你看现在标志位,是否设置标志位:{event.is_set()}")
 
 
async def coro2(event):
    print("coro1在这里卡住了,快设置标志位啊")
    await event.wait()
    print(f"coro2飞起来了,不信你看现在标志位,是否设置标志位:{event.is_set()}")
 
 
async def main(loop):
    # 创建共享事件
    event = asyncio.Event()
    # 现在设置标志位了吗?
    print("是否设置标志位:", event.is_set())
 
    # 执行回调将标志位设置,不然协程卡住了
    loop.call_later(1, set_event, event)
 
    # 运行卡住的的协程
    print("等待所有协程")
    await asyncio.wait([coro1(event), coro2(event)])
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(event_loop))
finally:
    event_loop.close()
 
'''
是否设置标志位: False
等待所有协程
coro1在这里卡住了,快设置标志位啊
coro1在这里卡住了,快设置标志位啊
设置标志位,因为协程会卡住,只有设置了标志位才会往下走
但我是一秒后才被调用,所以协程想往下走起码也要等到1秒后了
coro2飞起来了,不信你看现在标志位,是否设置标志位:True
coro1飞起来了,不信你看现在标志位,是否设置标志位:True
'''
# asyncio里面的事件和threading里面的事件的API是一致的。

  

3.队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import asyncio
 
 
'''
asyncio.Queue为协程提供了一个先进先出的数据结构,这与线程的queue.Queue或者进程里面的Queue很类似
'''
 
 
async def consumer(q: asyncio.Queue, n):
    print(f"消费者{n}号 开始")
    while True:
        item = await q.get()
        print(f"消费者{n}号: 消费元素{item}")
        # 由于我们要开启多个消费者,为了让其停下来,我们添加None作为停下来的信号
        if item is None:
            # task_done是什么意思?队列有一个属性,叫做unfinished_tasks
            # 每当我们往队列里面put一个元素的时候,这个值就会加1,
            # 并且队列还有一个join方法,表示阻塞,什么时候不阻塞呢?当unfinished_tasks为0的时候。
            # 因此我们每put一个元素的时候,unfinished_tasks都会加上1,那么当我get一个元素的时候,unfinished_tasks是不是也应该要减去1啊,但是我们想多了
            # get方法不会自动帮我们做这件事,需要手动调用task_done方法实现
            q.task_done()
            break
        else:
            await asyncio.sleep(3)
            q.task_done()
 
 
async def producer(q: asyncio.Queue, consumer_num):
    print(f"生产者 开始")
    for in range(20):
        await q.put(i)
        print(f"生产者: 生产元素{i},并放在了队列里")
    # 为了让消费者停下来,我就把None添加进去吧
    # 开启几个消费者,就添加几个None
    for in range(consumer_num):
        await q.put(None)
 
    # 等待所有消费者执行完毕
    # 只要unfinished_tasks不为0,那么q.join就会卡住,知道消费者全部消费完为止
    await q.join()
    print("生产者生产的东西全被消费者消费了")
 
 
async def main(consumer_num):
    = asyncio.Queue()
    consumers = [consumer(q, i) for in range(consumer_num)]
    await asyncio.wait(consumers + [producer(q, consumer_num)])
 
 
event_loop = asyncio.get_event_loop()
try:
    event_loop.run_until_complete(main(3))
finally:
    event_loop.close()
 
'''
生产者 开始
生产者: 生产元素0,并放在了队列里
生产者: 生产元素1,并放在了队列里
生产者: 生产元素2,并放在了队列里
生产者: 生产元素3,并放在了队列里
生产者: 生产元素4,并放在了队列里
生产者: 生产元素5,并放在了队列里
生产者: 生产元素6,并放在了队列里
生产者: 生产元素7,并放在了队列里
生产者: 生产元素8,并放在了队列里
生产者: 生产元素9,并放在了队列里
生产者: 生产元素10,并放在了队列里
生产者: 生产元素11,并放在了队列里
生产者: 生产元素12,并放在了队列里
生产者: 生产元素13,并放在了队列里
生产者: 生产元素14,并放在了队列里
生产者: 生产元素15,并放在了队列里
生产者: 生产元素16,并放在了队列里
生产者: 生产元素17,并放在了队列里
生产者: 生产元素18,并放在了队列里
生产者: 生产元素19,并放在了队列里
消费者1号 开始
消费者1号: 消费元素0
消费者0号 开始
消费者0号: 消费元素1
消费者2号 开始
消费者2号: 消费元素2
消费者1号: 消费元素3
消费者0号: 消费元素4
消费者2号: 消费元素5
消费者1号: 消费元素6
消费者0号: 消费元素7
消费者2号: 消费元素8
消费者1号: 消费元素9
消费者0号: 消费元素10
消费者2号: 消费元素11
消费者1号: 消费元素12
消费者0号: 消费元素13
消费者2号: 消费元素14
消费者1号: 消费元素15
消费者0号: 消费元素16
消费者2号: 消费元素17
消费者1号: 消费元素18
消费者0号: 消费元素19
消费者2号: 消费元素None
消费者1号: 消费元素None
消费者0号: 消费元素None
生产者生产的东西全被消费者消费了
'''

  

(六)concurrent.futures:管理并发任务池

1
2
3
4
5
6
7
8
9
10
'''
concurrent.futures模块提供了使用工作线程或进程池运行任务的接口。
线程池和进程池的API是一致的,所以应用只需要做最小的修改就可以在线程和进程之间进行切换
 
这个模块提供了两种类型的类与这些池交互。执行器(executor)用来管理工作线程或进程池,future用来管理计算的结果。
要使用一个工作线程或进程池,应用要创建适当的执行器类的一个实例,然后向它提交任务来运行。
 
每个任务启动时,会返回一个Future实例。需要任务的结果时,应用可以使用Future阻塞,直到得到结果。
目前已经提供了不同的API,可以很方便地等待任务完成,所以不需要直接管理Future对象。
'''

  

1.利用基本线程池使用map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from concurrent.futures import ThreadPoolExecutor
import threading
import time
 
 
'''
ThreadPoolExecutor管理一组工作线程,当这些线程可用于完成更多工作时,可以向他们传入任务。
'''
 
 
def task(n):
    print(f"{threading.current_thread().name}开始睡觉了")
    time.sleep(n)
    print(f"{threading.current_thread().name}睡了{n}秒")
    return n
 
 
exe = ThreadPoolExecutor()
print("main: start")
results = exe.map(task, [53214])
print(results)
print(list(results))
'''
main: start
ThreadPoolExecutor-0_0开始睡觉了
ThreadPoolExecutor-0_1开始睡觉了
ThreadPoolExecutor-0_2开始睡觉了
ThreadPoolExecutor-0_3开始睡觉了
ThreadPoolExecutor-0_4开始睡觉了
<generator object Executor.map.<locals>.result_iterator at 0x0000000009F022A0>
ThreadPoolExecutor-0_3睡了1秒
ThreadPoolExecutor-0_2睡了2秒
ThreadPoolExecutor-0_1睡了3秒
ThreadPoolExecutor-0_4睡了4秒
ThreadPoolExecutor-0_0睡了5秒
[5, 3, 2, 1, 4]
'''
# 可以看到map相当于开启了一个线程,然后打印results,results是所有任务的返回值组合而成的序列,这里是迭代器。
# 显然里面一开始是没有值的,因此转化list会卡住,正如future.result()一样,任务没有结束我就获取不到值,那么就会卡住
# 然后谁先睡完,谁先打印,所以是1,2,3,4,5
# 最后打印返回值比较特殊,可以看到这是按照我们添加的顺序打印的。
# 可以用一种不恰当的方式来理解
'''
results在当前这个示例中,就是五个future.result()组成的迭代器,我们就用列表的展示
这5个future就是我们map的顺序来的,刚才说了每进行一次map,等于开启了一个线程。
[future.result(), future.result(), future.result(), future.result(), future.result()]
首先睡了1秒的执行结束,那么结果就是这个。因为返回值为1的任务是我们第四个map添加的
[future.result(), future.result(), future.result(), 1, future.result()]
接下来睡了2秒的执行结束,那么结果就是这个。因为返回值为2的任务是我们第三个map添加的
[future.result(), future.result(), 2, 1, future.result()]
接下来睡了3秒的执行结束,那么结果就是这个。因为返回值为3的任务是我们第二个map添加的
[future.result(), 3, 2, 1, future.result()]
接下来睡了4秒的执行结束,那么结果就是这个。因为返回值为4的任务是我们第五个map添加的
[future.result(), 3, 2, 1, 4]
最后睡了5秒的执行结束,那么结果就是这个。因为返回值为5的任务是我们第一个map添加的
[5, 3, 2, 1, 4]
 
 
返回值只有当所有任务全部完成之后才可以获取,因此一开始只是相当于按照添加的顺序占了个坑,然后谁好了就把对应的坑给填上
'''

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from concurrent.futures import ThreadPoolExecutor
 
 
'''
多个参数怎么办呢?
'''
 
 
def task(name, age):
    print(f"name is {name}, age is {age}")
 
 
exe = ThreadPoolExecutor()
 
exe.map(task, ["mashiro""satori""koishi"], [161615])
'''
name is mashiro, age is 16
name is satori, age is 16
name is koishi, age is 15
'''
 
'''
传参是把所有的name组合在一起,把所有的age组合在一起。
所以不是exe.map(task, ["mashiro", 16], ["satori", 16], ["koishi", 15])这种传参方式
'''
# 但我如果有这种格式的数据呢?
girls = [
    ["mashiro"16], ["satori"16], ["koishi"15]
]
print(list(zip(*girls)))  # [('mashiro', 'satori', 'koishi'), (16, 16, 15)]
exe.map(task, *zip(*girls))
'''
name is mashiro, age is 16
name is satori, age is 16
name is koishi, age is 15
'''

  

2.调度单个任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from concurrent.futures import ThreadPoolExecutor
import time
 
 
'''
除了使用map,还可以借助submit利用一个执行器调度单个任务。
然后可以使用返回的future实例等待这个任务的结果
'''
 
 
def task(n):
    print(f"我接下来要睡{n}秒")
    time.sleep(n)
    return f"我睡了{n}秒"
 
 
exe = ThreadPoolExecutor()
= exe.submit(task, 3)
print(f)
print(f.result())
'''
我接下来要睡3秒
<Future at 0x2f88c50 state=running>
我睡了3秒
'''

  

3.按任意顺序等待任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
 
 
'''
调用Future对象的result方法会阻塞,直到任务完成。
如果使用submit添加多个任务,f = [exe.submit(task, 3), exe.submit(task, 1), exe.submit(task, 2)]
for i in f:
    print(i.result())
最终得到的还是3, 1, 2
这和map是类似的,会依旧按照添加的顺序返回。
 
如果任务处理的结果的顺序不重要,可以使用as_completed函数,哪个任务先完成,哪个先返回。可以看到很多功能和asyncio模块类似。
'''
 
 
def task(n):
    print(f"我接下来要睡{n}秒")
    time.sleep(n)
    return f"我睡了{n}秒"
 
 
exe = ThreadPoolExecutor()
= [exe.submit(task, 3), exe.submit(task, 2), exe.submit(task, 1)]
for in f:
    print(i.result())
'''
我接下来要睡3秒
我接下来要睡2秒
我接下来要睡1秒
我睡了3秒
我睡了2秒
我睡了1秒
'''
= [exe.submit(task, 3), exe.submit(task, 2), exe.submit(task, 1)]
for in as_completed(f):
    print(i.result())
'''
我接下来要睡3秒
我接下来要睡2秒
我接下来要睡1秒
我睡了1秒
我睡了2秒
我睡了3秒
'''
 
# 如果加上as_completed,那么result的时候,那么先返回哪个先打印
# 没有as_completed,那么按照顺序,如果结束就能打印
# 但是如果是map的话,必须等到所有任务全部结束,然后一次性返回

  

4.Future回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from concurrent.futures import ThreadPoolExecutor, Future
import time
 
 
'''
要在任务完成时采取某个动作,不用显示的等待结果,可以使用add_done_callback指示Future完成时要调用一个新函数。
这个回调应当是有一个参数(Future实例)的callable函数。
 
这个asyncio是一样的,每一个任务都可以看成是是一个Task对象(Future对象的子类),当任务return的时候,会自动传入给callback。
或者手动创建一个Future对象,当set_result的时候,也会自动执行callback函数
'''
 
 
def task(n):
    print(f"我接下来要睡{n}秒")
    time.sleep(n)
    return f"我睡了{n}秒"
 
 
# 会自动传进去,因此至少有一个参数用来接收
# 如果还需要指定额外的参数,也可以写进去,当我们指定回调的时候就要指定偏函数了
def callback(future):
    print(future.result())
 
 
exe = ThreadPoolExecutor()
= exe.submit(task, 4)
f.add_done_callback(callback)
 
# 或者手动创建一个Future对象
future = Future()
future.add_done_callback(callback)
future.set_result("当我set_result之后,那么会自动调用callback")
'''
我接下来要睡4秒
当我set_result之后,那么会自动调用callback
我睡了4秒
'''

  

5.撤销任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from concurrent.futures import ThreadPoolExecutor, Future
import time
 
 
'''
如果一个Future已经提交但还没有提供,那么可以调用它的cancel方法将其撤销
'''
 
 
def task(n):
    print(f"我接下来要睡{n}秒")
    time.sleep(n)
    return f"我睡了{n}秒"
 
 
exe = ThreadPoolExecutor()
task1 = exe.submit(task, 3)
task2 = exe.submit(task, 1)
task3 = exe.submit(task, 2)
task3.cancel()
'''
我接下来要睡3秒
我接下来要睡1秒
我接下来要睡2秒
'''
 
# 可以看到task3并没有取消掉,因为任务已经添加到线程池里面执行了。
# 可是我们只有先submit之后才能cancel,一旦submit之后又不能cancel了,这样不就死循环了吗
# 所以我们可以限制池子的任务数
 
# 表示池子的最大任务数为2
exe = ThreadPoolExecutor(max_workers=2)
task1 = exe.submit(task, 3)
task2 = exe.submit(task, 1)
task3 = exe.submit(task, 2)
# 尽管我添加了三个任务,但是最大数量为2,所以task3还没有执行,只是在池子里面排着队呢
# 这个时候就可以取消了
task3.cancel()
'''
我接下来要睡3秒
我接下来要睡1秒
'''
# 可以看到此时的task3并没有执行

  

6.任务中的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from concurrent.futures import ThreadPoolExecutor, Future
import time
 
 
'''
如果一个任务产生一个未处理的异常,那么它会被保存到这个任务的Future,而且可以通过result方法或者exception方法得到
'''
 
 
def task():
    1/0
 
 
exe = ThreadPoolExecutor()
= exe.submit(task)
print(f.exception())  # division by zero
 
try:
    f.result()
except Exception as e:
    print(e)  # division by zero

  

7.上下文管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from concurrent.futures import ThreadPoolExecutor, wait
import time
 
 
'''
我们在执行多个任务的时候,我们希望等所有任务都执行完毕之后,再往下走,该怎么做呢?
'''
nums = []
 
 
def task(n):
    nums.append(n)
 
 
exe = ThreadPoolExecutor()
f1 = exe.submit(task, 1)
f2 = exe.submit(task, 2)
f3 = exe.submit(task, 3)
f4 = exe.submit(task, 4)
 
# 如果没有这句话,nums会为空,因为在子线程还没有来得及执行,print(nums)就已经打印了
wait([f1, f2, f3, f4])
print(nums)  # [1, 2, 3, 4]
 
 
# 此外还可以有另一种写法,使用上下文的形式
with ThreadPoolExecutor() as exe:
    exe.submit(task, 1)
    exe.submit(task, 2)
    exe.submit(task, 3)
    exe.submit(task, 4)
print(nums)  # [1, 2, 3, 4, 1, 2, 3, 4]

  

原文地址:https://www.cnblogs.com/valorchang/p/11395651.html