I'm sure this has been suggested before, but Goroutine thread affinity is a feature that, while not strictly necessary, I think could improve the language. There are some C libraries that get very... cross when you try to access them from multiple threads. OpenGL, for instance, wants you to manage a single context per thread.
Now don't get me wrong, you can *manage* this now, but it can get rather kludgy. You have to lock the thread, create the context, never return from that goroutine or unlock the thread, and make absolutely sure that any goroutines you spawn from that goroutine don't touch the context and instead inform the main goroutine about what to change. Again, manageable (and because of channels, I'd argue it can be in some ways even easier than in C), but it can be difficult to work with if you really want to make use of concurrency.
My idea is an optional argument to the "go" keyword
go func, uint
If the second argument is passed in, the runtime will use it as an "id". The effects of doing this would be:
- On the first use of a given ID, the runtime will select a thread. That thread is the only thread goroutines with that affinity ID may be run on. That is, if I call "go Myfunc(), 1", then any other calls of the form "go <func>, 1" will be guaranteed to run on the same thread Myfunc() was run on -- regardless of whether or not Myfunc() has returned yet.
- However, goroutines with that affinity do not OWN (or "lock") said thread. Other goroutines may be run on that thread as well. Goroutines with a certain thread affinity may get precedence, though.
- If multiple IDs are in use, the runtime will attempt to spread them evenly among available threads. That is, for GOMAXPROCS=3, the first three IDs will be assigned to separate threads. If you add three more IDs, then each thread will have 2 IDs assigned to it, and so on.
If no ID is given (or an ID of 0?) then that goroutine may run on any thread, acting according to the current rules.
Some problems:
What if a Goroutine locks a thread that other goroutines have an affinity towards?
In my opinion, this simply means that no other goroutine with an affinity bound to that thread may run until the thread is unlocked or the goroutine returns. This does cause a potential trap, though: say you have two goroutines with different affinities, but bound to the same thread (because of the value of GOMAXPROCS and sheer bad luck). The user may erroneously assume that since they have different affinities, they must be on separate threads and thus it's okay to communicate via unbuffered channel between them. This will cause a deadlock. It is a problem, but in my opinion it's not too bad of a side effect (after all, I think you can currently make this happen if you use LockOSThread with GOMAXPROCS=1, and it's mostly just an extension of that problem). Explicit affinity would be sort of an "advanced feature" to begin with, so I think expecting users to be aware of these pitfalls is fair.
It could potentially be solved with some logic in the runtime that abdicates a locked thread to other goroutines with a different affinity ID if it's waiting on a channel, but that may be undesirable for various reasons.
What if you increase GOMAXPROCS after having used multiple affinity IDs? Will they get redistributed among the new threads?
No. Maybe I'm just colored by the problem I'm having, but it would be too dangerous to assume that any given ID can be sent to a different thread without consequences. However, further created IDs will be put on the new threads until they're even with the old ones.
What if you decrease GOMAXPROCS to an amount that would force the IDs to redistribute?
In my opinion, this should be a panic -- or at the very least the runtime should refuse to do it. Perhaps a function in the runtime package (runtime.RemoveAffinity(id uint) or something) could be available if the user is dead set on doing this.
What if your goroutine only needs to be on a certain thread sometimes but not others?
In my opinion, if you're trying to do that you're probably being overly fancy, and there are any number of workarounds that could suffice. But if this is deemed a "necessary" feature perhaps a function along the lines of runtime.SetCurrentGoroutineAffinity(id uint) could be made available.
I think this would also allow for people who really know what they're doing to request explicit parallelism (though extra features may be needed to really ensure that two different affinity IDs are not on the same thread as opposed to simply ensuring ones with the same ID do). Maybe in the future when "the scheduler improves" and GOMAXPROCS is removed the number of explicit thread affinities in the program can be used as a hint to runtime for how many threads to use.
The thing I like about the idea is that unless you're playing around with LockOSThread or GOMAXPROCS it's very conceptually simple to the user. Need all this stuff on the same thread for some reason? Make sure you put the same number when you call go. That's it. No messing around with LockOSThread and kludging your program to never exit that GoRoutine lest the next function call be assigned to a different thread. Hell, you don't even have to worry about what GOMAXPROCS is -- it will make sure every go call with the ID argument "1" runs on the same thread whether you have one thread or a billion running.
Thoughts? I know that if this does happen it's a very long time away since it would mess with the fundamental way the language works. But I figured bringing it up for discussion wouldn't hurt. Any big problems with it I didn't anticipate?