diff --git a/README.md b/README.md index fd2d912..3a8c26f 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ A curated collection of idiomatic design & application patterns for Go language. | [Condition Variable](/synchronization/condition_variable.md) | Provides a mechanism for threads to temporarily give up access in order to wait for some condition | ✘ | | [Lock/Mutex](/synchronization/mutex.md) | Enforces mutual exclusion limit on a resource to gain exclusive access | ✘ | | [Monitor](/synchronization/monitor.md) | Combination of mutex and condition variable patterns | ✘ | -| [Read-Write Lock](/synchronization/read_write_lock.md) | Allows parallel read access, but only exclusive access on write operations to a resource | ✘ | +| [Read-Write Lock](/synchronization/read_write_lock.md) | Allows parallel read access, but only exclusive access on write operations to a resource | ✔ | | [Semaphore](/synchronization/semaphore.md) | Allows controlling access to a common resource | ✔ | ## Concurrency Patterns diff --git a/synchronization/read_write_lock.md b/synchronization/read_write_lock.md new file mode 100644 index 0000000..5ca9eca --- /dev/null +++ b/synchronization/read_write_lock.md @@ -0,0 +1,112 @@ +# Read/Write Lock Pattern +Allows parallel read access, but only exclusive access on write operations to a resource + +## Implementation + +```go +package router + +import ( + "sync" + "net" +) + +type Socket string +type Peer interface { + socket Socket + connection net.Conn +} + +func (p *Peer) Socket() Socket { + return p.socket +} + +// Router hash table to associate Socket with Peers. +// Unstructured mesh architecture +// eg. {127.0.0.1:4000: Peer} +type Router struct { + sync.RWMutex + table map[Socket]Peer +} + + +// Return connection interface based on socket +func (r *Router) Query(socket Socket) *Peer { + // Mutex for reading topics. + // Do not write while topics are read. + // Write Lock can’t be acquired until all Read Locks are released. + r.RWMutex.RLock() + defer r.RWMutex.RUnlock() + + if peer, ok := r.table[socket]; ok { + return peer + } + + return nil +} + +// Add create new socket connection association +func (r *Router) Add(peer *Peer) { + // Lock write table while add operation + // A blocked Lock call excludes new readers from acquiring the lock. + r.RWMutex.Lock() + r.table[peer.Socket()] = peer + r.RWMutex.Unlock() +} + +// Delete removes a connection from router +func (r *Router) Delete(peer *Peer) { + // Lock write table while delete operation + // A blocked Lock call excludes new readers from acquiring the lock. + r.RWMutex.Lock() + delete(r.table, peer.Socket()) + r.RWMutex.Unlock() +} +``` + +## Usage +### Syncronize routing peers + +```go + +// New router +router:= &Router{ + table: make(map[Socket]Peer) +} + +// !Important: +// 1 - Write Lock can’t be acquired until all Read Locks are released. +// 2 - A blocked Lock call excludes new readers from acquiring the lock. + +// Writing operation +go func(r *Router){ + for { + // this will be running waiting for new connections + /// .. some code here + conn, err := listener.Accept() + // eg. 192.168.1.1:8080 + remote := connection.RemoteAddr().String() + socket := Socket(address) + // New peer + peer := &Peer{ + socket: socket, + connection: conn + } + // Here we need a write lock to avoid race condition + r.Add(peer) + } +}(router) + +// Reading operation +go func(r *Router){ + // ...some logic here + // reading operation 1 + connection := r.Query("192.168.1.1:8080") + //... more code here + // reading operation 2 + otherQuery:= r.Query("192.168.1.1:8081") + // read locks are like counters.. + // until counter = 0 Write can be acquired +}(router) + +```