18.Goroutine

Goroutines

第一个小例子

主函数返回时,所有的goroutine都会被直接打断,程序退出。

除了从主函数退出或者直接终止程序之外,没有其它的编程方法能够让一个goroutine来打断另一个的执行,但是之后可以看到一种方式来实现这个目的,通过goroutine之间的通信来让一个goroutine请求其它的goroutine,并让被请求的goroutine自行结束执行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
	"fmt"
	"time"
)

func main() {
	go spinner(100 * time.Millisecond)
	const n = 45
	fibN := fib(n) // slow
	fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
	for {
		for _, r := range `-\|/` {
			fmt.Printf("\r%c", r)
			time.Sleep(delay)
		}
	}
}

func fib(x int) int {
	if x < 2 {
		return x
	}
	return fib(x-1) + fib(x-2)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func T1() {
	go T2()
	fmt.Println("t1")

}

func T2() {
	fmt.Println("t2")
}

func Test1(t *testing.T) {

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		T1()
	}()
	wg.Wait()
	fmt.Println("t3")
}

资源共享并发

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func TestCounterThreadSafe2(t *testing.T) {
	var mut sync.Mutex
	var wg sync.WaitGroup

	counter := 0
	for i := 0; i < 5000; i++ {
		wg.Add(1)
		go func() {
			mut.Lock()
			defer wg.Done()
			defer mut.Unlock()

			counter++

		}()
	}

	wg.Wait()

	t.Logf("counter = %d", counter)
}

参数竞争

匿名函数中的循环变量快照问题。上面这个单独的变量f是被所有的匿名函数值所共享,且会被连续的循环迭代所更新的。当新的goroutine开始执行字面函数时,for循环可能已经更新了f并且开始了另一轮的迭代或者(更有可能的)已经结束了整个循环,所以当这些goroutine开始读取f的值时,它们所看到的值已经是slice的最后一个元素了。显式地添加这个参数,我们能够确保使用的f是当go语句执行时的“当前”那个f。

1
2
3
4
5
6
for _, f := range filenames {
    go func() {
        thumbnail.ImageFile(f) // NOTE: incorrect!
        // ...
    }()
}

正确的做法

1
2
3
4
5
6
for _, f := range filenames {
    go func(f string) {
        thumbnail.ImageFile(f) // NOTE: incorrect!
        // ...
    }(f)
}

GMP 调度器

  • goroutine 协程
  • machine 是操作系统的主线程, 物理线程
  • processor 处理器抽象,负责衔接 M 和 G 的调度上下文,将等待的 G 和 M 对接。P 决定了并行任务的数量,可通过 runtine.GOMAXPROCS 来设定,默认设置是可用核心数。

调度流程

  1. go func()创建
    1. 入 局部队列
    2. 局部已入全局队列
  2. M 获取 G
    1. m1P 本地队列为空则中全局获取
    2. 若为空, 从其它 MP 组合中偷取 G
  3. 调度
  4. 执行
  5. 若发生阻塞
    1. 创建一个M 或从 休眠队列取一个
    2. 接管当前正在阻塞 GP
  6. 时间片超时返回

参考

Go:g0,特殊的 Goroutine

Licensed under CC BY-NC-SA 4.0
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy