1

I was wondering which data types in Go are inherently thread safe (if any).

My assumption going in is that ints, floats and bools are safe and composite types are not.

  • Are my assumptions correct?
  • Are there different considerations with respect to mutating a composite type vs replacing it?
  • How about channels?

Thank you.

six fingered man
  • 2,460
  • 12
  • 15
  • 1
    The Go memory model's "Advice" section is a little glib, but useful: https://tip.golang.org/ref/mem . Agree with synful that in practice loads/stores of types up to the machine's register size most likely won't be torn, but you don't want to unnecessarily make your code's correctness depend on attributes that happen to be true but you weren't promised. Play with channels/`sync`/`sync/atomic` and if questions come up in that we're here. – twotwotwo Nov 15 '14 at 20:15
  • 5
    Do not forget that compiler is free to change your program, if it can prove new program has same effect as per memory model definition. You might discover it will reorder some of your code or even remove some statements. – alex Nov 15 '14 at 20:30
  • Thanks twotwotwo and alex. – six fingered man Nov 15 '14 at 20:54

2 Answers2

5

I don't believe that any of them are guaranteed to be atomic, but it's possible that some are in practice (of course, by atomic we mean that assigning to them simultaneously from two threads will produce one or the other value, and not some third value (such as a combination of the bits from each value) - we don't mean that you can atomically compare and store or something like that). Your best bet is to check out the Go Memory Model.

joshlf
  • 21,822
  • 11
  • 69
  • 96
  • Yes, that's what I mean by atomic, though I'd extend it to one thread reading while another is writing. I'll check that link out. I'm surprised a little that numbers don't have that guarantee. – six fingered man Nov 15 '14 at 20:16
  • 1
    The issue is that those guarantees are very architecture-specific, and you'd effectively be ruling out certain architectures by making them. For example, if you said that 32-bit operations were atomic, then 16-bit processors could never be supported. In addition to that, it's frankly not behavior that you want to encourage. If you want thread-safe operations, that stuff is handled for you by the sync and sync/atomic packages. Concurrency is really hard to get right, and it's best to leave it up to the experts. – joshlf Nov 15 '14 at 20:23
  • (of course, if you're an expert, or want to toy around, feel free, but it's still better to keep that stuff out of the language spec) – joshlf Nov 15 '14 at 20:23
  • 1
    https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong – JimB Nov 16 '14 at 12:12
  • @JimB: Thanks for that link. Good info. – six fingered man Nov 19 '14 at 21:51
2

Just a note :

Mutating a single word value should generally be atomic if you run the code on the same platform you compiled it on. But there is more to it that makes the use of atomic instructions compulsory (if you don't plan to use stronger guarantees even).

Reason is that besides memory reorderings at the compiler level, you might have reorderings at the CPU level for optimizations. (writes do not propagate immediately to main memory, CPU have store buffers etc).

So in a multithreaded environment, you need to explicitly make the changes visible to all cores or otherwise you will corrupt memory.

As far as mutating a composite type is concerned, yes, you need to be (VERY) careful. Easiest way is to lock the whole object (embedding a mutex is a good way to do it, it is more cache friendly).

Otherwise, if you want to do it atomically, you could adopt a Read-Copy-Update methodology (look up for RCU or Copy-on-Write) but BEWARE !! Unless you really know what you are doing you could get into trouble very very easily. It's difficult to do that sort of thing when you have mutable objects nested into other mutable objects (lookup for issues regarding linearizability of nested lock-free data structures). It is really tricky and I discourage it. Even if you were to add an extra level of indirection so that your datastructures look immutable, solving the issue of concurrent atomic reads, updates and deletes is PhD level stuff. If you are curious though, lookup for Aleksandar Prokopec's Thesis and papers : http://axel22.github.io/home/professional/

That's the reason channels and mutexes are here for you. And I expect incremental performance improvement on the already nice performance the channels have. Now channels are easy for some, but it took a few days for me to really wrap my head around them. But once done, it's really simple to use them.

Taye
  • 797
  • 1
  • 10
  • 15
  • 1
    Thanks for the answer. The particular scenario that I had in mind was where the handler for an HTTP request modifies a `uint32` that it closes over. Because requests are handled concurrently, we could have two invocations of the handler operate on the data in parallel. This would be running on a 32 or 64 bit machine (never 16 bit) and I wouldn't need to guarantee that all operations succeed, only that the data is never corrupt. For example, if the handler merely increments a shared counter, if two increments happen at the same time, it would be fine if only one increment actually took place. – six fingered man Nov 19 '14 at 21:27
  • 1
    I see. Well since the handler is probably instantiated once and never deleted, I suspect that you could either use a mutex, or atomically increment it without too much worry. The reason is, you need the synchronization between your core caches still if you run with GOMAXPROCS>1 (but a channel would be overkill here indeed). It's just so that you don't have one goroutine trying to do 3++ while the other attempts 5++. – Taye Nov 19 '14 at 21:48
  • Thanks. Since it seems like I was being a little optimistic about its safety and my use may not be quite so simple in all cases, I'll probably just go ahead and use a mutex or will look at the `sync/atomic` package to see what it offers. Thanks again! – six fingered man Nov 19 '14 at 21:53