写在前面:
0x01 — 使用并发
在Golang中运行并发很容易,加入有个函数 f(), 直接使用关键字 go f() 即可运行:
package main
import (
"fmt"
"testing"
"time"
)
func printInfo(n string, l []int) {
fmt.Println(n, "正在输出")
for i:=0; i < len(l);i++ {
fmt.Printf("%d \t", l[i])
}
fmt.Println("\n",n,"输出结束")
}
func TestGo(t *testing.T) {
// 定义一个整型切片
var li = []int{10,11,12,13,14,15,16,17,18,19}
t.Log("准备输出")
go printInfo("协程1", li) // 启动协程
go printInfo("协程2", li) // 启动协程
t.Log("输出结束")
time.Sleep(time.Second) // 睡眠一秒中
}
执行一次输出:
=== RUN TestGo
goroutine_test.go:23: 准备输出
goroutine_test.go:28: 输出结束
协程2 正在输出
10 11 12 13 14 15 16 17 18 19
协程2 输出结束
协程1 正在输出
10 11 12 13 14 15 16 17 18 19
协程1 输出结束
--- PASS: TestGo (1.00s)
PASS
再执行一次:
=== RUN TestGo
goroutine_test.go:23: 准备输出
goroutine_test.go:28: 输出结束
协程1 正在输出
协程2 正在输出
10 11 12 13 14 15 16 17 18 19
协程1 输出结束
10 11 12 13 14 15 16 17 18 19
协程2 输出结束
--- PASS: TestGo (1.01s)
PASS
从以上代码中可以看到,Info1和Info2这两个函数是并发运行的,顺序是无法确定的。
在测试函数中可以看到我在代码最后增加了一个time.Sleep方法,睡眠了1秒钟,是由于如果不睡眠,可能在并发协程还未执行,主程序就退出了,导致协程直接终止。
0x02 — 并发等待
为了等我们协程运行结束,同时不使用这种等待的low操作,可以使用内置的wait函数:
package main
import (
"fmt"
"sync"
"testing"
)
func printInfo(n string, l []int, wg * sync.WaitGroup) {
fmt.Println(n, "正在输出")
for i:=0; i < len(l);i++ {
fmt.Printf("%d \t", l[i])
}
fmt.Println("\n",n,"输出结束")
wg.Done() // 协程运行结束,通过Down方法释放
}
func TestGo(t *testing.T) {
// 定义一个同步等待并发变量
var wait sync.WaitGroup
// 定义一个整型切片
var li = []int{10,11,12,13,14,15,16,17,18,19}
t.Log("准备输出")
// 我们有两个协程,参数为2,参数的值会在协程中会被减去直到减为0,则执行指定函数
wait.Add(2)
go printInfo("协程1", li, &wait) // 启动协程
go printInfo("协程2", li, &wait) // 启动协程
wait.Wait() // 等待协程结束后,会调用
t.Log("输出结束")
//time.Sleep(time.Second) // 睡眠一秒中
}
以上是waitgroup使用方式,需要注意的是在两个goroutine函数中Done方法的调用指向是一个waitGroup对象。
0x03 — 并发控制
并发控制如果不借用三方库可以使用channel来实现,channel会在下一章节学习
0x02 — 总结
Golang goroutine很容易创建且开销较小,goroutine是由Golang自己调度,而不是系统,在协程间进行通信时,尽量遵循一个原则: 不要通过共享来通信,而要通过通信来共享 。后面我会介绍下通道的使用,在Golang中,通道很好地承接了通信的功能。