
Introduction
In another article we discussed the Lock pattern. In this we used the sync.Mutex struct. The problem with this struct is, is that it doesn’t distinguish between reading from a resource, like accessing an element in a vector, and writing to it.
In cases where many threads need to read a resource at one, and there are a few write-operations, the Read-Write Lock pattern can help. This pattern allows concurrent access for readers of the resource. In the case however of writing an exclusive lock is granted, and all read operations are blocked. This last part could be a source of a deadlock, in case the writer takes a long time, or in some bad cases, never finishes for some reason.
In Go, this pattern in implemented using the sync.RWMutex struct. This struct has a lock() method, which grants an exclusive lock, and a RLock() method which grants read access.
Implementation in Go
One of the areas where locks might come in handy, is if you are handing different versions in a version control system. In our example we will build an extremely simplified version control system.
Let’s start with our preliminaries:
package main
import (
	"fmt"
	"sync"
)
// Version represents a version with content
type Version struct {
	version string
	content string
}
// NewVersion creates a new Version with the given version and content
func NewVersion(version, content string) *Version {
	return &Version{
		version: version,
		content: content,
	}
}
func (v *Version) String() string {
	return fmt.Sprintf("Version: %s, Content: %s", v.version, v.content)
}
As you, in our example, a version just a version number and some content. The String() is used for automatic string conversion. It is in fact an implementation of the Stringer interface.
In this example we will go straight to the main function. You could of course wrap this pattern in a struct, if you want, but I want to keep things simple:
func main() {
	var wg sync.WaitGroup
	list := make([]*Version, 0)
	mu := sync.RWMutex{}
	for counter := 0; counter < 10; counter++ {
		wg.Add(1)
		go func(counter int) {
			defer wg.Done()
			mu.Lock()
			version := NewVersion(fmt.Sprintf("v0.%d", counter), fmt.Sprintf("content %d", counter*2))
			list = append(list, version)
			mu.Unlock()
		}(counter)
	}
	wg.Wait()
	mu.RLock()
	fmt.Printf("Result: %v\n", list)
	mu.RUnlock()
}
A short explanation:
- A 
sync.WaitGroupis defined, to synchronize completion of all the goroutines. - We create slice to hold pointer to our 
Versioninstances. - The 
sync.RWMutexis used to protect ourlistslice from concurrent access. 
Next we create 10 goroutines, and each routine:
- We update the 
WaitGroup - We create a new 
Versionstruct and add it to the list. This happens under the protection of thesync.RWMutexusing theLock()and theUnlock()methods. - We notify that our task is done, using 
Done()method, which is called using thedefermethod, which means it is automatically called when the goroutine returns. 
Now that we have a filled slice, we do the following:
- We use the 
Wait()method to wait for all goroutines to finish. - Next we unlock the slice for reading using the 
RLock()method, and unlock it after printing the contents. 
Conclusion
Implementation of the Read-Write lock is relatively painless in the Go, using the provided structures from the standard library.
Using this pattern can improve performance if the number of read-operations on a resource is greater than the number of write-operations