From f60393b0425b9dba0b2dacb503c247b5fb7eccdb Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Fri, 5 Mar 2021 14:31:48 +0300 Subject: [PATCH] Add waitgroup. --- waitgroup/README.md | 29 +++++++++++++ waitgroup/waitgroup.go | 43 ++++++++++++++++++++ waitgroup/waitgroup_test.go | 81 +++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 waitgroup/README.md create mode 100644 waitgroup/waitgroup.go create mode 100644 waitgroup/waitgroup_test.go diff --git a/waitgroup/README.md b/waitgroup/README.md new file mode 100644 index 0000000..c125348 --- /dev/null +++ b/waitgroup/README.md @@ -0,0 +1,29 @@ +## waitgroup + +[sync.WaitGroup](https://golang.org/pkg/sync/#WaitGroup) решает задачу ожидания завершения всех горутин из набора. + +### Что нужно сделать? + +Нужно написать реализацию WaitGroup используя каналы. + +Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено! + +```go +type WaitGroup struct {} + +func (wg *WaitGroup) Add(delta int) {} +func (wg *WaitGroup) Done() {} +func (wg *WaitGroup) Wait() {} +``` + +`WaitGroup` можно представлять себе как число. + +* При инициализации переменной типа `WaitGroup` число равно нулю. +* Вызов `Add` прибавляет `delta` к числу. +* Вызов `Done` вычитает из числа единицу. +* Если при вызове `Add` или `Done` число становится отрицательным, происходит panic. +* Вызов `Wait` при числе равном нулю -- это no-op. +* Вызов `Wait` при числе большем нуля блокируется до тех пор, пока число не станет равным нулю. + +`WaitGroup` может быть "переиспользована". +После достижения нуля можно вызвать `Add` заблокировав последующий `Wait`. diff --git a/waitgroup/waitgroup.go b/waitgroup/waitgroup.go new file mode 100644 index 0000000..a44decd --- /dev/null +++ b/waitgroup/waitgroup.go @@ -0,0 +1,43 @@ +// +build !solution + +package waitgroup + +// A WaitGroup waits for a collection of goroutines to finish. +// The main goroutine calls Add to set the number of +// goroutines to wait for. Then each of the goroutines +// runs and calls Done when finished. At the same time, +// Wait can be used to block until all goroutines have finished. +type WaitGroup struct { +} + +// New creates WaitGroup. +func New() *WaitGroup { + return nil +} + +// Add adds delta, which may be negative, to the WaitGroup counter. +// If the counter becomes zero, all goroutines blocked on Wait are released. +// If the counter goes negative, Add panics. +// +// Note that calls with a positive delta that occur when the counter is zero +// must happen before a Wait. Calls with a negative delta, or calls with a +// positive delta that start when the counter is greater than zero, may happen +// at any time. +// Typically this means the calls to Add should execute before the statement +// creating the goroutine or other event to be waited for. +// If a WaitGroup is reused to wait for several independent sets of events, +// new Add calls must happen after all previous Wait calls have returned. +// See the WaitGroup example. +func (wg *WaitGroup) Add(delta int) { + +} + +// Done decrements the WaitGroup counter by one. +func (wg *WaitGroup) Done() { + +} + +// Wait blocks until the WaitGroup counter is zero. +func (wg *WaitGroup) Wait() { + +} diff --git a/waitgroup/waitgroup_test.go b/waitgroup/waitgroup_test.go new file mode 100644 index 0000000..4f8ce6f --- /dev/null +++ b/waitgroup/waitgroup_test.go @@ -0,0 +1,81 @@ +package waitgroup + +import ( + "sync/atomic" + "testing" +) + +func testWaitGroup(t *testing.T, wg1 *WaitGroup, wg2 *WaitGroup) { + n := 16 + wg1.Add(n) + wg2.Add(n) + exited := make(chan bool, n) + for i := 0; i != n; i++ { + go func() { + wg1.Done() + wg2.Wait() + exited <- true + }() + } + wg1.Wait() + for i := 0; i != n; i++ { + select { + case <-exited: + t.Fatal("WaitGroup released group too soon") + default: + } + wg2.Done() + } + for i := 0; i != n; i++ { + <-exited // Will block if barrier fails to unlock someone. + } +} + +func TestWaitGroup(t *testing.T) { + wg1 := New() + wg2 := New() + + // Run the same test a few times to ensure barrier is in a proper state. + for i := 0; i != 8; i++ { + testWaitGroup(t, wg1, wg2) + } +} + +func TestWaitGroupMisuse(t *testing.T) { + defer func() { + err := recover() + if err != "negative WaitGroup counter" { + t.Fatalf("Unexpected panic: %#v", err) + } + }() + wg := New() + wg.Add(1) + wg.Done() + wg.Done() + t.Fatal("Should panic") +} + +func TestWaitGroupRace(t *testing.T) { + // Run this test for about 1ms. + for i := 0; i < 1000; i++ { + wg := New() + n := new(int32) + // spawn goroutine 1 + wg.Add(1) + go func() { + atomic.AddInt32(n, 1) + wg.Done() + }() + // spawn goroutine 2 + wg.Add(1) + go func() { + atomic.AddInt32(n, 1) + wg.Done() + }() + // Wait for goroutine 1 and 2 + wg.Wait() + if atomic.LoadInt32(n) != 2 { + t.Fatal("Spurious wakeup from Wait") + } + } +}