From 1728597005e0e78a2e7167b25efdbe7a2a43af88 Mon Sep 17 00:00:00 2001 From: Vladislav Byrgazov Date: Mon, 12 Jan 2026 22:01:31 +0500 Subject: [PATCH] homework: implemented RWMutex Signed-off-by: Vladislav Byrgazov --- homeworks/13_sync_primitives/README.md | 20 ++++ homeworks/13_sync_primitives/homework.go | 89 +++++++++++++++ homeworks/13_sync_primitives/homework_test.go | 102 ++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 homeworks/13_sync_primitives/README.md create mode 100644 homeworks/13_sync_primitives/homework.go create mode 100644 homeworks/13_sync_primitives/homework_test.go diff --git a/homeworks/13_sync_primitives/README.md b/homeworks/13_sync_primitives/README.md new file mode 100644 index 0000000..85d349c --- /dev/null +++ b/homeworks/13_sync_primitives/README.md @@ -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()` для того, чтобы попробовать захватить мьютекс без блокировки. diff --git a/homeworks/13_sync_primitives/homework.go b/homeworks/13_sync_primitives/homework.go new file mode 100644 index 0000000..05c8b47 --- /dev/null +++ b/homeworks/13_sync_primitives/homework.go @@ -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++ +} diff --git a/homeworks/13_sync_primitives/homework_test.go b/homeworks/13_sync_primitives/homework_test.go new file mode 100644 index 0000000..67d331b --- /dev/null +++ b/homeworks/13_sync_primitives/homework_test.go @@ -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()) +}