协程和线程


迭代器

是一种可以遍历的对象,可以作用于next()函数

生成器

Python中一边循环一边计算的机制称为生成器 比如生成器函数(yield )

装饰器

可以使用装饰器,装饰该函数,在不改变原函数的情况下,增加功能

进程,线程和协程


进程在操作系统上运行的一个程序, 操作系统以进程为单位分配资源, 每个进程都有自己的地址空间、内存、数据栈以及其他用于跟踪进程执行的辅助数据, 操作系统管理所有进程的执行, 并为这些进程合理的分配资源

线程是操作系统能够进行运算调度的最小单位, 被包含在进程之中, 是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程, 每条线程并行执行不同的任务

  • 进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位
  • 线程能减少并发执行的时间和空间开销
  • 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈

协程的作用, 是在执行函数A时, 可以随时中断, 去执行函数B, 然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句), 这一整个过程看似像多线程, 然而协程只有一个线程执行。

并发与并行

并发通常指有多个任务需要同时进行, 并行则是同一时刻有多个任务执行。用多线程、多进程、协程来说, 协程实现并发, 多线程与多进程实现并行

多线程多个任务在同一时间执行


生成器变为协程


通过yield关键字和.send方法, 用户可以随时中断一个函数执行转而执行另一个函数, 相当于手动从一个子程序的执行切换到了另一个子程序的执行。在这种子程序的切换过程中没有涉及到线程的切换, 我们将一个子程序和它被执行以及被挂起时的状态称之为一个协程

1
2
3
4
5
6
7
8
9
10
def gg():
print('a')
while True:
b = yield
print(b)
n = gg()
n.send(None) # 预激活协程,让函数gg运行到b = yield这一句
print('-')
n.send('b') # 给函数gg中的yield传值
n.throw(Exception,'value error') # 让调用方抛出异常, 在生成器中处理
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
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
print(new_num)
count += 1
total += new_num
average = total/count

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

# 调用方
def main():
calc_average = proxy_gen()
print(next(calc_average) ) # 预激下生成器
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0

if __name__ == '__main__':
main()

asyncio和future


future对象由asyncio创建

future表示终将发生的事情, 而确定某件事会发生的唯一方式就是执行的时间已经排定

future对象

future对象有四个状态:Pending、Running、Done、Cancelled。创建future的时候, task为pending, 事件循环调用执行的时候当然就是running, 调用完毕自然就是done, 如果需要停止事件循环, 就需要先把task取消, 可以使用asyncio.Task获取事件循环的task

asyncio协程如何实现并发

asyncio想要实现并发, 就需要多个协程来完成任务, 每当有任务阻塞的时候就await, 然后其他协程继续工作, 这需要创建多个协程的列表, 然后将这些协程注册到事件循环中。这里指的多个协程, 可以是多个协程函数, 也可以是一个协程函数的多个协程对象

处理并发

Python中处理并发与大多数语言一样, 但也有不一样的地方。常见的处理并发的方式如下:

  1. 使用进程池。即, 预先fork好一定数量的进程, 每来一个请求, 就从进程池中分配一个进程去处理该请求, 处理完成之后, 再将该进程放回进程池。这种方式的优点是可以充分利用多核, 但缺点同样明显, 就是比较浪费CPU, 因为多进程切换的时候由于要保存上下文, 需要更多的指令来保存更多的数据。而且进程比线程占用更多的内存。

  2. 使用线程池。与进程池工作原理类似, 但是每次是使用线程来处理请求。Python中使用线程池有一个比较大的缺点就是Python有GIL, 也就意味着, Python使用线程池无法充分利用多核。

  3. 使用协程。借助gevent这类工具, Python可以轻松的以同步的方式写出异步的代码, 从而可以轻松的处理并发问题, 同时配合使用进程池, 便可以充分利用多核, 并且借助event loop, 高效的处理I/O事件, 通常这都是Python中的最佳实践方案。


协程和线程
https://maocat.cc/2020/04/15/Python/协程/
发布于
2020年4月15日
许可协议