Go语言 协程

本文介绍了Go语言中的协程,协程作为一种轻量级线程,与传统线程相比,具有更低的开销和更灵活的调度。文章详细阐述了协程的优势,如何开启和管理协程,以及如何通过通道进行通信以避免竞态条件。通过示例代码展示了如何启动多个协程并发执行,并解释了为何在主线程中需要适当控制协程的执行流程以确保所有协程都有机会运行。
摘要由CSDN通过智能技术生成

导言

  • 原文链接: Part 21: Goroutines
  • If translation is not allowed, please leave me in the comment area and I will delete it as soon as possible.

协程

在这一节中,我们来讨论一下,Go语言 是怎么使用协程实现并发的。

协程是什么?

协程就是一个函数或方法 — 这个 函数或方法 与其他的 函数或方法 并发执行。协程可以看做轻量级线程。与线程相比,创建协程的耗费很少。因此,一个 Go 应用可以拥有成千上万的协程。

与线程相比,协程具有的优势

  1. 与线程相比,协程的耗费十分低廉。它们的堆栈只有 几kb,而且它们的堆栈能根据应用需要,进行扩大或缩小。然而,线程的栈空间必须被指定,而且是固定的。
  2. 协程是系统线程的多路复用。一个具有成千上万协程的程序,可能只有一个线程。假如运行在线程上的协程阻塞了 — 例如等待用户输入,此时将会有新的系统线程被创建,且余下的协程会被移向这个新线程。这些复杂的事情交代给运行环境就可以了,作为程序员,我们将拥有一个简单的 API,这个 API 已经隐藏了复杂的细节,使我们可以方便地进行并发作业。
  3. 协程们使用通道进行通信。在多协程并发环境中,通道可以避免竞态条件的发生,即防止协程对共享内存的 “同时” 访问。通道可以认为是一根管道,这根管道可供协程们进行数据传输。 (这比较抽象,之后再说管道是啥)

怎么开启一个协程?

只需在函数或方法前,添加一个关键字 go,你就能运行一个协程。

接下来,我们创建一个协程吧~

package main

import (  
    "fmt"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    fmt.Println("main function")
}

在第 11 行,go hello() 开启了一个新协程。现在,hello函数 将与 main函数 一起并发运行。main函数 在其自己的协程中运行,这个协程叫做 main协程 — main Goroutine

运行这个程序,你将会大吃一惊!

程序只会输出: main function,我们自己创建的协程呢?

在理解了协程的两个主要特性后,我们才能知道发生了什么。

  1. 当开启新协程时,协程的调用将会立即返回。与函数不同,控件并不会关心协程是否完成工作。控件会马上转向协程调用的下一句代码,并不会等待协程工作的完成,协程的返回值会被忽略。
  2. main协程 运行时,其他协程才能运行。如果 main协程 终止了,程序也将被终止,其它协程将无法运行。

我想,现在你应该可以理解,为什么我们创建的协程没有运行了。在第 11 行,在 go hello() 后,控件立即转向下一行,它并不会等待 hello协程 完成工作。在第 12 行打印了 main function 后,因为已经没有可运行的代码了,于是 main协程 终止了。最终,hello协程 并没有得到运行的机会。

我们修复一下上面的代码。

package main

import (  
    "fmt"
    "time"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

在上面程序的第 13 行,我们调用了 time包 的 Sleep函数,它能让正在运行的协程休眠一段时间。在这个例子中,main协程 被强制休眠 1 秒。此时,在 main协程终止前,hello协程 将拥有足够的运行时间。这个程序将首先打印 Hello world goroutine,并在 1 秒后打印 main function

main协程 中使用 Sleep函数,这是一个奇技淫巧,它能让我们理解协程们怎么工作。当然,我们也可以使用通道去阻塞 main协程,这也可以保证其他协程能完成它们的工作。

启动多个协程

为了更好的理解协程,我们来写一个程序,这个程序可以开启多个协程。

package main

import (  
    "fmt"
    "time"
)

func numbers() {  
    for i := 1; i <= 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}
func alphabets() {  
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}
func main() {  
    go numbers()
    go alphabets()
    time.Sleep(3000 * time.Millisecond)
    fmt.Println("main terminated")
}

在上面程序的第 2122 行,我们开启了 2 个协程。这 2 个协程将会并发地运行。numbers协程 会在休眠 250 毫秒后,打印 1,之后再次进入休眠状态,休眠结束再打印 2,这个循环将会一直发生,直到打印了 5。同样地,alphabets协程 会打印 ae,它拥有 400 毫秒的休眠时间。main协程 会在初始化 numbers协程 和 alphabets协程 后,休眠 3000 毫秒,之后进入终止状态。

运行这个程序,输出如下:

1 a 2 3 b 4 c 5 d e main terminated  

下图描绘了程序的运行细节,在新页面查看这个图片将会更清晰喔。
在这里插入图片描述
在图片中,蓝色框表示 numbers协程,栗色框表示 alphabets协程,绿色框表示 main协程。而最底的黑色框,统一了上述的 3 个协程,它展示了这 3 个协程的执行细节。黑色框顶部的字符串,如 0 ms250 ms,表示的是时间点,而框底部的字符串表示的是输出。蓝框给予了我们如下信息:在 250 ms后,1 会被打印,在 500 ms 后,2 会被打印,以此类推。

黑色框底部的 1 a 2 3 b 4 c 5 d e main terminated,就是程序的输出。

图片不言自明,通过这个图片,你将能理解这个程序做了什么。

这就是协程,祝你愉快~

原作者留言

优质内容来之不易,您可以通过该 链接 为我捐赠。

最后

感谢原作者的优质内容。

这是我的第四次翻译,欢迎指出文中的任何错误。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值