Ok so after getting away with using maps to share data between threads for 
a loooong time, I've finally seen the light and accepted that this is not 
safe.

In my case the threads in question are a painter thread and a 
mouse/keyboard event processing thread. They want to share data because 
obviously a mouse hitbox needs to match what is being drawn on the screen, 
unless you're going for a ux that drives your users insane :)

Anyway as I said it's not safe to concurrently read and write a map; 
eventually this will result in a panic. Yet I'm stuck with a codebase that 
does this all over the place. So despite hating the thought of blocking 
either thread I did what any honest java-for-dayjob coder would do and 
resigned myself to hiding the maps behind an RWLock.

Of course things are never that simple; adding locks to code that was not 
designed with them in mind is a good way to introduce deadlocks 
(particularly of the 
recursive-read-lock-while-other-thread-is-acquiring-write-lock variety). I 
made another change to address recursive lock acquisition, realised I 
missed a couple of shared maps and suddenly locking code was strewn all 
over my program; I got to the point of acquiring a read-lock every time the 
mouse moved before giving up in disgust. I thought about using an actual 
concurrent map, but giving up the builtin map's ease of use and 
well-typedness didn't thrill me. After some thought I came up with an 
alternative approach...

TLDR; My shared structures are *mostly* read-only, in the sense that read 
operations are much more common than writes. I decided I can afford to 
change the writes so instead of updating the maps in place, they (a) take a 
copy of the current map (b) update the copy and (c) replace the reference 
to the original map (held in a struct field) with the updated copy.

I was pretty chuffed with this approach. It's clearly not the most 
efficient but it allowed me to solve the panic without risking deadlock. 
I'd be surprised if I'm the first person to come up with it, but I haven't 
seen it mentioned - the majority of advice seems to be "use an RWLock" or 
"use a concurrent map".

I'm interested in understanding how safe/portable the approach is. I'm no 
expert on ARM/memory barriers but my understanding is that without an 
explicit sync/channel operation there's no guarantee another thread will 
see a change (in this case, the new map instance). In my case I have a 
channel based notification mechanism running alongside to wake up the 
painter thread when something changes, I'm just not sure what memory 
changes that is guaranteed to communicate.

I also just wanted to share the approach and see how other people feel 
about it. It reminds me a bit of a double-buffering - writing to the 
backbuffer and then swapping it into action.

-sqweek

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to