乐趣区

24-理解-Go-语言中的协程goroutine

Hi,大家好,我是明哥。

在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号《Go 编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。

我的在线博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


说到 Go 语言,很多没接触过它的人,对它的第一印象,一定是它从语言层面天生支持并发,非常方便,让开发者能快速写出高性能且易于理解的程序。

在 Python(为 Py 为例,主要是我比较熟悉,其他主流编程语言也类似)中,并发编程的门槛并不低,你要学习多进程,多线程,还要掌握各种支持并发的库 asyncio,aiohttp 等,同时你还要清楚它们之间的区别及优缺点,懂得在不同的场景选择不同的并发模式。

而 Golang 作为一门现代化的编程语言,它不需要你直面这些复杂的问题。在 Golang 里,你不需要学习如何创建进程池 / 线程池,也不需要知道什么情况下使用多线程,什么时候使用多进程。因为你没得选,也不需要选,它原生提供的 goroutine(也即协程)已经足够优秀,能够自动帮你处理好所有的事情,而你要做的只是执行它,就这么简单。

一个 goroutine 本身就是一个函数,当你直接调用时,它就是一个普通函数,如果你在调用前加一个关键字 go,那你就开启了一个 goroutine。

// 执行一个函数
func()

// 开启一个协程执行这个函数
go func()

1. 协程的初步使用

一个 Go 程序的入口通常是 main 函数, 程序启动后,main 函数最先运行,我们称之为 main goroutine

在 main 中或者其下调用的代码中才可以使用 go + func() 的方法来启动协程。

main 的地位相当于主线程,当 main 函数执行完成后,这个线程也就终结了,其下的运行着的所有协程也不管代码是不是还在跑,也得乖乖退出。

因此如下这段代码运行完,只会输出 hello, world,而不会输出 hello, go(因为协程的创建需要时间,当 hello, world 打印后,协程还没来得及并执行)

import "fmt"

func mytest() {fmt.Println("hello, go")
}

func main() {
    // 启动一个协程
    go mytest()
    fmt.Println("hello, world")
}

对于刚学习 Go 的协程同学来说,可以使用 time.Sleep 来使 main 阻塞,使其他协程能够有机会运行完全,但你要注意的是,这并不是推荐的方式(后续我们会学习其他更优雅的方式)。

当我在代码中加入一行 time.Sleep 输出就符合预期了。

import (
    "fmt"
    "time"
)

func mytest() {fmt.Println("hello, go")
}

func main() {go mytest()
    fmt.Println("hello, world")
    time.Sleep(time.Second)
}

输出如下

hello, world
hello, go

2. 多个协程的效果

为了让你看到并发的效果,这里举个最简单的例子

import (
    "fmt"
    "time"
)

func mygo(name string) {
    for i := 0; i < 10; i++ {fmt.Printf("In goroutine %s\n", name)
        // 为了避免第一个协程执行过快,观察不到并发的效果,加个休眠
        time.Sleep(10 * time.Millisecond) 
    }
}

func main() {go mygo("协程 1 号") // 第一个协程
    go mygo("协程 2 号") // 第二个协程
    time.Sleep(time.Second)
}

输出如下,可以观察到两个协程就如两个线程一样,并发执行

In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号
In goroutine 协程 1 号
In goroutine 协程 2 号

通过以上简单的例子,是不是折服于 Go 的这种强大的并发特性,将同步代码转为异步代码,真的只要一个关键字就可以了,也不需要使用其他库,简单方便。

本篇只介绍了协程的简单使用,真正的并发程序还是要结合 信道(channel)来实现。关于信道的内容,将在下一篇文章中介绍。

系列导读

01. 开发环境的搭建(Goland & VS Code)

02. 学习五种变量创建的方法

03. 详解数据类型:整形与浮点型

04. 详解数据类型:byte、rune 与 string

05. 详解数据类型:数组与切片

06. 详解数据类型:字典与布尔类型

07. 详解数据类型:指针

08. 面向对象编程:结构体与继承

09. 一篇文章理解 Go 里的函数

10. Go 语言流程控制:if-else 条件语句

11. Go 语言流程控制:switch-case 选择语句

12. Go 语言流程控制:for 循环语句

13. Go 语言流程控制:goto 无条件跳转

14. Go 语言流程控制:defer 延迟调用

15. 面向对象编程:接口与多态

16. 关键字:make 和 new 的区别?

17. 一篇文章理解 Go 里的语句块与作用域

18. 学习 Go 协程:goroutine

19. 学习 Go 协程:详解信道 / 通道

20. 几个信道死锁经典错误案例详解

21. 学习 Go 协程:WaitGroup

22. 学习 Go 协程:互斥锁和读写锁

23. Go 里的异常处理:panic 和 recover

24. 超详细解读 Go Modules 前世今生及入门使用

25. Go 语言中关于包导入必学的 8 个知识点

26. 如何开源自己写的模块给别人用?

27. 说说 Go 语言中的类型断言?

28. 这五点带你理解 Go 语言的 select 用法


退出移动版