Golang WaitGroup source code analysis

The analysis of sync.WaitGroup of Golang 1.9 is basically the same as that of Golang 1.10 except that it has been panicchanged throw. Source code location: sync\waitgroup.go.

structure

type WaitGroup struct {
	noCopy noCopy  // noCopy可以嵌入到结构中,在第一次使用后不可复制,使用go vet作为检测使用
	// 位值:高32位是计数器,低32位是goroution等待计数。
	// 64位的原子操作需要64位的对齐,但是32位。编译器不能确保它,所以分配了12个byte对齐的8个byte作为状态。
	state1 [12]byte // byte=uint8范围:0~255,只取前8个元素。转为2进制:0000 0000,0000 0000... ...0000 0000
	sema   uint32   // 信号量,用于唤醒goroution
}

I don't know if you are like me, whether you use Java's CountDownLatch or Golang's WaitGroup, you will have questions, can you install multiple threads|coroutines to wait? After reading the source code, you can answer it, you can install it

1111 1111 1111 ... 1111
\________32___________/

2^32 is so spicy! So you don't need to worry about being overwhelmed in a stand-alone situation.

function

The following code has removed the race code that is not related to the core code.

Add

Add or decrease the number of waiting goroutines.

The parameter delta may be negative and added to the WaitGroup counter, the following results may appear

  • If the counter becomes zero, all blocked goroutines are freed.
  • If the counter becomes negative, increase the panic.
func (wg *WaitGroup) Add(delta int) {
    // 获取到wg.state1数组中元素组成的二进制对应的十进制的值
	statep := wg.state()
	// 高32位是计数器
	state := atomic.AddUint64(statep, uint64(delta)<<32)
	// 获取计数器
	v := int32(state >> 32)
	w := uint32(state)
	// 计数器为负数,报panic
	if v < 0 {
		panic("sync: negative WaitGroup counter")
	}
	// 添加与等待并发调用,报panic
	if w != 0 && delta > 0 && v == int32(delta) {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	// 计数器添加成功
	if v > 0 || w == 0 {
		return
	}

	// 当等待计数器> 0时,而goroutine设置为0。
	// 此时不可能有同时发生的状态突变:
	// - 增加不能与等待同时发生,
	// - 如果计数器counter == 0,不再增加等待计数器
	if *statep != state {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	// Reset waiters count to 0.
	*statep = 0
	for ; w != 0; w-- {
		// 目的是作为一个简单的wakeup原语,以供同步使用。true为唤醒排在等待队列的第一个goroutine
		runtime_Semrelease(&wg.sema, false)
	}
}

// unsafe.Pointer其实就是类似C的void *,在golang中是用于各种指针相互转换的桥梁。
// uintptr是golang的内置类型,是能存储指针的整型,uintptr的底层类型是int,它和unsafe.Pointer可相互转换。
// uintptr和unsafe.Pointer的区别就是:unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算;
// 而uintptr是用于指针运算的,GC 不把 uintptr 当指针,也就是说 uintptr 无法持有对象,uintptr类型的目标会被回收。
// state()函数可以获取到wg.state1数组中元素组成的二进制对应的十进制的值
func (wg *WaitGroup) state() *uint64 {
	if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
		return (*uint64)(unsafe.Pointer(&wg.state1))
	} else {
		return (*uint64)(unsafe.Pointer(&wg.state1[4]))
	}
}

Done

Equivalent to Add(-1).

func (wg *WaitGroup) Done() {
    // 计数器减一
	wg.Add(-1)
}

Wait

Execute blocking until all WaitGroup counts become 0.

func (wg *WaitGroup) Wait() {
	// 获取到wg.state1数组中元素组成的二进制对应的十进制的值
	statep := wg.state()
	// cas算法
	for {
		state := atomic.LoadUint64(statep)
		// 高32位是计数器
		v := int32(state >> 32)
		w := uint32(state)
		// 计数器为0,结束等待
		if v == 0 {
			// Counter is 0, no need to wait.
			return
		}
		// 增加等待goroution计数,对低32位加1,不需要移位
		if atomic.CompareAndSwapUint64(statep, state, state+1) {
			// 目的是作为一个简单的sleep原语,以供同步使用
			runtime_Semacquire(&wg.sema)
			if *statep != 0 {
				panic("sync: WaitGroup is reused before previous Wait has returned")
			}
			return
		}
	}
}

Precautions for use

  1. WaitGroup cannot guarantee the execution order of multiple goroutines
  2. WaitGroup cannot specify a fixed number of goroutines

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324522324&siteId=291194637