7 ms·
Singleton Pattern in Go
- codahale 11y agoIt’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 http://c2.com/cgi/wiki?SingletonsAreEvil. If you have a bunch of immutable state, then build unexported package variables in the package’s `init` func and export funcs which use those variables. If you have a bunch of mutable state, then don’t use a singleton.
- lemevi 11y agoDo 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.
- codahale 11y agoIf 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 thread-safe, used in a single-threaded context, or a pinless grenade.
- pcwalton 11y agoThere 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-of-dispatch_once.html 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.
- twic 11y agoAn interesting exception is Lamport's Bakery: https://en.m.wikipedia.org/wiki/Lamport%27s_bakery_algorithm https://en.m.wikipedia.org/wiki/Lamport%27s_bakery_algorithm Thread safety without synchronisation primitives. I'm not sure it's ever actually a good idea to use it, though. EDIT: unless you count a fence as a synchronisation primitive.
- deleted 11y ago[deleted]
- doomrobo 11y agoIf you're only reading/writing from one thread, then locking/unlocking a mutex should have an extremely small overhead. So if you plan on having such a pattern available for use in multithreaded contexts, a mutex or some other thread-safe abstraction couldn't hurt.
- eloff 11y agoYou 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.
- tptacek 11y agoThis is cute, but I think (someone will correct me if I'm wrong) that the real idiom is: things that would be singletons in Java are instead servers managing a channel in Golang.
- ffk 11y agoWhy incur the cost and complexity of a channel if what you are doing doesn't require it? Go's sync.Once just implements the check-lock-check pattern. It doesn't use a channel in the background. In fact, it is better to use sync.Once to reduce the risk getting the semantics of check-lock-check incorrect. :) [edit: It occurred to me that the parent may be speaking about architecture and not about the singleton's design. From an architectural perspective, there are much better patterns in golang than a singleton.]
- tptacek 11y agoThe architecture! Not the design of this singleton.
- jamra 11y agoThe beauty of Go's channel design approach is how it isolates responsibilities. It's not just about synchronization. A great example is Rob Pike's lexer. When passing singletons around, you are communicating with shared state. Sometimes, there is no way around it.
- pcwalton 11y agoYour "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.
- skrap 11y agoTotally! 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.
- Manishearth 11y agoOn 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)
- millstone 11y agoYes, 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.
- maxlybbert 11y agoA few years ago, the C++ standard didn't guarantee that the double-checked idiom would work. Even if your threading library had some way to insert the necessary memory barriers, it was possible that your compiler would optimize important things away. C++11 changes that. The language now has standard ways to add memory barriers, and compiler optimizations can't remove them.
- 11y ago
- pstuart 11y agoThis seems more idiomatic: func init() { instance = &singleton{} }
- Jabbles 11y agoIf instance can truly be initialized like that, then there's no need for the init() function, just do it at the top level: var instance = &singleton{} In fact, a lot of idiomatic Go will ignore the Get() method, exporting the instance itself: var Instance = &singleton{} Clearly if you overwrite it, bad things will happen. Don't do that. There may possibly be a problem with unnecessarily slowing startup times in the case you don't need the singleton and initialization is more complicated than a malloc - in which case, profiling will tell you and you can revert to the method in the blog post. Remember that Go's strict import/dependency requirements mean you're unlikely to import the package (and trigger the initialization) unless you actually use the singleton. It is non-idiomatic to use the sync.atomic package.
- pcwalton 11y ago> There may possibly be a problem with unnecessarily slowing startup times in the case you don't need the singleton - in which case, profiling will tell you and you can revert to the method in the blog post. Not just in that case. If you lazily initialize then you can get other things done before—or while—you're waiting for the singleton constructor to run. Global constructors are suboptimal for performance almost as a rule.
- jmount 11y agoI thought there was a lot of literature pretty convincingly arguing that singleton is in fact an anti-pattern.
- inglor 11y agoThere 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.
- jsprogrammer 11y agoShouldn'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?
- incepted 11y agoSingletons are useful and wide spread concepts. What is an anti pattern is implementing them in a way that's not thread safe (which is made worse if your singleton is mutable). Letting DI frameworks handle your singletons is the best way to get the best of both worlds.
- inglor 11y agoNo no, singletons are pathological liars http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/ http://misko.hevery.com/2008/08/17/singletons-are-pathologic... Atomic mutable global state is still mutable global state. DI containers mean you don't need a singleton but can just create a single instance which lets you scope it and does not mix lifetime duration and object properties.
- incepted 11y ago> No no, singletons are pathological liars http://misko.hevery.com/2008/08/17/singletons-are-pathologic.. http://misko.hevery.com/2008/08/17/singletons-are-pathologic.... No, only badly implemented ones are. Besides, this article is seven years old and it was written way before DI with Guice or Dagger became a thing. It's a very outdated perspective (and you will notice that AngularJS, which Misko contributes to, supports both DI and singletons).
- mostafah 11y agoOh no.
- hendzen 11y agoThis post is chock-full of antipatterns. Both the singleton pattern and double checked locking should be avoided.
- hesdeadjim 11y agoYea it's easy enough to use dependency injection to satisfy these supposedly "global" requirements. About the only global state I can tolerate is read-only constants, but even then you are much better off with DI just because of testing.
- bkeroack 11y agoDI is an antipattern.
- vhost- 11y agoHow do handle configuration? Your program starts up, generates some settings, then those need to be shared across all threads to avoid incurring the cost of regenerating during every call to a function.
- ajankovic 11y agoBut in that case config values are to be treated as constants (read only). You don't have concurrency issues for code that doesn't change.
- TheMagicHorsey 11y agoWouldn't it make more sense to have some sort of manager that receives information on an incoming channel, manages the state internally, and reports out on an outgoing channel?
- jerf 11y agoAbstractly, if you somehow need a singleton object, this will be substantially faster if you need it frequently. And if you're careful to do all the other things you need to do in order to make this work and safe, as seen in the other messages in this post. Practically, all the examples I've ever of why you'd ever want a singleton are indeed better done as a goroutine server over channels. Within the object you get an implicit lock by virtue of being the only goroutine ever to touch that stuff, and as long as you don't need this object to use more than one CPU total and the overhead of channels isn't a big deal, which again, describes the vast bulk of cases I've ever heard of that call for singletons, it's a better way to go. And I'd note I've written a couple dozen different goroutine server things and a grand total of 0 'singletons', so... yeah. If I had an immutable singleton object that was somehow very expensive to initialize, I might consider this approach. Not sure when that would come up but I'm sure it describes something.
- inglor 11y agoA singleton is basically a global variable and those aren't really important to "shim" in Go. Object method calls are pretty much the same conceptually as message passing but when you actually need to synchronize as you point out goroutines and channels make things much simpler. I think what this post was going for wasn't "use singletons", it was "look at these interesting threading issues that arise and how we address them in (non idiomatic) Go"
- pcwalton 11y agoThere's more that needs to be done if the goal is "look at these interesting threading issues that arise and how we address them in (non idiomatic) Go", because the initialization described in the post is broken. Shared-memory multithreading is hard. Golang does not do much to prevent you from shooting yourself in the foot here, as this post proves. So use the abstractions wherever possible. In this case, Once correctly performs the atomics, and naive double-checked locking doesn't.
- dcsommer 11y agoThere's a package for this, if anyone needs this pattern off the shelf: https://github.com/dropbox/godropbox/tree/master/singleton https://github.com/dropbox/godropbox/tree/master/singleton
- strictfp 11y agoCoding Pro Tip: Instead of using the Singleton pattern, make one instance of your class at the start of your program and pass it around to its users. Singletons are really just as bad as global variables. Why? Because they are global variables.
- coldtea 11y ago>Singletons are really just as bad as global variables. Why? Because they are global variables. And just as handy as global variables.
- aikah 11y ago> And just as handy as global variables. which means not really handy in a non-thread safe context.
- nadams 11y agoDepending on what you are trying to implement - passing an extra variable everywhere is just creating noise (because potentially you have to have an extra parameter to EVERY function or method). I believe in KISS - everything should be as simple as possible but not simpler. Believe it or not there are valid uses for a Singleton which, many would agree, include a simple logging class [1]. One could argue that you could just create it in the section of code you want to log - but that just creates noise and you end up repeating code. logger = Log() # run code to open the file to the last position logger.log("test") vs Log.instance().log("test") Now imagine this was a multi-threaded application - singleton arguments are much different. > Singletons are really just as bad as global variables. Why? Because they are global variables. Everyone has their own opinions - but you can't make sweeping generalizations. I'm not saying a global variable is appropriate in every situation - but every language, and project, is different. There are even different dialects of C++ [2]. [1] - http://stackoverflow.com/questions/228164/on-design-patterns-when-to-use-the-singleton http://stackoverflow.com/questions/228164/on-design-patterns... [2] - http://www.reddit.com/r/programming/comments/197dn1/introduction_to_c_a_series_of_46_videos_created/c8lks22 http://www.reddit.com/r/programming/comments/197dn1/introduc...
- vortico 11y agoWhat's the point of a singleton when you can just have variables in an anonymous struct at root level, and functions defined at root as well? var server struct { host string port string } func serverStart() { server.host = "..." server.port = "..." }
- Amiga64 11y agoIsn't Singleton's really an anti-pattern? SO discussions here: http://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons http://stackoverflow.com/questions/137975/what-is-so-bad-abo...
- amelius 11y agoWhy not use an atomic compare-and-swap instruction as offered by all of the major underlying hardware architectures?
- deleted 11y ago[deleted]
- plorkyeran 11y agoC++11 did make it possible to write portable correct double-checked locking, but it's still very easy to get it wrong (unless you can get away with just using std::call_once).
- colin_mccabe 11y agoIt's funny how you can take an obvious antipattern (global variables) and turn it into a pattern by giving it a cool new name like "Singleton". In this spirit, I propose "the ProgramCounterAmbulator" as a cool new name for "goto." Actually, gotos are usually less harmful than globals. At least they don't interfere with unit testing the way globals tend to.
- penrod 11y agoAny discussion of Singletons is incomplete without https://sites.google.com/site/steveyegge2/singleton-considered-stupid https://sites.google.com/site/steveyegge2/singleton-consider...
- ignoramous 11y agoNo argument that Singletons make it difficult to unit test things but there are genuine cases where singletons are required: For instance, you don't want to end up creating multiple objects that read the configuration file, when one is enough. Or you certainly don't want to create too many objs that are heavy (like a cache that stores data heavy objects, or services, or god-objects (which are themselves an anti-pattern unavoidable in certain cases)). At times, there's genuinely only a single entity of "x" that's available for use by the environment, like a security-policy, or access to standard-output, and so on... Singletons are necessary evil, IMO. Re: Global state: At some point the abstraction will have to leak. If you squint enough, nothing is truly isolated, and everything's sharing everything else with other binaries on any given system at some abstraction level or the other. This is more often the reason why security-exploits are theoretically possible despite isolation.
- thomasahle 11y agoI find that a better approach is to try and isolate pieces of code that needs access to specific resources, such as configuration files or standard output. Performing IO tasks, from every module in a code base, is an easy way to create very unmaintainable code. Of course this is all a tradeoff with other code structuring ideals. Programming languages with Monads tend to be very good at isolating such things.
- Locke1689 11y agoTwo things: 1) Unless you've explicitly checked that Go's memory model prevents read/write moves to allow double-checked locking, don't do it. 2) The pattern is usually a CAS after the check.