diff --git a/README.md b/README.md index 042d1ca..e188904 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ cond.IsSet() // Returns true cond.UnSet() // Set to false cond.SetTo(true) // Set to whatever you want cond.SetToIf(false, true) // Set to true if it is false, returns false(not set) +cond.Toggle() *AtomicBool // Negates boolean atomically and returns a new AtomicBool object which holds previous boolean value. // embedding @@ -26,24 +27,32 @@ type Foo struct { } ``` -## Benchmark: +## Benchmark -- Go 1.6.2 -- OS X 10.11.4 +- Go 1.11.5 +- OS X 10.14.5 ```shell # Read -BenchmarkMutexRead-4 100000000 21.0 ns/op -BenchmarkAtomicValueRead-4 200000000 6.30 ns/op -BenchmarkAtomicBoolRead-4 300000000 4.21 ns/op # <--- This package +BenchmarkMutexRead-4 100000000 14.7 ns/op +BenchmarkAtomicValueRead-4 2000000000 0.45 ns/op +BenchmarkAtomicBoolRead-4 2000000000 0.35 ns/op # <--- This package # Write -BenchmarkMutexWrite-4 100000000 21.6 ns/op -BenchmarkAtomicValueWrite-4 30000000 43.4 ns/op -BenchmarkAtomicBoolWrite-4 200000000 9.87 ns/op # <--- This package +BenchmarkMutexWrite-4 100000000 14.5 ns/op +BenchmarkAtomicValueWrite-4 100000000 10.5 ns/op +BenchmarkAtomicBoolWrite-4 300000000 5.21 ns/op # <--- This package # CAS -BenchmarkMutexCAS-4 30000000 44.9 ns/op -BenchmarkAtomicBoolCAS-4 100000000 11.7 ns/op # <--- This package +BenchmarkMutexCAS-4 50000000 31.3 ns/op +BenchmarkAtomicBoolCAS-4 200000000 7.18 ns/op # <--- This package + +# Toggle +BenchmarkMutexToggle-4 50000000 32.6 ns/op +BenchmarkAtomicBoolToggle-4 300000000 5.21 ns/op # <--- This package ``` +## Thanks to these Contributors + +- [@barryz](https://github.com/barryz) + - Added the `Toggle` method diff --git a/bool.go b/bool.go index fdda210..2205f9b 100644 --- a/bool.go +++ b/bool.go @@ -37,10 +37,10 @@ func (ab *AtomicBool) UnSet() { // IsSet returns whether the Boolean is true func (ab *AtomicBool) IsSet() bool { - return atomic.LoadInt32((*int32)(ab)) == 1 + return atomic.LoadInt32((*int32)(ab))&1 == 1 } -// SetTo sets the boolean with given Boolean +// SetTo sets the boolean with given Boolean. func (ab *AtomicBool) SetTo(yes bool) { if yes { atomic.StoreInt32((*int32)(ab), 1) @@ -49,6 +49,11 @@ func (ab *AtomicBool) SetTo(yes bool) { } } +// Toggle inverts the boolean then returns the value before inverting. +func (ab *AtomicBool) Toggle() bool { + return atomic.AddInt32((*int32)(ab), 1)&1 == 0 +} + // SetToIf sets the Boolean to new only if the Boolean matches the old // Returns whether the set was done func (ab *AtomicBool) SetToIf(old, new bool) (set bool) { diff --git a/bool_test.go b/bool_test.go index d63517f..c35aa16 100644 --- a/bool_test.go +++ b/bool_test.go @@ -1,12 +1,15 @@ package abool import ( + "math" "sync" "sync/atomic" "testing" ) func TestBool(t *testing.T) { + t.Parallel() + v := NewBool(true) if !v.IsSet() { t.Fatal("NewValue(true) failed") @@ -49,12 +52,80 @@ func TestBool(t *testing.T) { if set := v.SetToIf(false, true); !set || !v.IsSet() { t.Fatal("AtomicBool.SetTo(false, true) failed") } + + v = New() + if v.IsSet() { + t.Fatal("Empty value of AtomicBool should be false") + } + + _ = v.Toggle() + if !v.IsSet() { + t.Fatal("AtomicBool.Toggle() to true failed") + } + + prev := v.Toggle() + if v.IsSet() == prev { + t.Fatal("AtomicBool.Toggle() to false failed") + } +} + +func TestToogleMultipleTimes(t *testing.T) { + t.Parallel() + + v := New() + pre := !v.IsSet() + for i := 0; i < 100; i++ { + v.SetTo(false) + for j := 0; j < i; j++ { + pre = v.Toggle() + } + + expected := i%2 != 0 + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after %d calls, expected: %v, got %v", i, expected, v.IsSet()) + } + + if pre == v.IsSet() { + t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre) + } + } +} + +func TestToogleAfterOverflow(t *testing.T) { + t.Parallel() + + var value int32 = math.MaxInt32 + v := (*AtomicBool)(&value) + + valueBeforeToggle := *(*int32)(v) + + // test first toggle after overflow + v.Toggle() + expected := math.MaxInt32%2 == 0 + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after overflow, expected: %v, got %v", expected, v.IsSet()) + } + + // make sure overflow happened + var valueAfterToggle int32 = *(*int32)(v) + if valueAfterToggle >= valueBeforeToggle { + t.Fatalf("Overflow does not happen as expected, before %d, after: %d", valueBeforeToggle, valueAfterToggle) + } + + // test second toggle after overflow + v.Toggle() + expected = !expected + if v.IsSet() != expected { + t.Fatalf("AtomicBool.Toogle() doesn't work after the second call after overflow, expected: %v, got %v", expected, v.IsSet()) + } } func TestRace(t *testing.T) { + t.Parallel() + repeat := 10000 var wg sync.WaitGroup - wg.Add(repeat * 3) + wg.Add(repeat * 4) v := New() // Writer @@ -80,6 +151,15 @@ func TestRace(t *testing.T) { wg.Done() } }() + + // Reader And Writer + go func() { + for i := 0; i < repeat; i++ { + v.Toggle() + wg.Done() + } + }() + wg.Wait() } @@ -89,6 +169,7 @@ func ExampleAtomicBool() { cond.IsSet() // returns true cond.UnSet() // set to false cond.SetTo(true) // set to whatever you want + cond.Toggle() // toggles the boolean value } // Benchmark Read @@ -174,3 +255,25 @@ func BenchmarkAtomicBoolCAS(b *testing.B) { v.SetToIf(false, true) } } + +// Benchmark toggle boolean value + +func BenchmarkMutexToggle(b *testing.B) { + var m sync.RWMutex + var v bool + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.Lock() + v = !v + m.Unlock() + } + b.StopTimer() +} + +func BenchmarkAtomicBoolToggle(b *testing.B) { + v := New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.Toggle() + } +}