goroutine private / local var/const

136 views
Skip to first unread message

andreas graeper

unread,
Jul 19, 2022, 12:14:14 PM7/19/22
to golang-nuts
hi,

for i=0;i<10;i++ { go func () { /* const c=i OR var v=i */
 fmt.Println("f:beg i=",i) // here c or v instead
// action
 fmt.Println("f:end i=",i) // here c or v instead
}}
when this routines get interrupted then beg-i and end-i differ
now i want at the beginning of the routine a const copy `const c int = i`
but its not allowed and `var v int = i` does not work, too.

does an instance of a go-routine has no own stack-frame like a normal function (c i.e) and therein local/private variables ? 

thanks in advance

peterGo

unread,
Jul 19, 2022, 12:42:21 PM7/19/22
to golang-nuts
Are you looking for somthing like this?


peter

Konstantin Khomoutov

unread,
Jul 20, 2022, 6:01:38 AM7/20/22
to andreas graeper, golang-nuts
This?

https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables

In short:

- Your goroutines run so-called anonymous functions - those defined "inline"
and having no name, some folks call them "lambdas".

Anonymous functions works like "closures": they "close over" any variable
accessed in such a function but declared in any of the outer scopes
accessible to the function.

- A closure captures its external variables via reference - that is, if you
create multiple closures in the same lexical scope, they all will reference
the same variables. To say that in other words, they do not receive copies
of such variables; they reference the original variables.

- In your code snippet you start 10 goroutines passing each a new closure,
each referencing the same loop variable, i.

Hence, in your example you create the condition for a data race of 10
goroutines over the same variable i. The behaviour of this code is hence
undefined. If you were to `go build -race` it (or `go run -race`, FWIW),
the runtime would crash your running program.


Conceptually two solutions are possible:

- Make your anonymous functions not close over that shared variable.

This can be done by defining one or more arguments in the function's
definition and pass the values the function has to operate on via that
parameters when calling it. Since passing of parameters is done via
copying them, each anonymous function will have their own copies.

In your simple case you could do what PeterGo proposed - something like:

go func (int i) { ... }(i)

If the set of variables to operate is huge so as being impractical to
be passed via arguments, you usually either define a helper `struct`
type containing all the required stuff and pass it as a single parameter
or stop using anonymous functions altogether and define a custom type
with a method, and to run a gorotine, you construct an instance of this
type and then call the goroutine to run that method on that instance.

(To recap: do not be attached to passing anonymous functions to goroutines:
it indeed is a cool feature, often useful, but it is so widely demonstrated
by various guides and blog posts that some novice Go programmers do not
even understand that you can pass anything "callable" to the `go`
statement, not only an anonymous function. Yes, I've witnessed people with
this mind wart in interviews at my $dayjob.)

- Create a copy of the loop iteration variable and capture _it_ in the
anonymous function - like this:

for i := 0, i < 10; i++ {
i := i
go func() { fmt.Println(i) }()
}

This "trick" works because in Go, all loop statements have two scopes: the
outer scope is where the loop iteration variables live, and the inner one
is the loop's body; hence in the presented example we create a variable
named "i" in the inner scope and initialize it with the value currently
held in the variable named "i" from the outer scope. The "inner" "i" is
different on each iteration - or, to say that in other words, - is
recreated anew on each iteration, so it's safe to capture it in a goroutine
started on that iteration - provided, of course, that it's the sole
goroutine capturing that variable and started on that iteration.

What approach to pick depends on the use case, personal aesthetics and
whatever the person(s) doing code review on your team prefer.

Konstantin Khomoutov

unread,
Jul 20, 2022, 7:09:03 AM7/20/22
to andreas graeper, golang-nuts
On Wed, Jul 20, 2022 at 01:00:59PM +0300, Konstantin Khomoutov wrote:

> > for i=0;i<10;i++ { go func () { /* const c=i OR var v=i */
> > fmt.Println("f:beg i=",i) // here c or v instead
> > // action
> > fmt.Println("f:end i=",i) // here c or v instead
> > }}

[...]

> - Make your anonymous functions not close over that shared variable.
>
> This can be done by defining one or more arguments in the function's
> definition and pass the values the function has to operate on via that
> parameters when calling it. Since passing of parameters is done via
> copying them, each anonymous function will have their own copies.
>
> In your simple case you could do what PeterGo proposed - something like:
>
> go func (int i) { ... }(i)

[...]

While we are at it, pay attention to the fact that variable assignments and
passing parameters to functions always copy values, some types contain
pointers - addresses of other variables - and hence copying of them copies
those pointers, not the memory they point at. Sometimes it is said that such
types have reference semantics.

Examples of such types are slices and maps. For instance, if in our toy
example we'd make our anonymous function receive a slice variable and pass
it a slice value when calling it to execute in a separate goroutine, the
slice itself would be copied but the memory of its backing array would not -
only the address of that memory block. Hence if we were to start multiple
goroutines this way, and at least one of them would attempt to mutate the
slice, that, again, could produce a data race (and generally weird behavior -
if some goroutine were to append to the slice, and it was required to grow).

To recap, when pondering how to make your goroutines not step on each other's
memory toes, you have to consider how the types the variables of which these
goroutines intend to manipulate, are defined, and which properties they have.

Reply all
Reply to author
Forward
0 new messages