Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions homeworks/13_sync_primitives/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Домашнее задание №13

**В домашнем задании нужно реализовать разделяемый мьютекс.**

Разделяемый мьютекс будет использоваться для управления доступом к ресурсам с возможностью одновременного чтения при наличии сразу нескольких читателей, но модификация ресурса может осуществляться только одним писателем. Для реализация следует использовать `sync.Mutex` - свой собственный мьютекс реализовывать не нужно.

API для разделяемого мьютекса будет выглядеть следующим образом:

```
type RWMutex struct { ... }
func (m *RWMutex) Lock() // захватить мьютекс на запись
func (m *RWMutex) Unlock() // освободить мьютекс на запись
func (m *RWMutex) RLock() // захватить мьютекс на чтеник
func (m *RWMutex) RUnlock() // освободить мьютекс на чтение
```

Для выполнения домашнего задания подготовлен шаблон кода и основные тесты, которую помогут проверить корректность реализации конвертации. Шаблона доступен по [ссылке](https://github.com/Balun-courses/deep_go/blob/master/homework/sync_primitives/homework_test.go).

**Задание со звездочкой**
Выполнять необязательно, но если вы хотите, можете попробовать реализовать методы `TryLock()` и `TryRLock()` для того, чтобы попробовать захватить мьютекс без блокировки.
89 changes: 89 additions & 0 deletions homeworks/13_sync_primitives/homework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package homework13

import (
"sync"
)

type RWMutex struct {
readerCount int
mutex sync.Mutex
writerCond *sync.Cond
writerActive bool
writersWaiting int
}

func NewRWMutex() *RWMutex {
rwmutex := RWMutex{}
rwmutex.writerCond = sync.NewCond(&rwmutex.mutex)
return &rwmutex
}

func (m *RWMutex) Lock() {
m.mutex.Lock()
defer m.mutex.Unlock()

m.underLock()
}

func (m *RWMutex) Unlock() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.writerActive = false
m.writerCond.Broadcast()
}

func (m *RWMutex) RLock() {
m.mutex.Lock()
defer m.mutex.Unlock()

m.underRLock()
}

func (m *RWMutex) RUnlock() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.readerCount--
if m.readerCount == 0 {
m.writerCond.Broadcast()
}
}

func (m *RWMutex) TryLock() bool {
ok := m.mutex.TryLock()
if !ok {
return false
}
defer m.mutex.Unlock()

m.underLock()

return true
}

func (m *RWMutex) TryRLock() bool {
ok := m.mutex.TryLock()
if !ok {
return false
}
defer m.mutex.Unlock()

m.underRLock()

return true
}

func (m *RWMutex) underLock() {
m.writersWaiting++
for m.writerActive || m.readerCount != 0 {
m.writerCond.Wait()
}
m.writerActive = true
m.writersWaiting--
}

func (m *RWMutex) underRLock() {
for m.writerActive || m.writersWaiting > 0 {
m.writerCond.Wait()
}
m.readerCount++
}
102 changes: 102 additions & 0 deletions homeworks/13_sync_primitives/homework_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package homework13

import (
"sync/atomic"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestRWMutexWithWriter(t *testing.T) {
mutex := NewRWMutex()
mutex.Lock() // writer

var mutualExlusionWithWriter atomic.Bool
mutualExlusionWithWriter.Store(true)
var mutualExlusionWithReader atomic.Bool
mutualExlusionWithReader.Store(true)

go func() {
mutex.Lock() // another writer
mutualExlusionWithWriter.Store(false)
}()

go func() {
mutex.RLock() // another reader
mutualExlusionWithReader.Store(false)
}()

time.Sleep(time.Second)
assert.True(t, mutualExlusionWithWriter.Load())
assert.True(t, mutualExlusionWithReader.Load())
}

func TestRWMutexWithReaders(t *testing.T) {
mutex := NewRWMutex()
mutex.RLock() // reader

var mutualExlusionWithWriter atomic.Bool
mutualExlusionWithWriter.Store(true)

go func() {
mutex.Lock() // another writer
mutualExlusionWithWriter.Store(false)
}()

time.Sleep(time.Second)
assert.True(t, mutualExlusionWithWriter.Load())
}

func TestRWMutexMultipleReaders(t *testing.T) {
mutex := NewRWMutex()
mutex.RLock() // reader

var readersCount atomic.Int32
readersCount.Add(1)

go func() {
mutex.RLock() // another reader
readersCount.Add(1)
}()

go func() {
mutex.RLock() // another reader
readersCount.Add(1)
}()

time.Sleep(time.Second)
assert.Equal(t, int32(3), readersCount.Load())
}

func TestRWMutexWithWriterPriority(t *testing.T) {
mutex := NewRWMutex()
mutex.RLock() // reader

var mutualExlusionWithWriter atomic.Bool
mutualExlusionWithWriter.Store(true)
var readersCount atomic.Int32
readersCount.Add(1)

go func() {
mutex.Lock() // another writer is waiting for reader
mutualExlusionWithWriter.Store(false)
}()

time.Sleep(time.Second)

go func() {
mutex.RLock() // another reader is waiting for a higher priority writer
readersCount.Add(1)
}()

go func() {
mutex.RLock() // another reader is waiting for a higher priority writer
readersCount.Add(1)
}()

time.Sleep(time.Second)

assert.True(t, mutualExlusionWithWriter.Load())
assert.Equal(t, int32(1), readersCount.Load())
}