Evert
Evert

Reputation: 99687

Sharing a slice across a few different goroutines

Given that I have an slice of structs of type User

Users := make([]User)

I am listening for TCP connections, and when a user connects, I'm adding an new user to this slice.

The way I've done this, is by setting up a NewUsers channel

NewUsers := make(chan User)

Upon new TCP connects, a User gets sent to this channel, and a central function waits for a User to arrive to add it to the Users slice.

But now I would like multiple subsystems (packages/functions) to use this list of Users. One function might simply want to receive a list of users, while a different function might want to broadcast messages to every user, or just users matching a certain condition.

How do multiple functions (which are possibly executed from different goroutines) safely access the list of users. I see two possible ways:

  1. Every subsystem that needs access to this list needs their own AddUser channel and maintain their own slice of users and something needs to broadcast new users to every one of these channels.
  2. Block access with a Mutex

Option 1 seems very convoluted and would generate a fair bit of duplication, but my understanding that Mutexes are best to be avoided if you try to stick to the "Share Memory By Communicating" mantra.

Upvotes: 1

Views: 2077

Answers (2)

Rick-777
Rick-777

Reputation: 10258

The idiomatic Go way to share data between concurrent activities is summed up in this:

Do not communicate by sharing memory; instead, share memory by communicating.

Andrew Gerrand blogged about this, for example.

It need not be overly-complex; you can think of designing internal microservices, expressed using goroutines with channels.

In your case, this probably means designing a service element to contain the master copy of the list of users.

The main advantages of the Go/CSP strategy are that

  • concurrency is a design choice, along with your other aspects of design
  • only local knowledge is needed to understand the concurrent behaviour: this arises because a goroutine can itself consist of internal goroutines, and this applies all the way down if needed. Understanding the external behaviour of the higher-level goroutines depends only on its interfaces, not on the hidden internals.

But...

There are times when a safely shared data structure (protected by mutexes) will be sufficient now and always. It might then be argued that the extra complexity of goroutines and channels is a non-requirement.

A safely shared list data structure is something you will find several people have provided as open-source APIs. (I have one myself - see the built-ins in runtemplate).

Upvotes: 3

OneOfOne
OneOfOne

Reputation: 99341

The mutex approach is the best, safest and most manageable approach to that problem and is the fastest.

Channels are complex beasts on the inside and are much slower than a rwmutex-guarded map/slice.

Upvotes: 2

Related Questions