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:
commit
9689fc7c56
3 changed files with 212 additions and 0 deletions
41
rwmutex/README.md
Normal file
41
rwmutex/README.md
Normal 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
55
rwmutex/rwmutex.go
Normal 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
116
rwmutex/rwmutex_test.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in a new issue