背景:
众所周知,Go自带高并发的性能,而且实现并发协程很方便,只需要在方法的前面添加go就可以实现。
具体如下:
func main() {
go func() {
fmt.Println("Hello Func")
}()
fmt.Println("Main Func")
}
不过上面的方法可以有效实现并发执行,但是有个问题那就是,如果主函数执行完成了,协程函数还没有执行完成那么就会自动结束掉。所以面对这种问题,我们就需要解决一个问题那就是让主函数执行完成后进行阻塞等待协程函数执行完成。
1. 最笨的方法就是加个Sleep()睡眠阻塞就可以实现了。
func main() {
fmt.Println("start")
go func() {
fmt.Println("Hello Func")
}()
time.Sleep(10)
fmt.Println("end")
}
2. 在真正的开发设计中还是不建议采用这种方式进行,因为这种睡眠阻塞会严重影响代码的性能同时我们无法判断睡眠多久可以结束。这个时候就需要我们采用管道channel的方式来实现。
具体如下: (该方式比较适合channel数据没有任何用途,仅仅实现业务之间的同步)
func main() {
done := make(chan struct{})
go Hello(done)
println("主函数处理业务")
<-done
}
func Hello(done chan struct{}){
println("协程并发处理业务")
// 关闭管道Channel
close(done)
}
还有一种就是协程之间channel的数据之间有数据交互(这种情况写,可以不使用close(done)进行关闭管道,关闭管道后将无法写数据到管道,读的话可以获取的是channel默认的值,例如a chan bool。那么读的话就是false默认值)。
func main() {
done := make(chan Man)
go Hello(done)
println("主函数处理业务")
s:=<-done
fmt.Println(s.name)
}
type Man struct {
name string
}
func Hello(done chan Man){
println("协程并发处理业务")
// 关闭管道Channel
var man Man
man.name="gcg"
done<- man
}
3. 复杂的Channel管道和协程问题:
func getMessagesChannel(msg string, delay time.Duration) <-chan string {
c := make(chan string)
go func() {
for i := 1; i <= 3; i++ {
c <- fmt.Sprintf("%s %d", msg, i)
// Wait before sending next message
time.Sleep(time.Millisecond * delay)
}
}()
return c
}
func main() {
c1 := getMessagesChannel("first", 300)
c2 := getMessagesChannel("second", 150)
c3 := getMessagesChannel("third", 10)
for {
select {
case msg := <-c1:
println(msg)
case msg := <-c2:
println(msg)
case msg := <-c3:
println(msg)
default:
break
}
}
}
执行结果:(由于是死循环,会一直处于监听状态,不会终止运行)
4. sync.WaitGroup(可以理解为上锁):WaitGroup
对象内部有一个计数器,最初从0开始,它有三个方法:Add(), Done(), Wait()
用来控制计数器的数量。Add(n)
把计数器设置为n
,Done()
每次把计数器-1
,wait()
会阻塞代码的运行,直到计数器地值减为0
。
func main() {
wg := sync.WaitGroup{}
wg.Add(5) //要绑定5个事件
for i := 0; i < 5; i++ {
go func(i int) {
fmt.Println(i)
wg.Done() //协程释放 -1
}(i)
}
wg.Wait() //会等待5次协程的释放
fmt.Println("结束")
}
注意:
1) 计数器的值不能为负值,即表示Add() 给wg设置的值必须为正值,表示该计数器要绑定几个协程事件
2) WaitGroup对象不是引用型,所以在函数传输中需要使用 *syn.WaitGroup地址表示。
如下实例:
func main() {
wg := sync.WaitGroup{}
wg.Add(5)
for i := 0; i < 5; i++ {
go f(i, &wg)
}
wg.Wait()
}
// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup) {
fmt.Println("你好呀",i)
wg.Done()
}