【Golang】Go语言编程思想(五):Goroutine
Goroutine
Golang 的并发编程非常有特色,它原生支持并发编程,这在其它编程语言当中是不太常见的。
以下面的例子来说明在 Golang 当中如何使用 go + func 来实现并发编程:
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
go func(i int) { // 开启的实际上是协程而非线程
// 但是目前看来协程和线程差不多
// 可以直接 go 一个匿名函数
// 当然也可以 go 一个函数, 但是直接 go 一个匿名函数更为常见
// 可以理解为, 主程序在持续的运行, 不断地开启线程, 线程中将打印 Hello ...
/* ... 相当于在 Python 当中不断 start 一个 Thread ... */
for {
fmt.Printf("Hello from"+
"goroutine %d\n", i)
}
// 将外部的 i 作为参数传入匿名函数中来保证线程的安全性
}(i) // i 具体的值从此处传入
}
}
在 IDE 中运行上述代码,没有任何输出。没有任何输出的原因是,go func 的函数体与 main 函数是并发执行的,并发执行的函数体当中还来不及执行,main 函数就已经结束了。Golang 的 main 一旦停止,则自动结束所有 goroutine。为了看到输出,在 main 函数的最后加入time.Sleep(time.Millisecond)
,此时便可以得到输出:
在其它编程语言当中,开启 10 个甚至是 100 个进程都是没问题的,但是如果想开启 1000 个,则较为困难,如果想让 1000 个进程同时输出,通常需要通过异步 IO 的方式来完成。但是在 Go 当中,不用关心这些问题,无论是 10 个,还是 100 个、1000 个,Golang 都可以并发执行。
协程 Coroutine
goroutine 实际上是一种协程,或者说它比较像协程(不是所有编程语言都支持协程这一概念)。
- 协程是轻量级的线程;
- 协程非抢占式地进行多任务处理,由协程主动交出控制权(线程进行抢占式多任务处理);
- 协程的执行实际上是编译器/解释器/虚拟机层面的任务,而不是操作系统层面的任务(直到 C++ 20 标准,C++ 才正式引入了协程的概念)。
- 多个协程可以在一个或多个线程上运行;
上面的代码开启了 10 个协程,每个协程会进行打印,由于 Print 与 IO 相关,因此会进行非抢占式处理。下面是一个抢占式处理的例子,在下例中我们试图在协程中对数组当中某个元素的值进行递增:
package main
import (
"fmt"
"time"
)
func main() {
var a [10]int
for i := 0; i < 10; i++ {
go func(i int) { // 开启的实际上是协程而非线程
// 但是目前看来协程和线程差不多
// 可以直接 go 一个匿名函数
// 当然也可以 go 一个函数, 但是直接 go 一个匿名函数更为常见
// 可以理解为, 主程序在持续的运行, 不断地开启线程, 线程中将打印 Hello ...
/* ... 相当于在 Python 当中不断 start 一个 Thread ... */
for {
a[i]++
}
// 将外部的 i 作为参数传入匿名函数中来保证线程的安全性
}(i) // i 具体的值从此处传入
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}
输出的结果为:
[7301865 8621345 0 11046028 0 11344634 8350582 12024286 6758912 8150205]
由于递增操作是抢占式操作,因此在某个临界值达到之前协程不会终止,而是一直抢占资源(在我的设备上可以得到最后的结果,在其它设备上可能始终陷入死循环)。
如果我们在 a[i] ++
语句之后加上runtime.Gosched()
,使得 go func 函数体变为:
for {
a[i]++
runtime.Gosched() // 手动交出协程的控制权
// 👆 一般情况下很少会使用, 此处仅做演示
}
则此时得到的结果是:[1071 707 674 781 731 733 779 702 699 704]
。原因是 go func 中手动交出了协程的控制权。
- 子程序是协程的一个特例。
其它语言当中的协程
- C++:Boost.Coroutine,通过外部库支持协程;
- Java:不支持,但第三方的 JVM 可以会在标准的 JVM 基础上支持协程;
- Python:使用 yield 关键字实现协程;Python 3.5 加入了 async def 对协程原生支持;
Goroutine 的定义
👆上图是一个调度器、线程和 Goroutine 的关系的例子,goroutine 可能被放在多个不同的线程当中,多个 goroutine 也可能处于相同的线程当中,由调度器来控制。
定义 Goroutine 的方法如下:
- 任何函数只需加上 go 就能送给调度器运行;
- 不需要在定义时区分是否为异步函数(相对于 Python 而言,不需要像 Python 那样在定义时就说明自己是一个协程);
- 调度器在合适的点进行切换;
- 使用 -race 来检测数据访问的冲突(在命令行中使用
go run -race XXX.go
来完成)
Goroutine 可能的切换点:
- I/O、select;
- channel;
- 等待锁;
- 函数调用(有时会切换,函数调用是 goroutine 切换的机会,是否切换由调度器决定);
- runtime.Gosched( )(方才已经演示过使用该方法进行手动切换);
- etc.
协程与协程之间可以相互通信,通过一个双向的通道来完成,这个通道被称为 Channel,是 Golang 的重要概念,将在下一章中进行学习。
原文地址:https://blog.csdn.net/Coffeemaker88/article/details/144341238
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!