From ae078ebcf71697658e201dba34dd5d6bd7598695 Mon Sep 17 00:00:00 2001 From: barryz Date: Fri, 7 Sep 2018 22:17:26 +0800 Subject: [PATCH 01/13] Add Flip method to flip boolean value --- README.md | 5 +++++ bool.go | 5 +++++ bool_test.go | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 042d1ca..1542a83 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.Flip() // Flip the boolean value, true to false, or false to ture // embedding @@ -45,5 +46,9 @@ BenchmarkAtomicBoolWrite-4 200000000 9.87 ns/op # <--- This package # CAS BenchmarkMutexCAS-4 30000000 44.9 ns/op BenchmarkAtomicBoolCAS-4 100000000 11.7 ns/op # <--- This package + +# Flip +BenchmarkMutexFlip-4 50000000 29.7 ns/op +BenchmarkAtomicBoolFlip-4 200000000 8.65 ns/op # <--- This package ``` diff --git a/bool.go b/bool.go index fdda210..7c8d194 100644 --- a/bool.go +++ b/bool.go @@ -49,6 +49,11 @@ func (ab *AtomicBool) SetTo(yes bool) { } } +// Flip flips the boolean value whether the value is set or not +func (ab *AtomicBool) Flip() { + atomic.StoreInt32((*int32)(ab), atomic.LoadInt32((*int32)(ab))^1) +} + // 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..6d83431 100644 --- a/bool_test.go +++ b/bool_test.go @@ -49,12 +49,18 @@ func TestBool(t *testing.T) { if set := v.SetToIf(false, true); !set || !v.IsSet() { t.Fatal("AtomicBool.SetTo(false, true) failed") } + + v.Flip() // expected false + if v.IsSet() { + t.Fatal("AtomicBool.Flip() failed") + } + } func TestRace(t *testing.T) { repeat := 10000 var wg sync.WaitGroup - wg.Add(repeat * 3) + wg.Add(repeat * 4) v := New() // Writer @@ -80,6 +86,15 @@ func TestRace(t *testing.T) { wg.Done() } }() + + // Reader And Writer + go func() { + for i := 0; i < repeat; i++ { + v.Flip() + wg.Done() + } + }() + wg.Wait() } @@ -89,6 +104,7 @@ func ExampleAtomicBool() { cond.IsSet() // returns true cond.UnSet() // set to false cond.SetTo(true) // set to whatever you want + cond.Flip() // flips the boolean value } // Benchmark Read @@ -174,3 +190,25 @@ func BenchmarkAtomicBoolCAS(b *testing.B) { v.SetToIf(false, true) } } + +// Benchmark flip boolean value + +func BenchmarkMutexFlip(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 BenchmarkAtomicBoolFlip(b *testing.B) { + v := New() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.Flip() + } +} From 0bc71b68c32ac9852043fd1123baff450b04b5b1 Mon Sep 17 00:00:00 2001 From: Tevin Date: Sat, 8 Sep 2018 12:35:14 +0800 Subject: [PATCH 02/13] Refine comments --- bool.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bool.go b/bool.go index 7c8d194..e9e20fa 100644 --- a/bool.go +++ b/bool.go @@ -40,7 +40,7 @@ func (ab *AtomicBool) IsSet() bool { return atomic.LoadInt32((*int32)(ab)) == 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,7 +49,7 @@ func (ab *AtomicBool) SetTo(yes bool) { } } -// Flip flips the boolean value whether the value is set or not +// Flip toggles the Boolean (replaces with its opposite value). func (ab *AtomicBool) Flip() { atomic.StoreInt32((*int32)(ab), atomic.LoadInt32((*int32)(ab))^1) } From 5a88366efc1cde74d77bb3296cd7ed5df0464a31 Mon Sep 17 00:00:00 2001 From: Tevin Date: Sat, 8 Sep 2018 12:36:39 +0800 Subject: [PATCH 03/13] Refine comments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1542a83..c13a511 100644 --- a/README.md +++ b/README.md @@ -18,7 +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.Flip() // Flip the boolean value, true to false, or false to ture +cond.Flip() // Flip toggles the value(replaces with its opposite value) // embedding From a8aae649693e6c1a076bdc1959bf0a08552fe135 Mon Sep 17 00:00:00 2001 From: barryz Date: Sat, 8 Sep 2018 20:31:55 +0800 Subject: [PATCH 04/13] Refine flip method, may be not atomic yet --- bool.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bool.go b/bool.go index e9e20fa..a57358a 100644 --- a/bool.go +++ b/bool.go @@ -51,7 +51,11 @@ func (ab *AtomicBool) SetTo(yes bool) { // Flip toggles the Boolean (replaces with its opposite value). func (ab *AtomicBool) Flip() { - atomic.StoreInt32((*int32)(ab), atomic.LoadInt32((*int32)(ab))^1) + var o, n int32 + o, n = 0, 1 + if !atomic.CompareAndSwapInt32((*int32)(ab), o, n) { + atomic.CompareAndSwapInt32((*int32)(ab), n, o) + } } // SetToIf sets the Boolean to new only if the Boolean matches the old From 42463bd9a011473efd0f3a35eaf5b6686adbbb14 Mon Sep 17 00:00:00 2001 From: barryz Date: Mon, 22 Jul 2019 13:04:35 +0800 Subject: [PATCH 05/13] atomically negates boolean value with Toggle method --- README.md | 18 +++++++++--------- bool.go | 10 +++------- bool_test.go | 16 ++++++++-------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c13a511..8d83a7e 100644 --- a/README.md +++ b/README.md @@ -18,7 +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.Flip() // Flip toggles the value(replaces with its opposite value) +cond.Toggle() bool // Toggle the boolean value atomically and returns the previous value. // embedding @@ -34,21 +34,21 @@ type Foo struct { ```shell # Read -BenchmarkMutexRead-4 100000000 21.0 ns/op +BenchmarkMutexRead-4 100000000 21.0 ns/op BenchmarkAtomicValueRead-4 200000000 6.30 ns/op BenchmarkAtomicBoolRead-4 300000000 4.21 ns/op # <--- This package # Write -BenchmarkMutexWrite-4 100000000 21.6 ns/op -BenchmarkAtomicValueWrite-4 30000000 43.4 ns/op +BenchmarkMutexWrite-4 100000000 21.6 ns/op +BenchmarkAtomicValueWrite-4 30000000 43.4 ns/op BenchmarkAtomicBoolWrite-4 200000000 9.87 ns/op # <--- This package # CAS -BenchmarkMutexCAS-4 30000000 44.9 ns/op -BenchmarkAtomicBoolCAS-4 100000000 11.7 ns/op # <--- This package +BenchmarkMutexCAS-4 30000000 44.9 ns/op +BenchmarkAtomicBoolCAS-4 100000000 11.7 ns/op # <--- This package -# Flip -BenchmarkMutexFlip-4 50000000 29.7 ns/op -BenchmarkAtomicBoolFlip-4 200000000 8.65 ns/op # <--- This package +# Toggle +BenchmarkMutexToggle-4 50000000 30.7 ns/op +BenchmarkAtomicBoolToggle-4 300000000 5.27 ns/op # <--- This package ``` diff --git a/bool.go b/bool.go index a57358a..7448c67 100644 --- a/bool.go +++ b/bool.go @@ -49,13 +49,9 @@ func (ab *AtomicBool) SetTo(yes bool) { } } -// Flip toggles the Boolean (replaces with its opposite value). -func (ab *AtomicBool) Flip() { - var o, n int32 - o, n = 0, 1 - if !atomic.CompareAndSwapInt32((*int32)(ab), o, n) { - atomic.CompareAndSwapInt32((*int32)(ab), n, o) - } +// Toggle negates boolean atomically and returns the previous value. +func (ab *AtomicBool) Toggle() bool { + return (atomic.AddInt32((*int32)(ab), 1)-1)&1 == 1 } // SetToIf sets the Boolean to new only if the Boolean matches the old diff --git a/bool_test.go b/bool_test.go index 6d83431..2e9b4f0 100644 --- a/bool_test.go +++ b/bool_test.go @@ -50,9 +50,9 @@ func TestBool(t *testing.T) { t.Fatal("AtomicBool.SetTo(false, true) failed") } - v.Flip() // expected false + _ = v.Toggle() // expected false if v.IsSet() { - t.Fatal("AtomicBool.Flip() failed") + t.Fatal("AtomicBool.Toggle() failed") } } @@ -90,7 +90,7 @@ func TestRace(t *testing.T) { // Reader And Writer go func() { for i := 0; i < repeat; i++ { - v.Flip() + v.Toggle() wg.Done() } }() @@ -104,7 +104,7 @@ func ExampleAtomicBool() { cond.IsSet() // returns true cond.UnSet() // set to false cond.SetTo(true) // set to whatever you want - cond.Flip() // flips the boolean value + cond.Toggle() // toggles the boolean value } // Benchmark Read @@ -191,9 +191,9 @@ func BenchmarkAtomicBoolCAS(b *testing.B) { } } -// Benchmark flip boolean value +// Benchmark toggle boolean value -func BenchmarkMutexFlip(b *testing.B) { +func BenchmarkMutexToggle(b *testing.B) { var m sync.RWMutex var v bool b.ResetTimer() @@ -205,10 +205,10 @@ func BenchmarkMutexFlip(b *testing.B) { b.StopTimer() } -func BenchmarkAtomicBoolFlip(b *testing.B) { +func BenchmarkAtomicBoolToggle(b *testing.B) { v := New() b.ResetTimer() for i := 0; i < b.N; i++ { - v.Flip() + v.Toggle() } } From 774dbeaec27c1d660e67297c09f1c49eddf22d20 Mon Sep 17 00:00:00 2001 From: barryz Date: Mon, 22 Jul 2019 13:11:38 +0800 Subject: [PATCH 06/13] fixing readme --- README.md | 25 ++++++++++++------------- bool.go | 2 +- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 8d83a7e..627883d 100644 --- a/README.md +++ b/README.md @@ -29,26 +29,25 @@ type Foo struct { ## 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 30.7 ns/op -BenchmarkAtomicBoolToggle-4 300000000 5.27 ns/op # <--- This package +BenchmarkMutexToggle-4 50000000 32.6 ns/op +BenchmarkAtomicBoolToggle-4 300000000 5.21 ns/op # <--- This package ``` - diff --git a/bool.go b/bool.go index 7448c67..401c11f 100644 --- a/bool.go +++ b/bool.go @@ -40,7 +40,7 @@ func (ab *AtomicBool) IsSet() bool { return atomic.LoadInt32((*int32)(ab)) == 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) From 9e538c6d38b9d98b1bd1092f2ced66a269f5ce32 Mon Sep 17 00:00:00 2001 From: barryz Date: Tue, 23 Jul 2019 23:43:19 +0800 Subject: [PATCH 07/13] implments Toggle method more explicit and simple. --- README.md | 2 +- bool.go | 6 +++--- bool_test.go | 13 +++++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 627883d..810b1ae 100644 --- a/README.md +++ b/README.md @@ -18,7 +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() bool // Toggle the boolean value atomically and returns the previous value. +cond.Toggle() *AtomicBool // Negates boolean atomically and returns a new AtomicBool object which holds previous boolean value. // embedding diff --git a/bool.go b/bool.go index 401c11f..3a3ca64 100644 --- a/bool.go +++ b/bool.go @@ -49,9 +49,9 @@ func (ab *AtomicBool) SetTo(yes bool) { } } -// Toggle negates boolean atomically and returns the previous value. -func (ab *AtomicBool) Toggle() bool { - return (atomic.AddInt32((*int32)(ab), 1)-1)&1 == 1 +// Toggle negates boolean atomically and returns a new AtomicBool object which holds previous boolean value. +func (ab *AtomicBool) Toggle() *AtomicBool { + return NewBool(atomic.AddInt32((*int32)(ab), 1)&1 == 0) } // SetToIf sets the Boolean to new only if the Boolean matches the old diff --git a/bool_test.go b/bool_test.go index 2e9b4f0..18760b0 100644 --- a/bool_test.go +++ b/bool_test.go @@ -50,11 +50,20 @@ func TestBool(t *testing.T) { t.Fatal("AtomicBool.SetTo(false, true) failed") } - _ = v.Toggle() // expected false + v = New() if v.IsSet() { - t.Fatal("AtomicBool.Toggle() failed") + 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.IsSet() { + t.Fatal("AtomicBool.Toggle() to false failed") + } } func TestRace(t *testing.T) { From 9c1906198381aafad9999e4471fcb4b9149aaf4a Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 30 Jun 2020 10:09:54 +0800 Subject: [PATCH 08/13] Add tests for Toggle() --- bool_test.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/bool_test.go b/bool_test.go index 18760b0..6fbd472 100644 --- a/bool_test.go +++ b/bool_test.go @@ -1,6 +1,7 @@ package abool import ( + "math" "sync" "sync/atomic" "testing" @@ -66,6 +67,57 @@ func TestBool(t *testing.T) { } } +func TestToogleMultipleTimes(t *testing.T) { + t.Parallel() + + v := New() + pre := NewBool(!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.IsSet() == v.IsSet() { + t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre.IsSet()) + } + } +} + +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) { repeat := 10000 var wg sync.WaitGroup From 4bc34b54a13d51320d31a0f76837686a170f9b44 Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 30 Jun 2020 10:10:19 +0800 Subject: [PATCH 09/13] Complete the implementation of toggle --- bool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bool.go b/bool.go index 3a3ca64..a1bf1bd 100644 --- a/bool.go +++ b/bool.go @@ -37,7 +37,7 @@ 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. From b830aebdb7a73eb37d7125e4ffa9d2111ac7fe63 Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 30 Jun 2020 10:10:38 +0800 Subject: [PATCH 10/13] Enable parallel testing --- bool_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bool_test.go b/bool_test.go index 6fbd472..bc16f9e 100644 --- a/bool_test.go +++ b/bool_test.go @@ -8,6 +8,8 @@ import ( ) func TestBool(t *testing.T) { + t.Parallel() + v := NewBool(true) if !v.IsSet() { t.Fatal("NewValue(true) failed") @@ -119,6 +121,8 @@ func TestToogleAfterOverflow(t *testing.T) { } func TestRace(t *testing.T) { + t.Parallel() + repeat := 10000 var wg sync.WaitGroup wg.Add(repeat * 4) From 53396ec6fec0e5719991a088997563c4946b5aef Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 7 Jul 2020 15:33:59 +0800 Subject: [PATCH 11/13] Change return type of Toggle to simple bool --- bool.go | 6 +++--- bool_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bool.go b/bool.go index a1bf1bd..2205f9b 100644 --- a/bool.go +++ b/bool.go @@ -49,9 +49,9 @@ func (ab *AtomicBool) SetTo(yes bool) { } } -// Toggle negates boolean atomically and returns a new AtomicBool object which holds previous boolean value. -func (ab *AtomicBool) Toggle() *AtomicBool { - return NewBool(atomic.AddInt32((*int32)(ab), 1)&1 == 0) +// 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 diff --git a/bool_test.go b/bool_test.go index bc16f9e..c35aa16 100644 --- a/bool_test.go +++ b/bool_test.go @@ -64,7 +64,7 @@ func TestBool(t *testing.T) { } prev := v.Toggle() - if v.IsSet() == prev.IsSet() { + if v.IsSet() == prev { t.Fatal("AtomicBool.Toggle() to false failed") } } @@ -73,7 +73,7 @@ func TestToogleMultipleTimes(t *testing.T) { t.Parallel() v := New() - pre := NewBool(!v.IsSet()) + pre := !v.IsSet() for i := 0; i < 100; i++ { v.SetTo(false) for j := 0; j < i; j++ { @@ -85,8 +85,8 @@ func TestToogleMultipleTimes(t *testing.T) { t.Fatalf("AtomicBool.Toogle() doesn't work after %d calls, expected: %v, got %v", i, expected, v.IsSet()) } - if pre.IsSet() == v.IsSet() { - t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre.IsSet()) + if pre == v.IsSet() { + t.Fatalf("AtomicBool.Toogle() returned wrong value at the %dth calls, expected: %v, got %v", i, !v.IsSet(), pre) } } } From 6ddf050e0ee9ac44a8bc0df20dc1690d152a34f5 Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 7 Jul 2020 15:46:54 +0800 Subject: [PATCH 12/13] Refine README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 810b1ae..e21f0c8 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ type Foo struct { } ``` -## Benchmark: +## Benchmark - Go 1.11.5 - OS X 10.14.5 From 74242c7da7a48fa46906b4414cbb44f84a81af4f Mon Sep 17 00:00:00 2001 From: Tevin Zhang Date: Tue, 7 Jul 2020 15:47:35 +0800 Subject: [PATCH 13/13] Add thanks to contributors --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e21f0c8..e188904 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,8 @@ BenchmarkAtomicBoolCAS-4 200000000 7.18 ns/op # <--- This 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