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 set
— Set
and 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
package.
The SetNonTs
and Set
methods embed the set
object and add some additional functionality. Both implement a constructor, NewNonTS
and New
respectively, which call the internal init
functions. Both also implement Add
and 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 Add
and 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.