Lazy Goroutine Locals

768 views
Skip to first unread message

Popog

unread,
Jul 29, 2012, 6:14:31 AM7/29/12
to golan...@googlegroups.com
From what I understand, it's possible to add goroutine locals by shoving them in the runtime.G struct. I was thinking of adding a map[uintptr]interface{} as well two functions to the runtime package:
func GetLocal(key interface{}) (value interface{}, ok bool)
func SetLocal(key interface{}, value interface{})

These functions serve to access the added map, with key being stripped to just the uintptr value for indexing.

With these it would be very simple to create lazy-initialized goroutine locals: http://pastebin.com/4etSzt1z

I wanted to get some feedback before I go off and attempt to implement this.

Rémy Oudompheng

unread,
Jul 29, 2012, 6:30:08 AM7/29/12
to Popog, golan...@googlegroups.com
Can you elaborate more on the purpose of this, and why you wouldn't
use regular variables to hold these values (so that a compiler can
eliminate any risk of mistyping and misspellings) ?

Rémy.

Popog

unread,
Jul 29, 2012, 6:48:23 AM7/29/12
to golan...@googlegroups.com, Popog
It's for when you need globals that are unique to each goroutines, similar to thread-local storage. Also it's lazy in order to keep goroutines lightweight.

Also there's no spelling involved, the key is not a string, it's a uintptr, which in my example is the address of the getter function.

Rémy Oudompheng

unread,
Jul 29, 2012, 7:06:05 AM7/29/12
to Popog, golan...@googlegroups.com
On 2012/7/29 Popog <pop...@gmail.com> wrote:
> It's for when you need globals that are unique to each goroutines, similar
> to thread-local storage. Also it's lazy in order to keep goroutines
> lightweight.

My question is more precisely: in which case do you need such a thing?
And again, why isn't a regular variable OK ?

> Also there's no spelling involved, the key is not a string, it's a uintptr,
> which in my example is the address of the getter function.

The issue with types remains. When you access such a thing from
various places in the code, it's easy for it to become inconsistent.
Same thing with the pointers. In you example you use &GetCounter but
why not GetCounter (which seems a lot more natural to me)?

It also means that calling a top-level package function may have
various effects depending on where you called it from, which is
currently not the case (thus increasing the amount of "spooky actions
at a distance" that occur).

Rémy.

Aram Hăvărneanu

unread,
Jul 29, 2012, 7:08:29 AM7/29/12
to Popog, golan...@googlegroups.com
No.

--
Aram Hăvărneanu

Jesse McNelis

unread,
Jul 29, 2012, 7:15:43 AM7/29/12
to Popog, golan...@googlegroups.com
On Sun, Jul 29, 2012 at 8:48 PM, Popog <pop...@gmail.com> wrote:
> It's for when you need globals that are unique to each goroutines, similar
> to thread-local storage. Also it's lazy in order to keep goroutines
> lightweight.

Thread local storage was a hack to make badly written libraries that
used mutable global state work in a multi-threaded environment.

Since Go doesn't have that legacy it's a really horrible idea to add
runtime support to encourage developers to write horrible libraries
that use mutable global state.

Just make a type to store the state and put methods on it to access that state.

--
=====================
http://jessta.id.au

Popog

unread,
Jul 29, 2012, 4:04:01 PM7/29/12
to golan...@googlegroups.com, Popog
On Sunday, July 29, 2012 4:06:05 AM UTC-7, Rémy Oudompheng wrote:
My question is more precisely: in which case do you need such a thing?
And again, why isn't a regular variable OK ?

I'm not writing a particular program or library that I intend to use this feature for, so I can't answer your question in specific. The best I can give you is that I want to save time writing code because globals are quick and easy, and adapting types to store state data can be really annoying and tedious.

The issue with types remains. When you access such a thing from
various places in the code, it's easy for it to become inconsistent.
Same thing with the pointers. In you example you use &GetCounter but
why not GetCounter (which seems a lot more natural to me)?

There is only the two access points inside GetCounter, everyone else calls GetCounter to get the counter. GetCounter is only using it's address as for a unique id. If you're still concerned about inconsistent access, store the key in a global.

It also means that calling a top-level package function may have
various effects depending on where you called it from, which is
currently not the case (thus increasing the amount of "spooky actions
at a distance" that occur).

Top level package functions can already use runtime.Callers to change their actions based on where they're called from.

Jim Whitehead II

unread,
Jul 29, 2012, 6:27:14 PM7/29/12
to Popog, golan...@googlegroups.com
A goroutine is the execution of a function or method call in a
independent, concurrent thread of control. Any variables that are
'local' to that function or method call are therefore, by definition,
goroutine-local storage. I really *really* have difficulty
understanding why you would re-invent that.

You can even define types that are local to a goroutine in order to carry state.

I'm just confused...

- Jim

Patrick Mylund Nielsen

unread,
Jul 29, 2012, 11:26:33 PM7/29/12
to Jim Whitehead II, Popog, golan...@googlegroups.com
I'm guessing what's asked for is e.g.

func foo() {
        a := bar()
        baz()
}

func baz() {
       qux(a)
}

IMO if you really want this, use closures, or make a struct and either attach methods to it, or pass around a reference to it.

Jim Whitehead II

unread,
Jul 30, 2012, 4:22:32 AM7/30/12
to Patrick Mylund Nielsen, Popog, golan...@googlegroups.com
On Mon, Jul 30, 2012 at 4:26 AM, Patrick Mylund Nielsen
<pat...@patrickmylund.com> wrote:
> I'm guessing what's asked for is e.g.
>
> func foo() {
> a := bar()
> baz()
> }
>
> func baz() {
> qux(a)
> }
>
> IMO if you really want this, use closures, or make a struct and either
> attach methods to it, or pass around a reference to it.

Ah, thanks for the (possible) clarification. I agree that if you need
something like this, it is much better to have an explicit state
parameter that can be passed around as necessary, at a bare minimum.

- Jim

Popog

unread,
Jul 30, 2012, 4:40:15 AM7/30/12
to golan...@googlegroups.com, Patrick Mylund Nielsen, Popog
Passing state data around works fine if you own all the code from the top of the stack down, but this is not always the case. Also, sometimes it can be tedious.

Imagine how annoying it would be if, in order to call Printf, you needed a context, and there was no global Stdin context. Main now has to make the context and pass it around to everyone who wants to use it. Suddenly some small helper class deep in the callstack has to call Printf? Better refactor all the classes above it to pass state data around so your small helper can use it.

Jim Whitehead II

unread,
Jul 30, 2012, 4:42:13 AM7/30/12
to Popog, golan...@googlegroups.com, Patrick Mylund Nielsen
On Mon, Jul 30, 2012 at 9:40 AM, Popog <pop...@gmail.com> wrote:
> Passing state data around works fine if you own all the code from the top of
> the stack down, but this is not always the case. Also, sometimes it can be
> tedious.
>
> Imagine how annoying it would be if, in order to call Printf, you needed a
> context, and there was no global Stdin context. Main now has to make the
> context and pass it around to everyone who wants to use it. Suddenly some
> small helper class deep in the callstack has to call Printf? Better refactor
> all the classes above it to pass state data around so your small helper can
> use it.

But the reasons these contexts exist in the first place is because of
what I consider to be a broken threading model. I find it really
difficult to envision a case where this would happen with actual Go
code.

- Jim

Popog

unread,
Jul 30, 2012, 5:05:06 AM7/30/12
to golan...@googlegroups.com, Popog, Patrick Mylund Nielsen
Wait, what? How is what essentially amounts to Fprintf indicative of a broken threading model? I wasn't saying those contexts should be stored in GLS, I was just giving an example of why passing around state data can sometimes be the wrong choice.

Jim Whitehead II

unread,
Jul 30, 2012, 5:10:59 AM7/30/12
to Popog, golan...@googlegroups.com, Patrick Mylund Nielsen
On Mon, Jul 30, 2012 at 10:05 AM, Popog <pop...@gmail.com> wrote:
> Wait, what? How is what essentially amounts to Fprintf indicative of a
> broken threading model? I wasn't saying those contexts should be stored in
> GLS, I was just giving an example of why passing around state data can
> sometimes be the wrong choice.

Sorry for the misunderstanding, I thought you were saying those
contexts should be stored in GLS.

- Jim

Patrick Mylund Nielsen

unread,
Jul 30, 2012, 8:27:25 AM7/30/12
to Popog, golan...@googlegroups.com
Actually this sounds pretty similar to the App Engine API, which is a good example of how to do this without "goroutine locals": https://developers.google.com/appengine/docs/go/reference#Context

Either that, closures, or actual globals.

Popog

unread,
Jul 30, 2012, 2:31:03 PM7/30/12
to golan...@googlegroups.com, Popog
I'm still of the opinion that explicit context passing and closures can be a lot more work than this option.

Also, the runtime package seems like the correct balance of allowing and not encouraging. I agree that having a special keyword makes it too easy for people who don't understand it to use in cases that don't warrant its use.

My biggest concern is getting the garbage collector to interact with the added map. Does anyone know where that code lives?

Rémy Oudompheng

unread,
Jul 30, 2012, 2:58:56 PM7/30/12
to Popog, golan...@googlegroups.com
On 2012/7/30 Popog <pop...@gmail.com> wrote:
> I'm still of the opinion that explicit context passing and closures can be a
> lot more work than this option.
>
> Also, the runtime package seems like the correct balance of allowing and not
> encouraging. I agree that having a special keyword makes it too easy for
> people who don't understand it to use in cases that don't warrant its use.

I still don't understand the covered use cases. I have written several
servers in Go over the past year and goroutine locals are essentially
useless in every single one of them. I expect that a mapping between
server requests and goroutines is the exception and not the standard
situation.

> My biggest concern is getting the garbage collector to interact with the
> added map. Does anyone know where that code lives?

The garbage collector interacts with everything that is allocated
using runtime·mallocgc()

Rémy.

Vanja Pejovic

unread,
Jul 30, 2012, 3:30:32 PM7/30/12
to Rémy Oudompheng, Popog, golan...@googlegroups.com
As someone who uses Guice (a pretty good dependency injection framework for Java) daily, I can say that I quite dislike thread locals.

They are a shackle and not a convenience. Sure, at first it seems like they let you add functionality without having to change or add parameters at every level. But as soon as you need to exit the one thread per request model, you end up having to write some very ugly code that makes the savings not worth it. You end up then having to jump through hoops to make sure the correct thread locals are copied, and other ones are not. Now you have multiple threads, each with their own copy of something that you actually might want them to share.

They are a source of confusion. They encourage action at a distance. Reading code which depends on a thread local can be awful. You have to figure out all the places in which the thread local can be set, and to what values, and then determine which one is used in each path through your application.

Ian Lance Taylor

unread,
Jul 30, 2012, 4:02:05 PM7/30/12
to Popog, golan...@googlegroups.com
On Mon, Jul 30, 2012 at 11:31 AM, Popog <pop...@gmail.com> wrote:
> I'm still of the opinion that explicit context passing and closures can be a
> lot more work than this option.

In Go it is very natural for one goroutine to start another. You then
have to decide whether to copy the goroutine-local variables or not.
What you wind up with is a form of dynamic scoping, in which the
connection between a name and its definition can be quite obscure.

When you need anything that complex, explicit context passing is
easier to understand.

Ian

Popog

unread,
Jul 30, 2012, 5:44:35 PM7/30/12
to golan...@googlegroups.com, Rémy Oudompheng, Popog
Even good features can be misused, globals themselves are prone to abuse by coders who are unfamiliar with their pitfalls. Things like instrumented profiling and manual memory management benefit nicely and cleanly from thread locals.

Han-Wen Nienhuys

unread,
Jul 30, 2012, 6:14:17 PM7/30/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng
On Mon, Jul 30, 2012 at 6:44 PM, Popog <pop...@gmail.com> wrote:
> Even good features can be misused, globals themselves are prone to abuse by
> coders who are unfamiliar with their pitfalls. Things like instrumented
> profiling and manual memory management benefit nicely and cleanly from
> thread locals.

Thread/goroutine local storage has been discussed various times in the
past. While you might be able to implement it, I give you zero chance
of getting it added to the language specification.

Also, I wouldn't exactly call globals a "good" feature. They are
easily and often abused.

--
Han-Wen Nienhuys
Google Engineering Belo Horizonte
han...@google.com

Andrew Gerrand

unread,
Jul 30, 2012, 6:30:53 PM7/30/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng
We wouldn't even be having this discussion if thread local storage wasn't useful. But every feature comes at a cost, and in my opinion the cost of thread locals far outweighs their benefits. They're just not a good fit for Go. 

Andrew 

Popog

unread,
Jul 30, 2012, 7:15:44 PM7/30/12
to golan...@googlegroups.com, Popog, Rémy Oudompheng
This just makes me think of exceptions in go. Go took exceptions and made them technically capable of doing try-catch if you work really hard at it, but it's not easy to do. So instead they are used to for truly panic worthy stuff. However, because try-catching-like behaviour is still useful, packages like "encoding/json" can use them to take some of the headache out of it's error handling.

As abuse is the primary concern with this feature, I suspect a similar mitigation is possible. To me, being in the runtime package was warning enough. It seems that everyone disagrees with me, so I'd love to hear some suggestions as to what the barrier to usage should be in order to limit abuse.

Andrew Gerrand

unread,
Jul 30, 2012, 7:35:45 PM7/30/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng
On 31 July 2012 09:15, Popog <pop...@gmail.com> wrote:
> This just makes me think of exceptions in go. Go took exceptions and made
> them technically capable of doing try-catch if you work really hard at it,
> but it's not easy to do. So instead they are used to for truly panic worthy
> stuff. However, because try-catching-like behaviour is still useful,
> packages like "encoding/json" can use them to take some of the headache out
> of it's error handling.
>
> As abuse is the primary concern with this feature, I suspect a similar
> mitigation is possible. To me, being in the runtime package was warning
> enough. It seems that everyone disagrees with me, so I'd love to hear some
> suggestions as to what the barrier to usage should be in order to limit
> abuse.

Goroutines are used very differently to threads. It is common for a
request to be handled by many goroutines, and frequently the goroutine
that finishes a request is not the one that first handled it. Knowing
the goroutine in which code runs is, 99% of the time, not useful as
context. If we introduced goroutine-local storage people would write
code assuming that it is, and that would be wrong.

It is just not going to happen.

Andrew

Popog

unread,
Jul 30, 2012, 8:39:46 PM7/30/12
to golan...@googlegroups.com, Popog, Rémy Oudompheng
The same can be said about callstacks, knowing the calling function is not useful 99% of the time and can be used to write really horrible code. Go might not be popular enough to have been abused in such a way, but it probably will be at some point.

Andrew Gerrand

unread,
Jul 30, 2012, 8:52:30 PM7/30/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng
On 31 July 2012 10:39, Popog <pop...@gmail.com> wrote:
> The same can be said about callstacks, knowing the calling function is not
> useful 99% of the time and can be used to write really horrible code. Go
> might not be popular enough to have been abused in such a way, but it
> probably will be at some point.

Sure, but the scope of this abuse is limited, because runtime.Callers
doesn't provide the values passed to function invocations. That
information, while present in a normal stack trace, is deliberately
unavailable to the runtime package.

I know, because I once tried to write something truly awful this way.
I'm glad that I couldn't.

Popog

unread,
Jul 30, 2012, 9:21:28 PM7/30/12
to golan...@googlegroups.com, Popog, Rémy Oudompheng
It still enables people to say "if caller == myFunc". It doesn't get much hackier than that.

Jesse McNelis

unread,
Jul 30, 2012, 9:26:00 PM7/30/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng
On Tue, Jul 31, 2012 at 11:21 AM, Popog <pop...@gmail.com> wrote:
> It still enables people to say "if caller == myFunc". It doesn't get much
> hackier than that.
>

Hacky but not generally lazy.
People love global variables because they allow them to be lazy and
not think about the flow of information through their program.
I see people using global variables all the time in horrible ways,
it's extremely rare that I see people writing code that depends on the
function that called it.

--
=====================
http://jessta.id.au

Popog

unread,
Jul 30, 2012, 11:48:57 PM7/30/12
to golan...@googlegroups.com, Popog, Rémy Oudompheng, uli.k...@gmail.com
Since when do goroutines have an id and how does runtime.Stack(buffer, true) retrieve it?

On Monday, July 30, 2012 5:48:07 PM UTC-7, uli.k...@gmail.com wrote:
On Tuesday, July 31, 2012 1:35:45 AM UTC+2, Andrew Gerrand wrote:

Goroutines are used very differently to threads. It is common for a
request to be handled by many goroutines, and frequently the goroutine
that finishes a request is not the one that first handled it. Knowing
the goroutine in which code runs is, 99% of the time, not useful as
context. If we introduced goroutine-local storage people would write
code assuming that it is, and that would be wrong.

It is just not going to happen.

I support this. One can always do:

package main

import (
        "fmt"
        "time"
)

type myThread struct {
        local string
}

func (t *myThread) Run() {
        fmt.Println("local:", t.local);
}

func main() {
        t := &myThread{"Hello!"}
        go t.Run();
        time.Sleep(1*time.Second);
}

The criticism seems to be that access to local requires the myThread as explicit method or function argument, but having two types of globals would make Go more complicated.

BTW if one really needs thread-local globals, one could misuse runtime.Stack(buffer, true) to extract the goroutine id to use it as a key for a local storage map.

Ian Lance Taylor

unread,
Jul 31, 2012, 12:54:49 AM7/31/12
to Popog, golan...@googlegroups.com, Rémy Oudompheng, uli.k...@gmail.com
On Mon, Jul 30, 2012 at 8:48 PM, Popog <pop...@gmail.com> wrote:
> Since when do goroutines have an id and how does runtime.Stack(buffer, true)
> retrieve it?

goroutines have an internal ID, and that is what runtime.Stack prints.
It's the goid field of the G struct.

Ian
Reply all
Reply to author
Forward
0 new messages