I came across this now archived project that implements a set data structure in Go and was intrigued by the implementation of both thread-safe and non-thread-safe implementations of the same data structure. Recently I’ve been attempting to get rid of locks in my code in favor of one master data structure that does all of the synchronization, having multiple options for thread safety is useful. Previously I did this by having a lower-case method name (a private method) that was non-thread-safe and an upper-case method name (public) that did implement thread-safety. However, as I’ve started to reorganize my packages this no longer works.
The way that the Set implementation works is that it defines a base data structure that is private,
set, as well as an interface (
set.Interface) that describes the methods a
set is expected to have. The
set methods are all private, then two data structures are composed that embed the
SetNonTS — the thread and non-thread safe versions of
set. In this snippet I’ll just show a bit of boiler plate code that does this for reference, see the full set implementation for more detail.
In the implementation above, the
set object provides four internal methods:
init() creates the internal map data structure,
add updates the map with one or more items,
remove deletes one or more items from the map, and
contains does a simple check to see if the item is in the internal map. All of these methods are private to the
Set methods embed the
set object and add some additional functionality. Both implement a constructor,
New respectively, which call the internal
init functions. Both also implement
Remove, which silently exit if no items are added, the difference being that
Set write locks the data structure after performing that check.
Contains is also implemented, which the
Set data structure read locks before checking.
The only small problem with this implementation is that there is a little bit of code duplication (e.g. the checks for non items in the
Remove methods). However, I’ve noticed in my code that often there are tasks that are done in either thread-safe or non-thread safe versions but not both (like marking a flag or sending data to a channel). Because of this, it’s often better to keep those methods separate rather then relying solely on embedding.