一. 协程的定义

Coroutines are computer-program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes. --Wikipedia

翻译过来就是:协程是一个在多个入口点允许在某些位置挂起和恢复执行的,可以产生非抢占式任务的子程序的计算机程序组件。

协程的本质是用户态的上下文切换

二. 子程序

子程序是与协程相近的一个概念,在这里也解释一下,与协程进行对比。

我先打个通俗的比方:

假如小明的一家人每天都要喝水(忽略这句废话),所以小明买了饮水机放在自己的房间里,小明老爸也买了饮水机在自己房间里,小明哥哥也买了一个饮水机在自己房里。那么这三个饮水不仅占地方,还费钱。怎么解决这个问题呢?

因此不如一开始就只买一个饮水机,放在大厅里,供他们三个人使用。这样一来,小明,小明老爸,小明哥哥,他们三共同使用一个饮水机了。

这个比方虽夸张了些,但是逻辑上:三个饮水机占地方又费钱,它同样体现在程序设计里面就是占用空间重复编码。为了避免这种功能上的重复,这种编码(子程序)可以只放在一个地方,供该程序中的其它模块进行调用。

关于子程序,高德纳的《计算机程序设计艺术》卷一(1.4.1)有更为详细的介绍,我引用一下其中的描述:

WHEN A CERTAIN task is to performed at sereral different places in a program ,it is usually undesirable to repeat the coding in each place. To avoid this situation,the coding (called a subroutine) can be put into one place only,and a few extra instructions can be added to restart the outer program properly after the subroutine is finished.Transfer of control between subroutines and main programs is called subroutine linkage.--Donald Knuth.1

翻译过来就是:当在一个程序的若干个不同的地方,都要执行同一个确定的任务时,通常不希望在每个地方都重复编码,为避免这种重复的情况,这种编码(称为子程序)可以只放在一个地方,而且在这个子程序完成之后,可以附加少量额外的指令,以便适当的重新开始外部的程序。子程序与主程序之间控制的转移,称为子程序的链接。[2]

子程序的优点有如下:

  1. 节省空间
  2. 使程序逻辑清晰,便于开发者自己和别人调试。

因此可以理解为子程序是为了被“共用“而实现的程序,不过也有专用的子程序,它们只打算出现一个地方。

三. 协程与子程序的区别

Subrountines are special cases of more general program conmponents,called couroutine. In contrast to the unsymmetric relationship between a main routine and a subroutine,there is complete symmetry betweem coroutines,whichi call on each other--Donald Knuth.[3]

大意是:子程序是协程的特殊情况,主程序和子程序的关系是非对称的,是调用与被调用的关系, 而协程之间是完全的对称, 它们可以相互调用。这句话可能不太好理解,我希望读者能很好理解这句话,图在下面:

(这篇回答原先写在知乎上,由于知乎现在已经不太友好,我注销了账户,图片引用自我原先的知乎图片。)

协程的好处: 主程序和协程之间是完成对称的, 是平等的, 是伪异步的. 当其中主程序挂起或阻塞时, 会切换到协程上执行.

我的观点就是,子程序实际上为了解决重复编码的问题,而协程实际上是为了解决: 当进程在阻塞和挂起时,程序员期望程序更高的执行效率,以协程的方式并发执行。 它们的解决的问题不一样,它们的目的不一样,这是最主要的。

协程在执行时会发生两种可能:

  1. 如果切换到协程之后,该协程执行结束了, 会直接切换回与之对称的主进程(对称关系,其实主进程的说法已经不合适了).
  2. 如果该协程也发生挂起, 同样会直接切换回主进程. 假设当主进程再次被挂机时, 又回去执行被挂起的协程.

这是我原先的猜测,这两句话可能不太好理解。下面,我用Golang中的goroutine 的代码,进行验证我这一猜想。

四. Golang中的协程示例

下面的程序示例中 multiplyByTwo 是一个协程, 它的作用是把值乘以2.

当主程序main()挂机时, 会自动切换到协程上执行. 该示例参考: Soham Kamani

代码的执行过程:

1.我定义了一个协程, 它的功能是乘以2.

2.主程序main是最开始被调用的, 当time.sleelp执行时, 这个时候主程序进行了挂起, 协程抓了时机, 开始执行!

3.但是我设计的协程, 它计算结束后也开始了挂机. 这个时候主程序又开始执行了.

4.然后主程序又挂起, 协程继续执行, 这次协程执行完毕

5.主程序执行完毕, 然后退出了.

package main

import (
"fmt"
"time"
) func main() {
fmt.Println("1. start run main")
n := 3 // We want to run a goroutine to multiply n by 2
go multiplyByTwo(n) time.Sleep(time.Second) // suspending
fmt.Println("4. Hi. Back to main again")
time.Sleep(time.Second) // suspending
fmt.Println("6. done!")
} func multiplyByTwo(num int) int {
fmt.Println("2. start run go-routine")
result := num * 2
fmt.Println("3. go-routine run result: ", result)
time.Sleep(time.Second) // suspending
fmt.Println("5. back to go-routine again!")
return result
}

执行的输出结果:

Output1:

1. start run main
2. start run go-routine
3. go-routine run result: 6
4. Hi. Back to main again
5. back to go-routine again!
6. done!

上面的结果并不是唯一确定的, 也可以是 1,2,3,5,4,6, 其中4,5的位置有随机性. or Output2:

1. start run main
2. start run go-routine
3. go-routine run result: 6
5. back to go-routine again!
4. Hi. Back to main again
6. done!

协程与主程序是对称的关系, 我们无法得知什么时候协程在执行, 使用time.sleep主动挂起是很好的调用协程的策略.

如果你做大量的基准测试,我猜测对于步骤4和步骤5的随机性,我猜测结果是接近是50%的。

这段代码把主程序和协程之间的相互调用体现的淋漓尽致, 这种对称的关系. 它是用户态发生的。

五. References

  1. 维基百科, Coroutine - Wikipedia
  2. Knuth, Donald Ervin (1997). Fundamental Algorithms. The Art of Computer Programming. 1 (3rd ed.). Addison-Wesley. Section 1.4.2: Coroutines, pp. 193–200. ISBN 0-201-89683-4.
  3. 计算程序设计艺术.第一卷,基本算法:第3版/(美)高德纳(Knuth,D.E)著;苏运霖译。--北京:国防工业出版社。章节1.4.2:协程,pp. 178-184 ISBN 987 -7 -118 -02799 -0
  4. 代码示例的参考源: An introduction to using and visualizing channels in Go ➡️

协程和Goroutines示例的更多相关文章

  1. Kotlin协程第一个示例剖析及Kotlin线程使用技巧

    Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...

  2. python中协程的使用示例

    例子1 把字符串分割为列表 def line_splitter( delimiter = None ): print( 'ready to split' ) result = None while T ...

  3. 『GoLang』协程与通道

    作为一门 21 世纪的语言,Go 原生支持应用之间的通信(网络,客户端和服务端,分布式计算)和程序的并发.程序可以在不同的处理器和计算机上同时执行不同的代码段.Go 语言为构建并发程序的基本代码块是 ...

  4. Unity C#笔记 协程

    什么是协程 协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行. 可能看了这段文字介绍还是有点模糊,其实可以用多线程来比较. 多线程 多线程,顾名思义,多条同时执行的线程. 最初 ...

  5. unity协程coroutine浅析

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.序言 在unity的游戏开发中,对于异步操作,有一个避免不了的操作: 协程,以前一直理解的懵懵懂懂,最近认真充电了一下 ...

  6. Python3 与 C# 并发编程之~ 协程篇

      3.协程篇¶ 去年微信公众号就陆陆续续发布了,我一直以为博客也汇总同步了,这几天有朋友说一直没找到,遂发现,的确是漏了,所以补上一篇 在线预览:https://github.lesschina.c ...

  7. 转载:PHP 协程实现

    转自:https://newt0n.github.io/2017/02/10/PHP-%E5%8D%8F%E7%A8%8B%E5%8E%9F%E7%90%86/ 实现 PHP 协程需要了解的基本内容. ...

  8. python自动化开发学习 进程, 线程, 协程

    python自动化开发学习 进程, 线程, 协程   前言 在过去单核CPU也可以执行多任务,操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换任务2,任务2执行0.01秒,在切换到任务3,这 ...

  9. 如何正确的在 Android 上使用协程 ?

    前言 你还记得是哪一年的 Google IO 正式宣布 Kotlin 成为 Android 一级开发语言吗?是 Google IO 2017 .如今两年时间过去了,站在一名 Android 开发者的角 ...

随机推荐

  1. 对IT战略的认识

    提到战略,我不由想起了战术这个名词.按照我的理解,战术属于短期内的计划,是战略的组成部分,是实施次战略的短期工作计划或行动步骤:而战略是用来帮助我们赢取目标的行动计划(例如为获得更多的潜在客户而制定的 ...

  2. hyper-v显示分辨率如何自动调整

    打开文件/etc/default/grub 找到GRUB_CMDLINE_LINUX_DEFAULT所在行,在最后加上 video=hyperv_fb:[分辨率],比如我想要的分辨率是1600×900 ...

  3. linux 实时监控网速脚本(转)

    #!/bin/bash ethn=$ while true do RX_pre=$(cat /proc/net/dev | grep $ethn | sed 's/:/ /g' | awk '{pri ...

  4. coroutine闲谈

    coroutine居然能被吹到这种地步

  5. angular自定义module

    在app.module.ts里面,imports部分,添加你的自定义模块名在你的自定义模块内,添加了component以后,需要添加exports导出,类似下面 import { NgModule } ...

  6. jQuery 取值操作

    模板使用: https://startbootstrap.com/themes/sb-admin-2/ 使用的 bootstrap 模块 ,上面的这个网站可以下载 select 取值 <sele ...

  7. 008-linux shell vim使用

    一.概述 vi: Visual Interface 可视化接口 vim: VI iMproved VI增强版 全屏编辑器,模式化编辑器 vim模式: 编辑模式(命令模式) 输入模式 末行模式 模式转换 ...

  8. Anaconda(二)

    三.配置依赖包仓库 conda在安装依赖包的时候会检测已有包的版本与需要安装的版本是否匹配.以及相关包更新后的版本与现有的其他包是否会造成冲突. 添加清华镜像源(依赖包仓库),命令行中直接使用以下命令 ...

  9. python对不同类型文件(doc,txt,pdf)的字符查找

    python对不同类型文件的字符查找 TXT文件: def txt_handler(self, f_name, find_str): """ 处理txt文件 :param ...

  10. (十)redis源码解读

    一.redis工作机制 redis是 单线程,所有命令(set,get等)都会加入到队列中,然后一个个执行. 二.为什么redis速度快? 1.基于内存 2.redis协议resp 简单.可读.效率高 ...