Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”

This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money

In the previous article we used WaitGroup when we wrote coroutines

So the way we’re going to write it is something like this

func main(a) {
    ...dothing()

	wg := sync.WaitGroup{}
	// Control the declaration cycle of multiple subcoroutines
	wg.Add(xx)

	for i := 0; i < xx; i++ {
		go func(ctx context.Context) {
			deferwg.Done() ... dothing() }(ctx) } ... dothing()// Wait for all subcoroutines to close gracefully
	wg.Wait()
	fmt.Println("close server ")}Copy the code

As you can see, sync.waitGroup is mainly used to wait for a batch of coroutines to close. For example, the main coroutine above waits for all its children to close before exiting

So let’s explore the source code implementation of sync.waitGroup today

Explore source code implementation

The use of sync.waitgroup is given above by dMEO and looks simple to use

The Add function adds the number of coroutines to wait

Using the Done function informs WaitGroup that the current coroutine task is complete

Use the Wait function to Wait for all subcoroutines to close

I’ll open the source code

The source path is SRC /sync/waitgroup.go

Single test file SRC /sync/waitgroup_test.go line 301

Source file a total of 4 functions, 1 structure

  • type WaitGroup struct {
  • func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
  • func (wg *WaitGroup) Add(delta int) {
  • func (wg *WaitGroup) Done() {
  • func (wg *WaitGroup) Wait() {

Let’s take a look at what each of these functions does

type WaitGroup struct {

The WaitGroup waits for a set of Goroutines to complete, and the main Goroutine calls Add to set the waiting Goroutines

Each coroutine call is then run and called Done when finished

In the meantime, Wait can be used to block until all goroutines are complete

WaitGroup cannot be replicated after the first use

We can see that the WaitGroup structure has two members

  • noCopy

If we detect an assignment to WaitGroup in our program, then the program will report an error

  • state1

State1 is an array with three elements, each of which is 32 bits

On 64-bit systems, 64-bit atomic operations require 64-bit alignment

The high 32 bits correspond to the counter counter, which indicates the number of coroutines that have not yet completed a task

The lower 32 bits correspond to the number of waitgroup. Wait coroutines that have been called

The remaining 32 bits are the sema semaphore.

func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {

Keep looking at the source code

// state returns pointers to the state and sema fields stored within wg.state1.
func (wg *WaitGroup) state(a) (statep *uint64, semap *uint32) {
	if uintptr(unsafe.Pointer(&wg.state1))%8= =0 {
		return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]}else {
		return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]}}Copy the code

Here we can see that the state function returns a pointer to the state and sema fields stored in WG.state1

The implementation of the state() function is important here, and there are two cases

  • In the first case, under a 64-bit system, the pointer to the semA field returns &WG.state1 [2], indicating that for a 64-bit system, state1 data configuration is: counter, waiter, sema
  • In the second case, the pointer to the semA field returns &WG.state1 [0] below the 32-bit system, indicating that the state1 data layout is: sema, counter, waiter

Fat fish who are careful about why might have some ideas,

Why is the state1 array in the data structure arranged differently in different operating systems?

Let’s take a closer look at the above source code

64-bit system:

return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]

32-bit system

return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]

The main reason golang uses it this way is that Golang combines counter and waiter as one 64-bit piece of data, and therefore on different operating systems

Due to byte alignment, on a 64-bit system, the first two bits of 32-bit data add up to exactly 64 bits, which is exactly aligned

For 32-bit systems, semA is more suitable for the first 32-bit data, and the next two 32-bit data can be retrieved as a single 64-bit variable

The Add function increases the number of waiting coroutines by adding counter +delta:

We can see that the Add function, after retrieving the above 64-bit variables (counter and waiter) and sema semaphore via the state function, adds delta data to counter via the atomic.adduint64 function

Why is delta shifted 32 bits to the left here?

The state function returns a 64-bit variable with 32 bits higher than counter and 32 bits lower than waiter. The delta is added to counter, so it moves 32 bits to the left

func (wg *WaitGroup) Done() {

// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done(a) {
	wg.Add(- 1)}Copy the code

There’s nothing special about the Done function

func (wg *WaitGroup) Wait() {

The Wait function increases the number of waiters:

Block until the number of Couters in the WaitGroup becomes zero

Functions mainly through atomic.Com pareAndSwapUint64 CAS (compare and swap) way to manipulate the waiter.

It is obvious that this logic must be true in order to go to the inside implementation and run runtime_Semacquire(semap), or false in order to continue the loop again

Although the specific implementation of Waitgroup. Go is only 141 lines, we still need to study the specific details and learn the design principle. For example, the design idea of state1 structure member is very clever, there is no need to split it into three members and lock the value when operating. This is a good way to show performance

Slowly learn good ideas, day arch a pawn

Welcome to like, follow and favorites

Friends, your support and encouragement, I insist on sharing, improve the quality of the power

All right, that’s it for this time

Technology is open, our mentality, should be more open. Embrace change, live in the sun, and strive to move forward.

I am Nezha, welcome to like, see you next time ~