对于协程(coroutine),你必须知道事情都在这里了(内附代码)

pexels-photo-373543.jpeg

什么是协程

协程(coroutine)的概念根据Donald Knuth的说法早在1958年就由Melvin Conway提出了,对应wikipedia的定义如下:

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.

这里子例程(subroutine)是一个概括性的术语,子例程可以是整个程序中的一个代码区块,当它被主程序调用的时候就会进入运行。例如函数就是子例程中的一种。

c = max(a,b);

从wikipedia定义可以看出协程相比子例程更加的灵活,允许执行过程中被挂起恢复,多个协程可以一起相互协作执行任务。从协程(co + routine)名字上来拆解为支持协作(cooperate)的例程。

协程与子例程的执行区别

WX20200321-234331@2x.png

图中左边是子例程的执行过程,右边是协程的执行过程,可以很明显的看出来执行过程中的区别。

  • 先说左边,子例程可以看成是某个函数,子例程的执行过程中通常是嵌套顺序执行的过程,子例程1和子例程2的关系(调用和被调用)不是完全平等的,子例程1能调用子例程2,但子例程2不能反过来调用子例程1。
  • 再说右边,协程1和协程2的关系是完全对等的,协程1执行过程中可以中断挂起执行另外一个协程2,反之也是可以的,直到最终两个协程都执行完以后再返回回到主程序中,即协程1和协程2相互协作完成了整个任务。
    接下来举一个协程实现生产者和消费者的例子:
var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

这里有一个队列queue,一个生产者produce,一个消费者consume,yield代表中断挂起当前协程,并恢复其他协程的操作。生产者生产物品以后加入到队列以后,中断挂起自身并恢复消费者,消费者从队列中消费完物品以后中断挂起自身并恢复生产者,不断来回切换直到达到最终条件(比如所有原料都生产成物品并全都被消费完成),程序终止。

进程、线程、协程的关系和比较

通常会提到进程是资源分配的最小单位,线程是CPU调度的最小单位, 一个进程里可以有多个线程,这里直接画了个图来说明三者关系。

WX20200321-234347@2x.png

  • 进程是资源分配的最小单位,会拥有独立的地址空间以及对应的内存空间,还有网络和文件资源等,不同进程之间资源都是独立的,可以通过进程间通信(管道、共享内存、信号量等方式)来进行交互。
  • 线程为CPU调度的基本单位,除了拥有运行中必不可少的信息(如程序计数器、一组寄存器和栈)以外,本身并不拥有系统资源,所有线程会共享进程的资源,比如会共享堆资源。
  • 协程可以认为是运行在线程上的代码块,协程提供的挂起操作会使协程暂停执行,而不会导致线程阻塞。一个线程内部可以创建几千个协程都没有任何问题。
  • 进程的切换和线程切换中都包含了对应上下文的切换,这块都涉及到了内核来完成,即一次用户态到内核态的切换和一次内核态到用户态的切换。因为进程上下文切换保存的信息更多,所以进程切换代价会比线程切换代价更大。
  • 协程是一个纯用户态的并发机制,同一时刻只会有一个协程在运行,其他协程挂起等待;不同协程之间的切换不涉及内核,只用在用户态切换即可,所以切换代价更小,更轻量级,适合IO密集型的场景。

coroutine的python实现

  1. Python最初的版本里是包含了yield/send关键字,通过yield/send可以方便的实现一个协程的例子,这里还是以为生产者和消费者为例,具体实现方式如下:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    def consumer():
        """consumer定义"""
        ret = ''
        while True:
            # 挂起consumer,恢复producer, ret会传回给producer
            item_id = yield ret
            if not item_id:
                return
            print('consume item_id:{}'.format(item_id))
            ret = 'success'
    
    def producer(consumer):
        """producer定义"""
        # send一个None可以看成是启动consumer
        # consumer这里包含了yield关键字相当于是一个generator
        consumer.send(None)
        item_id = 0
        # 生产的item的总数
        item_total_count = 3
        while item_id < item_total_count:
            item_id = item_id + 1
            print('produce item:{}'.format(item_id))
            # 挂起producer,恢复consumer, item_id会传回给consumer
            ret = c.send(item_id)
            print('consumer return:{}'.format(ret))
        consumer.close()
    
    if __name__ == "__main__":
        c = consumer()
        producer(c)
    

    结果:

    produce item:1
    consume item_id:1
    consumer return:success
    produce item:2
    consume item_id:2
    consumer return:success
    produce item:3
    consume item_id:3
    consumer return:success 
    
  2. python 3.5版本开始引入了async/await关键字给了我们另外一种实现的方法,还是以为生产者和消费者为例,具体实现方式如下:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import asyncio
    import random
    
    async def producer(queue, item_total_count):
        """producer 定义"""
        for item_id in range(0, item_total_count):
            # 生产一个新的item
            print('produce item_id:{}'.format(item_id))
            # 模拟IO操作, 挂起producer,恢复consumer
            #await asyncio.sleep(random.random())
            # 把item放入队列, 挂起producer,恢复consumer
            await queue.put(item_id)
        # 放入一个None到队列表示生产全部结束
        await queue.put(None)
    
    
    async def consumer(queue):
        """consumer 定义"""
        while True:
            # 等待从队列中获得一个新的item, 等待过程中会挂起consumer,恢复producer
            item_id = await queue.get()
            if item_id is None:
                # 表示生产者都生产完了,没有待消费的请求了
                break
            print('consume item_id:{}'.format(item_id))
            # 模拟IO操作, 挂起consumer,恢复producer
            #await asyncio.sleep(random.random())
    
    if __name__ == "__main__":
        loop = asyncio.get_event_loop()
        queue = asyncio.Queue(loop=loop)
        producer_coro = producer(queue, 3)
        consumer_coro = consumer(queue)
        loop.run_until_complete(asyncio.gather(producer_coro, consumer_coro))
        loop.close()
    

    结果:

    produce item_id:0
    produce item_id:1
    produce item_id:2
    consume item_id:0
    consume item_id:1
    consume item_id:2
    

文章声明: 本文属于个人原创,欢迎转载,请注明出处,谢谢

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 我是在深入学习 kotlin 时第一次看到协程,作为传统线程模型的进化版,虽说协程这个概念几十年前就有了,但是协程...
    前行的乌龟阅读 99,804评论 32 182
  • 原创文章出自公众号:「码农富哥」,如需转载请请注明出处!文章如果对你有收获,可以收藏转发,这会给我一个大大鼓励哟!...
    大富帅阅读 9,910评论 3 21
  • python之进程、线程与协程 有这么个例子说他们的区别,帮助理解很有用。 有一个老板想开一个工厂生产手机。 他需...
    道无虚阅读 3,175评论 0 3
  • 原文:http://www.jianshu.com/p/4e048726b613 引言 随着node.js的盛行,...
    jacke121阅读 2,081评论 1 3
  • 协程 协程是轻量级线程,一个线程中可以有很多协程,协程本质上可以认为是运行在线程上的代码块,协程提供的挂起操作会使...
    android老男孩阅读 5,970评论 3 10