协程和Goroutines示例

一. 协程的定义

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]

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

v2-5a6599da8257f14c195efa64a520894f_hd.jpg

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

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

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

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

  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 ➡️

转载于:https://www.cnblogs.com/crb912/p/9179453.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值