WaitGroup介绍
在go语言中,WaitGroup是用来表示一组等待goroutine运行结束的集合。你可以在主goroutine中使用Add方法添加需要等待的goroutine数量,在其它的goroutine结束的时候,需要调用Done方法来通知已经执行结束。同时,Wait方法会阻塞代码的执行直到所有的goroutine都执行完成。官方文档请点
使用WaitGroup等待goroutine结束
在上一篇文章中,我们使用了time.Sleep来阻塞主goroutine的执行,等待其它goroutine执行结束。现在我们来使用WaitGroup来实现:
package main
import (
"sync"
"fmt"
)
//使用WaitGroup来等待goroutine结束
var wait sync.WaitGroup
func A(){
fmt.Println("A执行完成。。")
wait.Done()
}
func main(){
//增加1,表示需要等待一个goroutine执行结束
wait.Add(1)
go A()
//等待goroutine结束
wait.Wait()
fmt.Println("main函数执行完成")
}
//A执行完成。。
//main函数执行完成
复制代码
可以看到使用了上面的代码来等待goroutine完成更加准确,而不像time.Sleep那样可能会出现等待时间之后goroutine还没有结束或者等待时间过长的问题。
从上面的代码,我们可以可以看到WaitGroup其实是使用了信号量的机制来实现的。
通道
在go语言中,通道被用来在多个goroutine之间同步数据。在上一篇文章中介绍的原子操作和同步锁解决数据争用的问题。同样可以使用通道来解决,而且通道更加方便,容易使用。当需要在多个goroutine中共享资源的时候,通道可以在goroutine之间传递数据,并且可以确保这个过程是同步的。而通道又分为有无缓冲通道和有缓冲通道。
无缓冲通道
无缓冲通道在收到数据时立即传递给另外一个goroutine,不会缓冲数据。这就需要发送数据的goroutine和接收数据的goroutine同时准备好。否则就会阻塞。
//创建一个无缓冲通道
buffer := make(chan int)
复制代码
使用内置的make函数来创建通道,第一个参数需要使用关键字chan,之后是该通道可以传递的数据类型。上面的例子表示创建了一个可以传递int类型数据的无缓冲通道。
package main
import (
"sync"
"fmt"
)
func main(){
//创建一个WaitGroup
var wait sync.WaitGroup
//信号量+2,表示等待两个goroutine完成
wait.Add(2)
buffer := make(chan int)
go func(){
fmt.Println("将数据放入通道。")
//将12放入通道
buffer <- 12
//完成
wait.Done()
}()
go func(){
//从通道中获取数据
fmt.Println("从通道拿到数据:",<- buffer)
//关闭通道
close(buffer)
//完成
wait.Done()
}()
//等待两个goroutine完成
wait.Wait()
fmt.Println("main函数执行完成。")
}
//将数据放入通道。
//从通道拿到数据: 12
//main函数执行完成。
复制代码
上面的代码,使用通道将数据从一个goroutine传递到了另外一个goroutine。我们也可以在两个goroutine之间相互传递数据。来看下面的代码:
package main
import (
"sync"
"fmt"
)
func main(){
//创建一个WaitGroup
var wait sync.WaitGroup
//信号量+2,表示等待两个goroutine完成
wait.Add(2)
buffer := make(chan int)
go func(name string){
fmt.Println("将数据放入通道。")
//将12放入通道
buffer <- 12
fmt.Println(name,"从通道拿到数据:",<-buffer)
//完成
wait.Done()
}("A")
go func(name string){
//从通道中获取数据
value := <- buffer
fmt.Println(name,"从通道拿到数据:",value)
//将value加1
value++
//重新放入通道
buffer <- value
//完成
wait.Done()
}("B")
//等待两个goroutine完成
wait.Wait()
//关闭通道
close(buffer)
fmt.Println("main函数执行完成。")
}
//将数据放入通道。
//B 从通道拿到数据: 12
//A 从通道拿到数据: 13
//main函数执行完成。
复制代码
从上面的代码执行结果可以看出,我们可以在两个goroutine之间交换数据,并且通道保证了数据的同步。
有缓冲通道
有缓冲通道表示,在通道接收到数据的时候会保留到缓冲区,等待接收goroutine的读取。只有在缓冲区满了之后,发送goroutine才会阻塞,而只有缓冲区无数据的时候,接收goroutine才会阻塞。
//创建一个有缓冲通道,缓冲区的大小为10
buffer := make(chan int,10)
复制代码
上面的代码使用内置函数make创建了一个有缓冲的通道,缓冲的大小为10。
package main
import (
"time"
"sync"
"fmt"
)
var wait sync.WaitGroup
func Producer(channel chan int){
for i:= 1 ; i<10 ;i++{
//休眠100毫秒
time.Sleep(100 * time.Millisecond)
//向通道中发送数据
channel <- i
fmt.Println("生产者生产:",i)
}
//生产完成,关闭通道
close(channel)
wait.Done()
}
func Consumer(channel chan int){
//延迟执行
defer wait.Done()
for {
//从通道中读取数据
value ,ok := <- channel
//当通道关闭的时候返回
if !ok {
return
}
//休眠150毫秒
time.Sleep(150 * time.Millisecond)
fmt.Println("消费者消费:",value)
}
}
func main(){
//创建一个有缓冲通道,缓冲区的大小为10
buffer := make(chan int,10)
wait.Add(2)
go Producer(buffer)
go Consumer(buffer)
wait.Wait()
fmt.Println("mian函数执行完成")
}
//生产者生产: 1
//生产者生产: 2
//消费者消费: 1
//生产者生产: 3
//消费者消费: 2
//生产者生产: 4
//生产者生产: 5
//消费者消费: 3
//生产者生产: 6
//消费者消费: 4
//生产者生产: 7
//生产者生产: 8
//消费者消费: 5
//生产者生产: 9
//消费者消费: 6
//消费者消费: 7
//消费者消费: 8
//消费者消费: 9
//mian函数执行完成
复制代码
上面的代码模拟了生产者消费者模式,生产者往通道发送数据,而消费者从通道中获取数据,当生产者生产完成之后,关闭通道。