For-learning-Go-Tutorial

Go语言是谷歌2009发布的第二款开源编程语言。

Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。

因而一直想的是自己可以根据自己学习和使用Go语言编程的心得,写一本Go的书可以帮助想要学习Go语言的初学者快速入门开发和使用!

Sync.WaitGroup解析

Golang中的同步可以通过Sync.WaitGroup来实现的.WaitGroup的功能,它实现了一个类似队列的结构,可以一直向队列中添加任务,当任务完成后便从队列中删除,如果队列中的任务没有完全完成,可以通过Wait()函数来出发阻塞,防止程序继续进行,直到所有的队列任务都完成为止.

WaitGroup的特点是,Wait()方法可以用来阻塞直到队列中的所有任务都完成时才解除阻塞,而不需要sleep一个固定的时间来等待.但是其缺点是无法指定固定的Goroutine数目,我们可以通过使用channel解决这个问题。

Sync.WaitGroup中有3个方法,Add(),Done(),Wait()。其中Done()是Add(-1)的别名。

Sync.WaitGroup中三个方法的作用是:

  • Add:添加或者减少等待goroutine的数量
  • Done:相当于Add(-1),减掉一个goroutine计数,计数不为0
  • Wait:执行阻塞,直到所有的WaitGroup数量变成0

这里需要注意下,Sync中的Add()方法和Done方法,官方的文档也有说明这个,The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished.即在运行main函数的goroutine里运行Add()方法,在其他的goroutine里面运行Done()函数。

Add()方法添加将可能为负的增量添加到WaitGroup计数器。如果计数器变为零,则释放在等待时阻止的所有goroutine。如果计数器变为负数,则panic。 请注意,在计数器为零时发生的具有正增量的调用时必须在Wait()方法之前。 具有负增量的调用或具有在计数器大于零时开始的正增量的调用可以在任何时间发生。 通常,这意味着对Add的调用应该在语句之前执行,创建要等待的goroutine或其他事件。如果重新使用WaitGroup等待几个独立的事件集,新的Add()调用必须在所有先前的Wait()调用返回后发生。

  1. func (wg *WaitGroup) Add(delta int) {
  2. statep, semap := wg.state()
  3. if race.Enabled {
  4. _ = *statep // trigger nil deref early
  5. if delta < 0 {
  6. // Synchronize decrements with Wait.
  7. race.ReleaseMerge(unsafe.Pointer(wg))
  8. }
  9. race.Disable()
  10. defer race.Enable()
  11. }
  12. state := atomic.AddUint64(statep, uint64(delta)<<32)
  13. v := int32(state >> 32)
  14. w := uint32(state)
  15. if race.Enabled && delta > 0 && v == int32(delta) {
  16. // The first increment must be synchronized with Wait.
  17. // Need to model this as a read, because there can be
  18. // several concurrent wg.counter transitions from 0.
  19. race.Read(unsafe.Pointer(semap))
  20. }
  21. if v < 0 {
  22. panic("sync: negative WaitGroup counter")
  23. }
  24. if w != 0 && delta > 0 && v == int32(delta) {
  25. panic("sync: WaitGroup misuse: Add called concurrently with Wait")
  26. }
  27. if v > 0 || w == 0 {
  28. return
  29. }
  30. // This goroutine has set counter to 0 when waiters > 0.
  31. // Now there can't be concurrent mutations of state:
  32. // - Adds must not happen concurrently with Wait,
  33. // - Wait does not increment waiters if it sees counter == 0.
  34. // Still do a cheap sanity check to detect WaitGroup misuse.
  35. if *statep != state {
  36. panic("sync: WaitGroup misuse: Add called concurrently with Wait")
  37. }
  38. // Reset waiters count to 0.
  39. *statep = 0
  40. for ; w != 0; w-- {
  41. runtime_Semrelease(semap, false)
  42. }
  43. }

Done()方法将WaitGroup计数器减1。

  1. func (wg *WaitGroup) Done() {
  2. wg.Add(-1)
  3. }

Wait()直到WaitGroup计数器为零。

  1. func (wg *WaitGroup) Wait() {
  2. statep, semap := wg.state()
  3. if race.Enabled {
  4. _ = *statep // trigger nil deref early
  5. race.Disable()
  6. }
  7. for {
  8. state := atomic.LoadUint64(statep)
  9. v := int32(state >> 32)
  10. w := uint32(state)
  11. if v == 0 {
  12. // Counter is 0, no need to wait.
  13. if race.Enabled {
  14. race.Enable()
  15. race.Acquire(unsafe.Pointer(wg))
  16. }
  17. return
  18. }
  19. // Increment waiters count.
  20. if atomic.CompareAndSwapUint64(statep, state, state+1) {
  21. if race.Enabled && w == 0 {
  22. // Wait must be synchronized with the first Add.
  23. // Need to model this is as a write to race with the read in Add.
  24. // As a consequence, can do the write only for the first waiter,
  25. // otherwise concurrent Waits will race with each other.
  26. race.Write(unsafe.Pointer(semap))
  27. }
  28. runtime_Semacquire(semap)
  29. if *statep != 0 {
  30. panic("sync: WaitGroup is reused before previous Wait has returned")
  31. }
  32. if race.Enabled {
  33. race.Enable()
  34. race.Acquire(unsafe.Pointer(wg))
  35. }
  36. return
  37. }
  38. }
  39. }

应用示例:

  1. func main(){
  2. var wg sync.WaitGroup
  3. for i:=0;i<5;i=i+1{
  4. wg.Add(1)
  5. go func(n int) {
  6. //defer wg.Done(),注意这个Done的位置,是另一个函数
  7. defer wg.Add(-1)
  8. EchoNumber(n)
  9. }(i)
  10. }
  11. wg.Wait()
  12. }
  13. func EchoNumber(i int){
  14. time.Sleep(time.Millisecond *2000)
  15. fmt.Println(i)
  16. }

运行:

  1. 3
  2. 4
  3. 2
  4. 0
  5. 1

这个应用示例很简单,是将每次循环的数量过3秒钟输出。那么,这个程序如果不用WaitGroup,那么将看不见输出结果。因为Goroutine还没执行完,主线程已经执行完毕。注释的defer wg.Done()和defer wg.Add(-1)作用一样。