On Wed, Nov 11, 2009 at 08:47, Peter Thatcher <
ptha...@gmail.com> wrote:
> Let me say again that I love that Rob Pike is reviving CSP with Go.
> But I have another question: what do we do about mutable data
> structures being sent via channels? For example: [snip]
>
> So, what's the right way of avoiding passing mutable structs or maps
> in channels? I realize "don't do that" is an easy answer, but it's
> not very useful :).
It is more useful than it sounds. If you want to pass a value to many
goroutines, you can send the value (struct, int, etc) on a channel
carrying values, and if you want to pass a pointer, you pass it on
a channel carrying pointers. Both have their place: given
type Point struct {
X, Y int
}
it probably makes sense to use a chan Point instead of a chan *Point
and send values. On the other hand, the RPC code uses a chan *RPCMsg
to good effect: sending the pointer is understood (by the programmer)
to transfer ownership to another goroutine, so while there is a mutable
value being passed around, only one goroutine uses it at a time.
> A better example might be a shared cache in a map. Multiple
> coroutines would be updating the cache simultaneously, and thus
> problems of code interleaving could still be a major problem.
One option is to use a single goroutine to manage the map.
The other option is to use a lock (sync.Mutex). The right option
probably depends on exactly how large the operations are.
If you are managing a couple counters, it is cheaper and simpler
to use a lock than to use a goroutine managing the counters.
If you are managing a complex item like a connection to a remote
RPC server, it probably makes sense to let a single, easily understood
goroutine manage the server and have other goroutines communicate
over channels. The line in the middle is essentially uncharted:
we can't say precisely where it starts to make sense to use channels
instead of mutexes. That line will partly depend on how effiicent the
implementation of channels and scheduling gets. I do know this:
as soon as you would need to utter the words "condition variable",
you've crossed the line and should use a channel. ;-)
> type person struct {
> age int; //-1 means "unknown"
> }
>
> func grow(chan *people) {
> person := <- people;
> if person.age < 0 {
> *person.age++;
> }
> }
>
> func hide(chan *people) {
> person := <- people;
> *person.age = -1;
> }
>
> func main() {
> people := make(chan *person);
> go grow(people);
> go hide(people);
>
> bob := new(Person);
> bob.age = 30;
> people <- bob;
> }
>
> Admittedly, this is big contrived, but it easy to run into this in the
> real world: two goroutines mutating shared data. In this case, bob
> can end up being age 0. Is this possible?
No, but perhaps for a reason you weren't expecting.
Channels do not provide any kind of fan-out: each send
is matched to exactly one receiver. The send people <- bob
can cause bob to be received in grow or hide but not both.
So once all the goroutines have stopped, bob.age is either
still 30 (went to grow) or -1 (went to hide). The fact that
there's no fan-out makes channels well suited to passing
ownership around.
Russ
P. S. There is yet another reason too, completely irrelevant except
that it will bite you if you run this program: when main returns,
the program exits. If a program must wait for the other goroutines
to finish, it needs to do so explicitly. In long-running programs,
a common trick is to put <-make(chan int) at the end of main
and let another goroutine call os.Exit at an appropriate time.