Live data from Hacker News

Singleton Pattern in Go

marcio.io

41–50 of 76 posts

Re: Singleton Pattern in Go

#42
post #18

Earlier quoted context omitted.

Totally! I was going to post the same thing. Double-checked locking is either impossible or really hard to get right, depending on the language and architecture's guarantees! If you must use a singleton, I'd really recommend doing the so-called "aggressive" approach, which should have really been named the "actually won't crash sometimes" approach.

On C++ if we're using a bool as the "check" and pthread_mutex_lock on a regular mutex would that work, or do we still need to hardcode fences? (Asking because this pattern is used by libcxxabi code)

Yes, this is broken on certain architectures like PPC. Another core may see the "check" bool as set, but the fields of the protected object as uninitialized. One way to address this is to insert a write barrier just before setting the check bool, and a read barrier just after reading it.

Re: Singleton Pattern in Go

#43
post #18
post #4

Your "check-lock-check" code is probably broken (depending on the intricacies of Golang's memory model). If the compiler or CPU reorders any stores to the fields of "instance" after the assignment to "instance" itself, other threads could start working with a partially uninitialized object. Once() uses atomics on the fast path for a reason.

Totally! I was going to post the same thing. Double-checked locking is either impossible or really hard to get right, depending on the language and architecture's guarantees! If you must use a singleton, I'd really recommend doing the so-called "aggressive" approach, which should have really been named the "actually won't crash sometimes" approach.

+1 for the aggressive approach, mutexes are very fast in go.

I got curious and wrote a quick little benchmark test:

    $ cat bench_test.go
    package main
    
    import (
    	"sync"
    	"testing"
    )
    
    func BenchmarkMutex(b *testing.B) {
    	var m sync.Mutex
    	for n := 0; n 
A set of mutex.Lock() & .Unlock() calls takes only 24.0ns on average to complete.

Thus it's possible to lock/unlock more than 41 million times per second on the puny 2011 MacBook Air I used for this.

My .02c:

The post seems like a case of premature optimization.

Resource bottlenecks due to too much mutex locking in go does not seem like a case that will be commonly hit.

With infinite potential bottlenecks, I don't like to spend my time worrying and fussing over things that are:

A) Not yet a problem.

B) Unlikely to ever be a problem or give me grief.

Worrying about the overhead cost of locking falls squarely into just such a category.

Re: Singleton Pattern in Go

#45
post #21
post #2

It’s worth noting that not only do you need to synchronize access to the singleton, you need to synchronize access to the singleton’s state as well. And even if you manage that at a fine-grained layer, you’re still setting yourself up for all the problems associated with singletons: http://c2.com/cgi/wiki?SingletonsAreEvil . If you have a bunch of immutable state, then build unexported package variables in the packag…

Do you think it would be fine to use a singleton if the writes are all happening in a single thread and it's not critical that the reads be synchronized? Sharing state across go routines seems like something to be avoided. In vanilla Java it's always not easy to avoid sharing state.

You would have to use sync.Atomic for all reads and writes. If you just use normal reads and writes it's in violation of the Go language memory model. It might work for now or it might start a nuclear war. You never know.

Re: Singleton Pattern in Go

#46
post #21

Earlier quoted context omitted.

Do you think it would be fine to use a singleton if the writes are all happening in a single thread and it's not critical that the reads be synchronized? Sharing state across go routines seems like something to be avoided. In vanilla Java it's always not easy to avoid sharing state.

If you want your reads to ever work, then they need to be synchronized. Reading from an unfenced address during concurrent writes is undefined behavior for any CPU architecture you can think of, which means you’ll get stale reads _in a best-case scenario_. You can also get garbage reads (e.g. as your CPU interprets your read of a 64-bit pointer as two 32-bit reads), crashes, bees, etc. The code you write is either th…

There are exceptions to this rule--i.e. there are ways to not really be thread-safe but to have things work anyway--but they fall in the category of "you have to really, really know your CPU and be willing to write processor-specific code that just happens to work", so you can basically ignore them.

My favorite is the libdispatch abuse of cpuid to flood the pipeline on Intel CPUs for this problem: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-...

But really, take Coda's advice. If you aren't synchronizing your reads, you can basically just assume your code is broken.

Re: Singleton Pattern in Go

#47
post #18

Earlier quoted context omitted.

Totally! I was going to post the same thing. Double-checked locking is either impossible or really hard to get right, depending on the language and architecture's guarantees! If you must use a singleton, I'd really recommend doing the so-called "aggressive" approach, which should have really been named the "actually won't crash sometimes" approach.

+1 for the aggressive approach, mutexes are very fast in go. I got curious and wrote a quick little benchmark test: $ cat bench_test.go package main import ( "sync" "testing" ) func BenchmarkMutex(b *testing.B) { var m sync.Mutex for n := 0; n A set of mutex.Lock() & .Unlock() calls takes only 24.0ns on average to complete. Thus it's possible to lock/unlock more than 41 million times per second on the puny 2011 MacBo…

Here's the mutex source: https://golang.org/src/sync/mutex.go . The fast path is just a CAS, which of course is going to be fast. But it's also important to know how it performs under contention.

Re: Singleton Pattern in Go

#48
post #18

Earlier quoted context omitted.

Totally! I was going to post the same thing. Double-checked locking is either impossible or really hard to get right, depending on the language and architecture's guarantees! If you must use a singleton, I'd really recommend doing the so-called "aggressive" approach, which should have really been named the "actually won't crash sometimes" approach.

+1 for the aggressive approach, mutexes are very fast in go. I got curious and wrote a quick little benchmark test: $ cat bench_test.go package main import ( "sync" "testing" ) func BenchmarkMutex(b *testing.B) { var m sync.Mutex for n := 0; n A set of mutex.Lock() & .Unlock() calls takes only 24.0ns on average to complete. Thus it's possible to lock/unlock more than 41 million times per second on the puny 2011 MacBo…

Be careful with that benchmark. It's very vulnerable to SROA and constant propagation optimizing it away to nothing. (Doesn't look like Go's compiler optimizations are able to do that based on those numbers, but a modern optimizer will.)

Re: Singleton Pattern in Go

#49
post #18

Earlier quoted context omitted.

Totally! I was going to post the same thing. Double-checked locking is either impossible or really hard to get right, depending on the language and architecture's guarantees! If you must use a singleton, I'd really recommend doing the so-called "aggressive" approach, which should have really been named the "actually won't crash sometimes" approach.

+1 for the aggressive approach, mutexes are very fast in go. I got curious and wrote a quick little benchmark test: $ cat bench_test.go package main import ( "sync" "testing" ) func BenchmarkMutex(b *testing.B) { var m sync.Mutex for n := 0; n A set of mutex.Lock() & .Unlock() calls takes only 24.0ns on average to complete. Thus it's possible to lock/unlock more than 41 million times per second on the puny 2011 MacBo…

This has no contention and is essentially a no-op? Or do I not understand go?

Re: Singleton Pattern in Go

#50
post #15
post #6

I thought there was a lot of literature pretty convincingly arguing that singleton is in fact an anti-pattern.

There is, singletons are an anti-pattern, they're still interesting to discuss like this in terms of making them safe. In a language with both concurrency primitives and globals singletons have little (no) place anyway. I still learned from the post.

Shouldn't there be a disclaimer though?

Or, at least an example of when it would be a good idea to use a singleton over some other pattern?

Post reply on HN