Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GO - Pointer or Variable in WaitGroups reference

According the following function declarations from sync package:

Add -------> func (wg *WaitGroup) Add(delta int)

Done ------> func (wg *WaitGroup) Done()

Wait ------> func (wg *WaitGroup) Wait()

I understand that all 3 of them are called by a pointer to a WaitGroup, right?

If this is correct, I don't understand in the next pice of code, why Done function is called using a pointer variable, but Add and Wait functions are called using a variable (not a pointer):


package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {

    defer wg.Done()

    fmt.Printf("Worker %d starting\n", id)

    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {

    var wg sync.WaitGroup

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i, &wg)
    }

    wg.Wait()
}

Thanks a lot for your help.

like image 887
Kevin Flynn Avatar asked Oct 11 '25 15:10

Kevin Flynn


1 Answers

Done, Add and Wait are called on pointer. All functions refer to a pointer receiver *WaitGroup. The fact that you declare variable as value of WaitGroup doesn't mean much as all those methods will all access and modify the variable. The only problem happens when you want to pass your variable to worker - if you try to pass it as value you will make a copy and then Done will be referring to different pointer than Add and Wait - that's why you pass it's address with &.

I think here is best explanation I so far seen on the topic: https://github.com/golang/go/wiki/MethodSets#variables

In general, when you have a variable of a type, you can pretty much call whatever you want on it. When you combine the two rules above together, the following is valid:

type List []int

func (l List) Len() int        { return len(l) }
func (l *List) Append(val int) { *l = append(*l, val) }

func main() {
    // A bare value
    var lst List
    lst.Append(1)
    fmt.Printf("%v (len: %d)\n", lst, lst.Len())

    // A pointer value
    plst := new(List)
    plst.Append(2)
    fmt.Printf("%v (len: %d)\n", plst, plst.Len())
}

Note that both pointer and value methods can both be called on both pointer and non-pointer values. To understand why, let's examine the method sets of both types, directly from the spec:

List
- Len() int

*List
- Len() int
- Append(int) 

Notice that the method set for List does not actually contain Append(int) even though you can see from the above program that you can call the method without a problem. This is a result of the second spec section above. It implicitly translates the first line below into the second:

lst.Append(1)
(&lst).Append(1)

Now that the value before the dot is a *List, its method set includes Append, and the call is legal.

To make it easier to remember these rules, it may be helpful to simply consider the pointer- and value-receiver methods separately from the method set. It is legal to call a pointer-valued method on anything that is already a pointer or whose address can be taken (as is the case in the above example). It is legal to call a value method on anything which is a value or whose value can be dereferenced (as is the case with any pointer; this case is specified explicitly in the spec).

like image 187
Kamil Dziedzic Avatar answered Oct 15 '25 14:10

Kamil Dziedzic