From 1c237114cd4a43133463c11ec83285cb7d636b3d Mon Sep 17 00:00:00 2001 From: verytable Date: Fri, 5 Mar 2021 11:58:41 +0000 Subject: [PATCH] Resolve "add rwmutex" --- rwmutex/README.md | 41 ++++++++++++++ rwmutex/rwmutex.go | 55 +++++++++++++++++++ rwmutex/rwmutex_test.go | 116 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 rwmutex/README.md create mode 100644 rwmutex/rwmutex.go create mode 100644 rwmutex/rwmutex_test.go diff --git a/rwmutex/README.md b/rwmutex/README.md new file mode 100644 index 0000000..0131ea3 --- /dev/null +++ b/rwmutex/README.md @@ -0,0 +1,41 @@ +## rwmutex + +[sync.RWMutex](https://golang.org/pkg/sync/#RWMutex) -- это примитив синхронизации, +предоставляющий доступ к критической секции произвольному количеству читателей, +не более, чем одному читателю. При этом, если есть писатель, то читателей нет. + +### Что нужно сделать? + +Нужно написать реализацию RWMutex используя каналы. + +Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено! + +```go +type RWMutex struct {} + +func (rw *RWMutex) Lock() {} +func (rw *RWMutex) Unlock() {} + +func (rw *RWMutex) RLock() {} +func (rw *RWMutex) RUnlock() {} +``` + +`RWMutex` можно представлять себе как две блокировки, блокировка на чтение и блокировка на запись. + +`New()` возвращает `RWMutex`, в котором ни одна из блокировок не взята. + +Процессы, желающие изменить данные (писатели), берут блокировку на запись с помощью метода `Lock`. +Процессы, желающие прочитать данные (читатели), берут блокировку на чтение с помощью метода `RLock`. +По окончании записи писатель отпускает блокировку на запись (`Unlock`). +С блокировкой на чтение связано число, число активных читателей. +При взятии блокировки (`RLock`) это число инкрементируется. +При завершении чтения (`RUnlock`) блокировка на чтение уменьшает счётчик. + +#### Свойства +1. Писатель не заблокируется при взятии блокировки только при условии, + что никакой другой писатель и никакой другой читатель не владеет соответствующей блокировкой. +2. Если какой-то писатель взял блокировку на запись, любой новый писатель или читатель заблокируется при взятии блокировки. +3. Если какой-то читатель взял блокировку на чтение, любой новый писатель заблокируется на взятии блокировки. + Однако любой писатель сможет взять блокировку на чтение. + +Для выполнения этих свойств достаточно двух каналов. diff --git a/rwmutex/rwmutex.go b/rwmutex/rwmutex.go new file mode 100644 index 0000000..53f1663 --- /dev/null +++ b/rwmutex/rwmutex.go @@ -0,0 +1,55 @@ +// +build !solution + +package rwmutex + +// A RWMutex is a reader/writer mutual exclusion lock. +// The lock can be held by an arbitrary number of readers or a single writer. +// The zero value for a RWMutex is an unlocked mutex. +// +// If a goroutine holds a RWMutex for reading and another goroutine might +// call Lock, no goroutine should expect to be able to acquire a read lock +// until the initial read lock is released. In particular, this prohibits +// recursive read locking. This is to ensure that the lock eventually becomes +// available; a blocked Lock call excludes new readers from acquiring the +// lock. +type RWMutex struct { +} + +// New creates *RWMutex. +func New() *RWMutex { + return nil +} + +// RLock locks rw for reading. +// +// It should not be used for recursive read locking; a blocked Lock +// call excludes new readers from acquiring the lock. See the +// documentation on the RWMutex type. +func (rw *RWMutex) RLock() { + +} + +// RUnlock undoes a single RLock call; +// it does not affect other simultaneous readers. +// It is a run-time error if rw is not locked for reading +// on entry to RUnlock. +func (rw *RWMutex) RUnlock() { + +} + +// Lock locks rw for writing. +// If the lock is already locked for reading or writing, +// Lock blocks until the lock is available. +func (rw *RWMutex) Lock() { + +} + +// Unlock unlocks rw for writing. It is a run-time error if rw is +// not locked for writing on entry to Unlock. +// +// As with Mutexes, a locked RWMutex is not associated with a particular +// goroutine. One goroutine may RLock (Lock) a RWMutex and then +// arrange for another goroutine to RUnlock (Unlock) it. +func (rw *RWMutex) Unlock() { + +} diff --git a/rwmutex/rwmutex_test.go b/rwmutex/rwmutex_test.go new file mode 100644 index 0000000..6917345 --- /dev/null +++ b/rwmutex/rwmutex_test.go @@ -0,0 +1,116 @@ +package rwmutex + +import ( + "fmt" + "runtime" + "sync/atomic" + "testing" +) + +func parallelReader(m *RWMutex, clocked, cunlock, cdone chan bool) { + m.RLock() + clocked <- true + <-cunlock + m.RUnlock() + cdone <- true +} + +func doTestParallelReaders(numReaders, gomaxprocs int) { + runtime.GOMAXPROCS(gomaxprocs) + m := New() + clocked := make(chan bool) + cunlock := make(chan bool) + cdone := make(chan bool) + for i := 0; i < numReaders; i++ { + go parallelReader(m, clocked, cunlock, cdone) + } + // Wait for all parallel RLock()s to succeed. + for i := 0; i < numReaders; i++ { + <-clocked + } + for i := 0; i < numReaders; i++ { + cunlock <- true + } + // Wait for the goroutines to finish. + for i := 0; i < numReaders; i++ { + <-cdone + } +} + +func TestParallelReaders(t *testing.T) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + doTestParallelReaders(1, 4) + doTestParallelReaders(3, 4) + doTestParallelReaders(4, 2) +} + +func reader(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) { + for i := 0; i < numIterations; i++ { + rwm.RLock() + n := atomic.AddInt32(activity, 1) + if n < 1 || n >= 10000 { + rwm.RUnlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -1) + rwm.RUnlock() + } + cdone <- true +} + +func writer(rwm *RWMutex, numIterations int, activity *int32, cdone chan bool) { + for i := 0; i < numIterations; i++ { + rwm.Lock() + n := atomic.AddInt32(activity, 10000) + if n != 10000 { + rwm.Unlock() + panic(fmt.Sprintf("wlock(%d)\n", n)) + } + for i := 0; i < 100; i++ { + } + atomic.AddInt32(activity, -10000) + rwm.Unlock() + } + cdone <- true +} + +func HammerRWMutex(gomaxprocs, numReaders, numIterations int) { + runtime.GOMAXPROCS(gomaxprocs) + // Number of active readers + 10000 * number of active writers. + var activity int32 + rwm := New() + cdone := make(chan bool) + go writer(rwm, numIterations, &activity, cdone) + var i int + for i = 0; i < numReaders/2; i++ { + go reader(rwm, numIterations, &activity, cdone) + } + go writer(rwm, numIterations, &activity, cdone) + for ; i < numReaders; i++ { + go reader(rwm, numIterations, &activity, cdone) + } + // Wait for the 2 writers and all readers to finish. + for i := 0; i < 2+numReaders; i++ { + <-cdone + } +} + +func TestRWMutex(t *testing.T) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) + n := 1000 + if testing.Short() { + n = 5 + } + HammerRWMutex(1, 1, n) + HammerRWMutex(1, 3, n) + HammerRWMutex(1, 10, n) + HammerRWMutex(4, 1, n) + HammerRWMutex(4, 3, n) + HammerRWMutex(4, 10, n) + HammerRWMutex(10, 1, n) + HammerRWMutex(10, 3, n) + HammerRWMutex(10, 10, n) + HammerRWMutex(10, 5, n) +}