Python中的进程、线程与协程

0.概述

在Python中,常常涉及进程、线程与协程的概念。本文档对其做基本的记录与说明。

1.概念引入

我们都熟悉函数,也称为子程序程序子进程等。函数是打包为一个单元来执行特定任务的一系列指令。当一个复杂函数的逻辑被划分为几个独立的步骤,这些步骤本身就是函数时,这些函数被称为辅助函数或子程序

Python 中的subroutine (子例程),由负责协调使用这些子例程的 main 函数调用。子例程具有单个入口点。

子程序

协程是子例程的泛化。它们用于协作多任务处理,其中进程自愿定期或在空闲时放弃(放弃)控制权,以便同时运行多个应用程序。协程和子例程的区别是:

  • 子例程不同,协程有许多用于暂停和恢复执行的入口点。协程可以暂停其执行并将控制权转移到其他协程,并且可以从中断点重新开始执行。
  • 子例程不同,没有 main 函数来按特定顺序调用协程并协调结果。协程是协作的,这意味着它们链接在一起形成一个管道。一个协程可能会使用输入数据并将其发送给处理它的其他协程。最后,可能有一个协程来显示结果。

协程

2.协程与线程

现在你可能会想协程与线程有什么不同,两者似乎都在做同样的工作。对于线程,它是一个操作系统(或运行时环境)根据调度程序在线程之间切换,也就是说,线程是CPU调度和分派的基本单位。而在协程的情况下,由程序员和编程语言决定何时切换协程。协程通过程序员在设定点暂停和恢复来协同多任务处理。

2.1 Python 协程

在 Python 中,协程类似于生成器,但它几乎没有额外的方法,并且我们使用yield语句的方式略有变化。生成器生成数据用于迭代,而协程也可以使用数据。
在 Python 2.5 中,对 yield 语句进行了轻微的修改,现在 yield 也可以用作表达式。例如,在赋值语句的右侧:

line = (yield)

我们发送到协程的任何值都会被 (yield) 表达式捕获并返回。

可以通过 send()方法将值发送到协程 。例如,考虑这样一个协程,它打印出带有前缀“Dear”的名称。我们可以使用 send()方法将名称发送到协程。

# Python3 program for demonstrating 
# coroutine execution

def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    while True:
        name = (yield)
        if prefix in name:
            print(name)

# calling coroutine, nothing will happen
corou = print_name("Dear")

# This will start execution of coroutine and 
# Prints first line "Searching prefix..."
# and advance execution to the first yield expression
corou.__next__()

# sending inputs
corou.send("Atul")
corou.send("Dear Atul")
输出结果:
Searching prefix:Dear
Dear Atul

2.2 协程的执行

协程的执行类似于生成器。当我们调用协程时,什么也没发生,它只在响应 next()和 sends()方法时运行。这在上面的例子中可以清楚地看到,因为只有在调用 __next__() 方法后,我们的协程才开始执行。在此调用之后,程序执行将推进到第一个 yield 表达式,现在执行过程暂停并等待将值发送到 corou 对象。当第一个值发送给它时,它会检查前缀和打印名称(如果存在前缀)。打印名称后,它会经历循环,直到再次遇到 name = (yield) 表达式。
 

2.3 关闭协程

协程可能会无限期运行,以关闭协程close()方法。当协程关闭时,它会生成 GeneratorExit 异常,该异常可以通过常规方式捕获。关闭协程后,如果我们尝试发送值,将引发 StopIteration 异常。下面是一个简单的例子:

# Python3 program for demonstrating
# closing a coroutine

def print_name(prefix):
    print("Searching prefix:{}".format(prefix))
    try : 
        while True:
                name = (yield)
                if prefix in name:
                    print(name)
    except GeneratorExit:
            print("Closing coroutine!!")

corou = print_name("Dear")
corou.__next__()
corou.send("Atul")
corou.send("Dear Atul")
corou.close()

输出:

Searching prefix:Dear
Dear Atul
Closing coroutine!!

2.4 创建管道的链接协程

协程可用于设置管道。我们可以将协程链接在一起,并使用 send()方法将数据推送到管道中。管道需要:

  • 初始源(生产者)派生整个管道。生产者通常不是一个协程,它只是一个简单的方法。
  • 接收器,它是管道的端点。接收器可能会收集所有数据并显示这些数据。

pipeline

以下是链接的简单示例 

# Python3 program for demonstrating
# coroutine chaining

def producer(sentence, next_coroutine):
    '''
    Producer which just split strings and
    feed it to pattern_filter coroutine
    '''
    tokens = sentence.split(" ")
    for token in tokens:
        next_coroutine.send(token)
    next_coroutine.close()

def pattern_filter(pattern="ing", next_coroutine=None):
    '''
    Search for pattern in received token 
    and if pattern got matched, send it to
    print_token() coroutine for printing
    '''
    print("Searching for {}".format(pattern))
    try:
        while True:
            token = (yield)
            if pattern in token:
                next_coroutine.send(token)
    except GeneratorExit:
        print("Done with filtering!!")
        next_coroutine.close()

def print_token():
    '''
    Act as a sink, simply print the
    received tokens
    '''
    print("I'm sink, i'll print tokens")
    try:
        while True:
            token = (yield)
            print(token)
    except GeneratorExit:
        print("Done with printing!")

pt = print_token()
pt.__next__()
pf = pattern_filter(next_coroutine = pt)
pf.__next__()

sentence = "Bob is running behind a fast moving car"
producer(sentence, pf)
输出:
I'm sink, i'll print tokens
Searching for ing
running
moving
Done with filtering!!
Done with printing!

3.小结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源最大,效率很低
  • 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

scott198512

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

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

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

打赏作者

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

抵扣说明:

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

余额充值