diff --git a/once/README.md b/once/README.md new file mode 100644 index 0000000..db59433 --- /dev/null +++ b/once/README.md @@ -0,0 +1,21 @@ +## once + +[sync.Once](https://golang.org/pkg/sync/#Once) решает задачу ленивой инициализации. + +Как правило, `sync.Once` используется для того, чтобы гарантировать, что некоторый код многопоточной программы будет выполнен ровно один раз. + +### Что нужно сделать? + +Нужно написать реализацию Once используя каналы. + +Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено! + +```go +type Once struct {} + +func (o *Once) Do(f func()) {} +``` + +`Do` вызывает функцию `f` тогда и только тогда, когда `Do` вызывается впервые для данного экземпляра `Once`. + +Важное свойство: код `f` должен завершиться до того, как завершится любой из конкурентных `Do`. diff --git a/once/once.go b/once/once.go new file mode 100644 index 0000000..dc1a058 --- /dev/null +++ b/once/once.go @@ -0,0 +1,31 @@ +// +build !solution + +package once + +// Once describes an object that will perform exactly one action. +type Once struct { +} + +// New create Once. +func New() *Once { + return nil +} + +// Do calls the function f if and only if Do is being called for the +// first time for this instance of Once. In other words, given +// once := New() +// if once.Do(f) is called multiple times, only the first call will invoke f, +// even if f has a different value in each invocation. A new instance of +// Once is required for each function to execute. +// +// Do is intended for initialization that must be run exactly once. +// +// Because no call to Do returns until the one call to f returns, if f causes +// Do to be called, it will deadlock. +// +// If f panics, Do considers it to have returned; future calls of Do return +// without calling f. +// +func (o *Once) Do(f func()) { + +} diff --git a/once/once_test.go b/once/once_test.go new file mode 100644 index 0000000..093e3c1 --- /dev/null +++ b/once/once_test.go @@ -0,0 +1,54 @@ +package once + +import ( + "testing" +) + +type one int + +func (o *one) Increment() { + *o++ +} + +func run(t *testing.T, once *Once, o *one, c chan bool) { + once.Do(func() { o.Increment() }) + if v := *o; v != 1 { + t.Errorf("once failed inside run: %d is not 1", v) + } + c <- true +} + +func TestOnce(t *testing.T) { + o := new(one) + once := New() + c := make(chan bool) + const N = 10 + for i := 0; i < N; i++ { + go run(t, once, o, c) + } + for i := 0; i < N; i++ { + <-c + } + if *o != 1 { + t.Errorf("once failed outside run: %d is not 1", *o) + } +} + +func TestOncePanic(t *testing.T) { + once := New() + + func() { + defer func() { + if r := recover(); r == nil { + t.Fatalf("Once.Do did not panic") + } + }() + once.Do(func() { + panic("failed") + }) + }() + + once.Do(func() { + t.Fatalf("Once.Do called twice") + }) +}