问题 1:怎样才能让主 goroutine 等待其他 goroutine?
- sleep,最简单粗暴。
- 使用通道类型。
sync.WaitGroup类型。
问题 2:怎样让我们启用的多个 goroutine 按照既定的顺序运行?
问题背景:
go函数的实际执行顺序往往与其所属的go语句的执行顺序(或者说 goroutine 的启用顺序)不同,而且默认情况下的执行顺序是不可预知的。 在问题1的代码上做进一步的改写
demo1
package main
import (
"fmt"
//"time"
)
func main() {
num := 10
sign := make(chan struct{}, num)
//类型字面量struct{}有些类似于空接口类型interface{},
//它代表了既不包含任何字段也不拥有任何方法的空结构体类型。
for i := 0; i < num; i++ {
go func() {
fmt.Println(i)
sign <- struct{}{}
}()
}
// 办法1。
//time.Sleep(time.Millisecond * 500)
// 办法2。
for j := 0; j < num; j++ {
<-sign
}
}
---
9
10
3
10
10
10
10
10
10
10
- 保证每个 goroutine 都可以拿到一个唯一的整数。
for i := 0; i < 10; i++ {
//在go语句被执行时,我们传给go函数的参数i会先被求值
//因此得到了当次迭代的序号
go func(i int) {
fmt.Println(i)
}(i)
}
for i := uint32(0); i < 10; i++ {
go func(i uint32) {
fn := func() {//声明了一个匿名的函数,并把它赋给了变量fn
fmt.Println(i)
}//函数功能:打印go函数的参数i的值
trigger(i, fn) //参数:uint32类型的参数i, func()类型的参数fn
//func()代表的是既无参数声明也无结果声明的函数类型。
}(i)
}
trigger := func(i uint32, fn func()) {
for {
if n := atomic.LoadUint32(&count); n == i {
fn()
atomic.AddUint32(&count, 1)
break
}
time.Sleep(time.Nanosecond)
}
}
trigger函数会不断地获取一个名叫count的变量的值,并判断该值是否与参数i的值相同。如果相同,那么就立即调用
fn代表的函数,然后把count变量的值加1,最后显式地退出当前的循环。否则,我们就先让当前的 goroutine“睡眠”一个纳秒再进入下一个迭代。
🐖Ⅰ:操作变量
count的时候使用的都是原子操作。这是由于
trigger函数会被多个 goroutine 并发地调用,所以它用到的非本地变量count,就被多个用户级线程共用了。因此,对它的操作就产生了竞态条件(race condition),破坏了程序的并发安全性。所以,我们总是应该对这样的操作加以保护,在
sync/atomic包中声明了很多用于原子操作的函数。由于选用的原子操作函数对被操作的数值的类型有约束,所以对
count以及相关的变量和参数的类型进行了统一的变更(由int变为了uint32)。 宏观理解 纵观count变量、trigger函数以及改造后的for语句和go函数,我要做的是,让count变量成为一个信号,即它的值总是下一个可以调用打印函数的go函数的序号。这个序号其实就是启用 goroutine 时,那个当次迭代的序号。
从而保证
go函数实际的执行顺序与go语句的执行顺序完全一致。此外,这里的
trigger函数实现了一种自旋(spinning)。除非发现条件已满足,否则它会不断地进行检查。
demo2
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
var count uint32
trigger := func(i uint32, fn func()) {
for {
if n := atomic.LoadUint32(&count); n == i {
fn()
atomic.AddUint32(&count, 1)
break
}
time.Sleep(time.Nanosecond)
}
}
for i := uint32(0); i < 10; i++ {
go func(i uint32) {
fn := func() {
fmt.Println(i)
}
trigger(i, fn)
}(i)
}
trigger(10, func() {})
}
---
0
1
2
3
4
5
6
7
8
9
思考题
1.runtime包中提供了哪些与模型三要素 G、P 和 M 相关的函数?(模型三要素内容在上一篇)