✍个人博客:Pandaconda-优快云博客
📣专栏地址:http://t.csdnimg.cn/UWz06
📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
109. WaitGroup 原理
在 Go 语言中,sync.WaitGroup 的实现原理非常简单,它基本上是通过一个计数器来实现的。当我们调用 WaitGroup.Add(n) 方法时,它会将计数器的值增加 n。当我们调用 WaitGroup.Done() 方法时,它会将计数器的值减 1。而当我们调用 WaitGroup.Wait() 方法时,它会阻塞等待,直到计数器的值为 0。
110. WaitGroup 实现方法
下面是 sync.WaitGroup 的简化版实现,用来更好地理解它的工作原理:
type WaitGroup struct {
counter int32
cond *sync.Cond
}
func NewWaitGroup() *WaitGroup {
return &WaitGroup{
counter: 0,
cond: sync.NewCond(&sync.Mutex{}),
}
}
func (wg *WaitGroup) Add(delta int) {
atomic.AddInt32(&wg.counter, int32(delta))
}
func (wg *WaitGroup) Done() {
atomic.AddInt32(&wg.counter, -1)
if wg.counter == 0 {
wg.cond.Broadcast()
}
}
func (wg *WaitGroup) Wait() {
wg.cond.L.Lock()
for wg.counter > 0 {
wg.cond.Wait()
}
wg.cond.L.Unlock()
}
在这个简化版实现中,我们首先创建了一个 WaitGroup 类型,并将计数器初始化为 0。然后,我们使用 sync.Cond 类型来实现等待和通知机制,以便在调用 Wait() 方法时可以阻塞等待。
在 Add() 方法中,我们使用原子操作将计数器的值增加指定的值。在 Done() 方法中,我们使用原子操作将计数器的值减 1,并检查计数器是否为 0。如果计数器为 0,说明所有 Goroutine 都已经完成任务,我们就可以使用 sync.Cond.Broadcast() 方法通知所有正在等待的 Goroutine 继续执行。
在 Wait() 方法中,我们首先获取互斥锁,然后在一个循环中等待计数器的值为 0。在循环中,我们使用 sync.Cond.Wait() 方法释放互斥锁,并等待条件变量上的通知。当计数器的值为 0 时,说明所有 Goroutine 都已经完成任务,我们就可以退出循环,并释放互斥锁。
需要注意的是,这个简化版实现并没有考虑到 WaitGroup 的嵌套使用情况,并且也没有处理 WaitGroup 计数器被减为负数的情况。真正的 sync.WaitGroup 实现要比这个简化版实现复杂得多,但是基本的实现原理是一样的。
111. 什么是 sync.Once?
sync.Once 是 Go 语言中的一个同步原语,用于实现只执行一次的操作。它可以保证在多个 Goroutine 中只执行一次指定的操作,即使这个操作被多次调用。
sync.Once 的使用非常简单,只需要创建一个 sync.Once 类型的变量,然后使用 Do() 方法来指定要执行的操作。Do() 方法会保证指定的操作只会被执行一次,无论它被调用多少次。下面是一个简单的例子:
var once sync.Once
func setup() {
fmt.Println("Performing setup...")
}
func main() {
// 在第一次调用时执行 setup 函数
once.Do(setup)
// 在第二次调用时不执行任何操作
once.Do(func() { fmt.Println("This shouldn't be printed.") })
}
在这个例子中,我们首先定义了一个 sync.Once 类型的变量 once。然后,我们使用 once.Do() 方法来指定要执行的操作。在第一次调用时,Do() 方法会执行 setup() 函数,输出 “Performing setup…”。在第二次调用时,Do() 方法不会执行任何操作,因为 setup() 函数已经被执行过了。
需要注意的是,Do() 方法是阻塞的,也就是说,在第一次调用还没有完成之前,后续的调用会被阻塞。这个特性可以用来保证只有一个 Goroutine 执行指定的操作,而其他 Goroutine 等待它完成之后再继续执行。此外,Do() 方法只会执行一次指定的操作,即使在多个 Goroutine 中调用它。这个特性可以用来避免重复初始化等问题。