Merge branch '29-add-rwmutex' into 'master'

Resolve "add rwmutex"

Closes #29

See merge request slon/shad-go-private!41
This commit is contained in:
verytable 2021-03-05 11:58:42 +00:00
commit 9689fc7c56
3 changed files with 212 additions and 0 deletions

41
rwmutex/README.md Normal file
View file

@ -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. Если какой-то читатель взял блокировку на чтение, любой новый писатель заблокируется на взятии блокировки.
Однако любой писатель сможет взять блокировку на чтение.
Для выполнения этих свойств достаточно двух каналов.

55
rwmutex/rwmutex.go Normal file
View file

@ -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() {
}

116
rwmutex/rwmutex_test.go Normal file
View file

@ -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)
}