Skip to content

协程

来看一个例子

go
package main

import (
	"fmt"
	"time"
)

func showMsg(msg string) {
	for i := 0; i < 3; i++ {
		fmt.Println(`show msg...` + msg)
		//设置一个程序 每打印一次后,休息一秒钟
		time.Sleep(time.Second * 1)
	}
}

func main() {
	showMsg(`111`)
	showMsg(`222`)
	print(`执行下面的内容`)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

定义了一个showMsg,这是一个延时函数

函数的执行方法是从上到下的,所以输出了以下内容

show msg...111
show msg...111
show msg...111
show msg...222
show msg...222
show msg...222
执行下面的内容
1
2
3
4
5
6
7

前面的两个程序阻止了主程序6s中的时间,这对于程序是致命的

如果我们不希望这种延时函数阻止主程序执行,那么就需要启用一个协程,关键字是go

go
func main() {
	go showMsg(`111`) // 在第一个函数启动一个协程
	showMsg(`222`)
	print(`执行下面的内容`)
}
1
2
3
4
5

查看执行结果

show msg...111
show msg...222
show msg...222
show msg...111
show msg...111
show msg...222
执行下面的内容
1
2
3
4
5
6
7

两个函数开始交替进行执行,主程序在第三秒的时候开始执行

如果我们在两个函数上都启动协程会发生什么呢?

go
func main() {
	go showMsg(`111`) // 在第一个函数启动一个协程
	go showMsg(`222`)
	print(`执行下面的内容`)
}
1
2
3
4
5

查看执行结果

执行下面的内容
1

因为主程序的关闭,两个协程也被关闭了。

WaitGroup 实现等待协程

WaitGroup 就是一个等待协程结束的结构

go
package main

import (
	"fmt"
	"sync"
	"time"
)

var wp sync.WaitGroup

func showMsg(msg string) {
	defer wp.Done()
	for i := 0; i < 3; i++ {
		fmt.Println(`show msg...` + msg)
		//设置一个程序 每打印一次后,休息一秒钟
		time.Sleep(time.Second * 1)
	}
}

func main() {
	go showMsg(`111`)
	wp.Add(1)
	go showMsg(`222`)
	wp.Add(1)
	wp.Wait()
	print(`执行下面的内容`)
}

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

sync.Mutex

使用sync.Mutex加锁的方式,来解决两个携程操作一个变量导致计算错误的

go
package main

import (
	"sync"
	"time"
)

var count = 100
var lock sync.Mutex

func add() {
	lock.Lock()
	count++
	lock.Unlock()
}
func sub() {
	lock.Lock()
	count--
	lock.Unlock()
}
func main() {
	for i := 0; i < 100; i++ {
		go sub()
		go add()

	}

	time.Sleep(time.Second)
	println(`count=`, count)
}

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
30
31

原子操作

加减

原子操作也可以让两个携程操作一个变量 并在高并发期间,保持不冲突

go
package main

import (
	"sync/atomic"
	"time"
)

var count int32 = 100

func add() {
	atomic.AddInt32(&count, 1)
}
func sub() {
	atomic.AddInt32(&count, -1)
}
func main() {
	for i := 0; i < 100; i++ {
		go sub()
		go add()

	}
	time.Sleep(time.Second)
	println(`count=`, count)
}

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

读写

go
package main

import (
	"sync/atomic"
)

var count int32 = 100

func add() {
	atomic.AddInt32(&count, 1)
	val := atomic.LoadInt32(&count)
	atomic.StoreInt32(&count, 600)
	println(val)   // 101
	println(count) //600
}

func main() {
	add()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

runtime.Gosched

runtime.Gosched的作用是让出cpu的时间片。当有协程执行时,先让协程执行完毕,再执行

go
package main

import (
	"fmt"
	"runtime"
)

func showMsg(msg string) {
	for i := 0; i < 3; i++ {
		fmt.Println(`show msg...` + msg)
	}
}
func main() {
	go showMsg(`111`)
	for i := 0; i < 4; i++ {
		runtime.Gosched() // 当有任务执行时,让出来
		print(`执行后面的内容...`, "\n")
	}
	print(`end...`)

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

输出结果

go
执行后面的内容...
show msg...111
执行后面的内容...
show msg...111
执行后面的内容...
show msg...111
执行后面的内容...
end...
1
2
3
4
5
6
7
8

第一次跑到前面可能是主进程执行的更快那时候cpu本来就属于空置状态, 假如注释掉runtime.Gosched()那么协程将可能在还没有执行完毕,就被关闭

runtime.Goexit()

runtime.Goexit() 的作用是退出协程

go
package main

import (
	"fmt"
	"runtime"
	"time"
)

func showMsg() {
	for i := 0; i < 10; i++ {
		if i == 5 {
			runtime.Goexit() // 退出协程
		}
		fmt.Println(`show msg...`, i)
	}
}

func main() {
	go showMsg()

	time.Sleep(time.Second)

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

runtime.NumCPU()

获取cpu核数