How to guard a struct from concurrent modification: placement of the mutex

520 views
Skip to first unread message

James Aguilar

unread,
Aug 21, 2015, 9:57:18 PM8/21/15
to golang-nuts
I have a struct with various fields. It needs to be protected for concurrent access. It looks like this:

type S struct {
 
SomeBigSlice []int
}

func
(s *S) Read(scanner io.RuneScanner) error {
 
// Read from scanner, then write an update to S. Called frequently.
}


I also intend to export the state of this struct through a debugging endpoint in the DefaultServerMux.

There are a few ways I could go about protecting a struct like this. In general, the approaches are mutex-in or mutex-out. 

Mutex Out

Mutex-out is simple: the structure is totally unguarded. The owner has to lock it before each operation. 

This complicates ReadOnce. You can't hold the mutex during the entire read, because then the http handler will be blocked if you are waiting for IO. So you need to do something like splitting the reader into two separate functions. One of these would read some sort of update object from the scanner. The other would, with the lock, update S.

It also complicates the http handler, as you have to pass the mutex-to-be-locked to the registration function as a parameter.

Mutex In

The mutex in approach has a private and a public subtype. In the public subtype, the mutex is an exposed field in the struct. The struct's receivers lock it before modifying the struct, and external code locks it before reading the struct.

In the private subtype, the receivers and other in-package elements can handle the issue the same as the public subtype. To the interface you add a Do() function, which gives you locked access to the struct. This seems to more or less be the approach of the expvar package.

There's another variant of the private subtype where members of the struct are only exposed via getters. This seems most like other languages, although, for my purposes, it would not be as good as iteration over slices is involved. So getting values index by index would be very slow under a pure-getter approach.

Advice

What is your advice for me? Which among these approaches is most Go-idiomatic? I'm leaning toward mutex-out at the moment, but this is a toy project for learning about Go, so doing what's most idiomatic is more important than which way I lean.

Tamás Gulácsi

unread,
Aug 22, 2015, 1:30:36 AM8/22/15
to golang-nuts
type S struct {
SomeBigSlice []int
sync.RWMutex
}

But do you really need to access the strict from the web while it's being updated? Using two instances and swapping them would be easier.

James Aguilar

unread,
Aug 22, 2015, 5:45:03 PM8/22/15
to golang-nuts
The object graph represented by the struct could be in the tens to hundreds of megabytes. Although not many of these bytes change during any given update, there may be dozens, or maybe hundreds of updates a second.  It's not wise to deepcopy the struct per update, and I'm not up for fine-grained locking.

Another person replied to me offlist and suggested the same embedding approach you mentioned. But when we searched the standard lib, it seemed like it was not used anywhere. The standard lib either uses private mutexes or doesn't guard the struct at all. So I'm going with the mutex-out approach mentioned in my initial email.
Reply all
Reply to author
Forward
0 new messages