这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记。
waitGroup
由于goroutine是并发的,所以channel想统一管理非常的困难,这就需要用到青训营第二节课中提到的waitGroup同步机制,示例代码如下:
package main
import (
"fmt"
"sync"
)
func doWork(id int,
w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c\n",
id, n)
w.done()
}
}
type worker struct {
in chan int
done func()
}
func createWorker(
id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
go doWork(id, w)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}
wg.Add(20)
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
wg.Wait()
}
func main() {
chanDemo()
}
waitGroup有著名的三大方法,分别是Add方法、Wait方法和Done方法,可以有效解决同步问题。
在创建workers即接收数据的channel时,每次都要将waitGroup以指针形式传入,因为全局只能有一个waitGroup,否则同步机制会混乱。
在worker结构体中,in是需要接收的数据,然后为了好看又把wg.Done()封装进了结构体中。这是一个好的编程思想,此处是画蛇添足,但以后就可以在实现的时候隐去实现细节,专注编写新的代码。
w.done()方法就是通知waitGroup这个通道的数据已经全部接收完毕了。
这样就不用再去增添一个参数 done chan bool,然后done<-true,最后再在主函数中接收<-worker.done了。
Add方法是说明当前有多少个任务,此处有20个任务。
wg.Wait()方法就是阻塞主线程,直到所有的协程都已经完成了接受数据和打印,然后再结束。
Select方法
package main
import (
"fmt"
"math/rand"
"time"
)
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(
time.Duration(rand.Intn(1500)) *
time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("Worker %d received %d\n",
id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
var c1, c2 = generator(), generator()
var worker = createWorker(0)
var values []int
tm := time.After(10 * time.Second)
tick := time.Tick(time.Second)
for {
var activeWorker chan<- int
var activeValue int
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
case activeWorker <- activeValue:
values = values[1:]
case <-time.After(800 * time.Millisecond):
fmt.Println("timeout")
case <-tick:
fmt.Println(
"queue len =", len(values))
case <-tm:
fmt.Println("bye")
return
}
}
}
select方法可以在所有goroutine协程中选择最快的那个去执行。下面就是一个select方法的实例,其中有到一定时间接收不到数据的timeout机制,以及时间戳定时打印机制,以及定时退出机制,还有Worker的初始化以及从c1,c2两个channel中收集数据的机制,在此作为示范,不再赘述。
今天的笔记就分享到这里,有什么问题可以在评论区交流讨论