原站地址:Go语言核心36讲_Golang_Go语言-极客时间
一、sync.WaitGroup和sync.Once
1. sync.WaitGroup 比通道更加适合实现一对多的 goroutine 协作流程。
2. WaitGroup类型有三个指针方法:Wait、Add和Done,以及内部有一个计数器。
(1) Wait方法:阻塞当前的 goroutine,直到计数器归零。
(2) add方法:加减计数器的值。用来记录需要等待的 goroutine 的数量
(3) Done方法:对计数器的值进行减1操作。可以在需要等待的 goroutine 中,通过defer语句调用它。
3. 具体实现代码:
func coordinateWithWaitGroup() {var wg sync.WaitGroup //声明WaitGroup类型,开箱即用wg.Add(2) //设置要等待的goroutine数目,加到计数器里num := int32(0)fmt.Printf("The number: %d [with sync.WaitGroup]\n", num)max := int32(10)go addNum(&num, 3, max, wg.Done) //启用第一个goroutine,结束后调用Done函数对计数器减1go addNum(&num, 4, max, wg.Done) //启用第二个goroutinewg.Wait() //阻塞等待计数器归0
}
4. 使用 WaitGroup注意:
(1) 计数器的值不能小于0.虽然是开箱即用,但是必须且尽早地增加其计数器的值。
(2) WaitGroup可以多次使用,但是不能跨周期使用。必须要等这个Wait方法执行结束之后,才能够开始下一个周期。
也就是: wait执行过程中,计数器不能被其他goroutine增加,否则会panic。wait方法和add方法必须放在同一个goroutine 中。
5. sync.Once 类型说明:
(1) 向其输入一个函数参数,然后保证这个函数之后只会被执行一次。
(2) 它的Do方法只接受一个函数参数,且必须是无参数声明和结果声明的函数。
(3) 只执行一次,是针对Once值来说的。所以,有多个只需要执行一次的函数,就应该为它们每一个都分配各自的Once值。
(4) 它内部有一个名叫done的字段,通过原子操作来计数,保证只执行一次。
6. 使用 sync.Once 需要注意的事项:
(1) Do方法只会在参数函数执行结束之后,才把done字段的值变为1。如果并发地调用了Once值的Do方法,会让其中一个发生阻塞。所以:Do方法不能是太耗时的或不会终结的。
(2) 对done字段的赋值1 是在 Once内部用defer语句的方式进行的,不是交给用户操作,所以无论是逻辑错误还是panic都必然赋值1,不能再执行。所以:Do方法不能实现重试机制。
二、context.Context类型
1. Context类型也是一种非常通用的同步工具,它还可以提供一类代表上下文的信息值。
并且这些信息的值是并发安全的,可以被传播给多个 goroutine。
Context还能传达撤销信号。
2. Context类型的值是可以繁衍的,可以通过一个Context值产生出任意个子值。
产生子值时需要输入父值的Context,子值携带父值的数据。
所有的Context值共同构成了代表了上下文全貌的树形结构,树根是一个已经在context包中预定义好的Context值,它是全局唯一的。
3. context包中包含了四个用于产生Context值的函数: (撤销具体意义见下面4)
(1) WithCancel:产生一个可撤销的Context子值,和一个用于触发撤销信号的函数。
(2) WithDeadline:产生会定时撤销的Context子值
(3) WithTimeout:产生会定时撤销的Context子值
(4) WithValue:产生会携带额外数据的Context子值
4. “撤销”一个Context值意味着什么?
Context类型的Done方法会返回一个接收通道,这个接收通道的用途并不是传递元素值,而是让调用方去接收 当前Context值已“撤销”的信号。
一旦Context值被撤销,接收通道就会立即被关闭。对于一个未包含任何元素值的通道来说,它的关闭会使针对它的接收操作结束。
代码示例如下:
func coordinateWithContext() {total := 12var num int32//1. 获得context子值和撤销函数cxt, cancelFunc := context.WithCancel(context.Background()) for i := 1; i <= total; i++ {go addNum(&num, i, func() {if atomic.LoadInt32(&num) == int32(total) {cancelFunc() //2. 全部goroutine运行完毕,执行撤销函数。}})}<-cxt.Done() //3. 撤销函数执行完毕,cxt被撤销,cxt.Done()返回的通道被关闭。//4. 从被关闭的通道做接收操作,代码会解除阻塞,往下执行。fmt.Println("End.")
}
5. “撤销”的应用场景还包括: HTTP 请求被终止后的响应,SQL 指令被取消后的处理。
6. 当父值context的撤销函数被调用后,Context值会先关闭它内部的接收通道,然后向它所有子值传达撤销信号。
7. WithValue函数在产生含数据的Context值时,需要三个输入参数,即:父值、键和值。
含数据Context值并不是用字典来存储键和值,只是把键和值简单地存储在自己相应的字段中而已。
8. Context类型的Value方法就是被用来获取数据的。
它先判断给定的键,是否与当前值中存储的键相等,如果相等就把返回;否则就到其父值中继续查找。
如果其父值中仍然未存储相等的键,那么就沿着上下文根节点的方向一路查找下去。
9. Context接口没有提供改变数据的方法。只能通过在上下文树中添加含数据的Context值来存储新的数据,以及通过撤销此种值的父值来丢弃掉不需要的数据。
三、临时对象池sync.Pool
1. sync.Pool类型,被称为临时对象池,临时对象。的意思是:不需要持久使用的某一类值。
2. sync.Pool类型只有两个方法——Put和Get。
Get方法会从当前的池中删除掉一个值,然后把这个值作为结果返回。
如果Get方法没能获得值,就会使用New字段创建一个新值。
New字段的实际值需要我们在初始化临时对象池的时候就给定。
3. Go 系统的垃圾回收器,在每次开始执行之前,都会对临时对象池中的值进行全面地清除。这就 是它的最大价值。