Copy-On-Write is a classic way to deal with concurrency problems in the "frequently read, but infrequently updated" scenario.
I noticed an interesting discussion from a previous conversation:
https://groups.google.com/g/golang-nuts/c/zyQnord8hyc. The author of weed-fs tried to implement Copy-On-Write by simply assigning a new value to a string variable in a struct. Like this: `vs.masterNode = master`.
So here comes the question, what's the recommend way of implmentating COW?
The atomic package provides an example:
https://pkg.go.dev/sync/atomic#example-Value-ReadMostlyimport (
"sync"
"sync/atomic"
)
func main() {
type Map map[string]string
var m atomic.Value
m.Store(make(Map))
var mu sync.Mutex // used only by writers
// read function can be used to read the data without further synchronization
read := func(key string) (val string) {
m1 := m.Load().(Map)
return m1[key]
}
// insert function can be used to update the data without further synchronization
insert := func(key, val string) {
mu.Lock() // synchronize with other potential writers
defer mu.Unlock()
m1 := m.Load().(Map) // load current value of the data structure
m2 := make(Map) // create a new value
for k, v := range m1 {
m2[k] = v // copy all data from the current object to the new one
}
m2[key] = val // do the update that we need
m.Store(m2) // atomically replace the current object with the new one
// At this point all new readers start working with the new version.
// The old version will be garbage collected once the existing readers
// (if any) are done with it.
}
_, _ = read, insert
}
Acutally, most of the usecase I saw use map as the underlying data structure for demonstration.
What's the correct way to implement COW on a string variable, or maybe other primitive type like int64 ? Should I just replace the `Map` type, and keep everything else the same in the above official example ?