Merge pull request #6 from schicho/hide-datastructure

Reintroduce Toggle
This commit is contained in:
Tevin 2022-05-30 21:27:34 +08:00 committed by GitHub
commit ef804a4993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 7 deletions

28
bool.go
View File

@ -24,21 +24,23 @@ func NewBool(ok bool) *AtomicBool {
// AtomicBool is an atomic Boolean. // AtomicBool is an atomic Boolean.
// Its methods are all atomic, thus safe to be called by multiple goroutines simultaneously. // Its methods are all atomic, thus safe to be called by multiple goroutines simultaneously.
// Note: When embedding into a struct one should always use *AtomicBool to avoid copy. // Note: When embedding into a struct one should always use *AtomicBool to avoid copy.
type AtomicBool int32 type AtomicBool struct {
boolean int32
}
// Set sets the Boolean to true. // Set sets the Boolean to true.
func (ab *AtomicBool) Set() { func (ab *AtomicBool) Set() {
atomic.StoreInt32((*int32)(ab), 1) atomic.StoreInt32(&ab.boolean, 1)
} }
// UnSet sets the Boolean to false. // UnSet sets the Boolean to false.
func (ab *AtomicBool) UnSet() { func (ab *AtomicBool) UnSet() {
atomic.StoreInt32((*int32)(ab), 0) atomic.StoreInt32(&ab.boolean, 0)
} }
// IsSet returns whether the Boolean is true. // IsSet returns whether the Boolean is true.
func (ab *AtomicBool) IsSet() bool { func (ab *AtomicBool) IsSet() bool {
return atomic.LoadInt32((*int32)(ab)) == 1 return atomic.LoadInt32(&ab.boolean)&1 == 1
} }
// IsNotSet returns whether the Boolean is false. // IsNotSet returns whether the Boolean is false.
@ -49,9 +51,21 @@ func (ab *AtomicBool) IsNotSet() bool {
// SetTo sets the boolean with given Boolean. // SetTo sets the boolean with given Boolean.
func (ab *AtomicBool) SetTo(yes bool) { func (ab *AtomicBool) SetTo(yes bool) {
if yes { if yes {
atomic.StoreInt32((*int32)(ab), 1) atomic.StoreInt32(&ab.boolean, 1)
} else { } else {
atomic.StoreInt32((*int32)(ab), 0) atomic.StoreInt32(&ab.boolean, 0)
}
}
// Toggle inverts the Boolean then returns the value before inverting.
// Based on: https://github.com/uber-go/atomic/blob/3504dfaa1fa414923b1c8693f45d2f6931daf229/bool_ext.go#L40
func (ab *AtomicBool) Toggle() bool {
var old bool
for {
old = ab.IsSet()
if ab.SetToIf(old, !old) {
return old
}
} }
} }
@ -65,7 +79,7 @@ func (ab *AtomicBool) SetToIf(old, new bool) (set bool) {
if new { if new {
n = 1 n = 1
} }
return atomic.CompareAndSwapInt32((*int32)(ab), o, n) return atomic.CompareAndSwapInt32(&ab.boolean, o, n)
} }
// MarshalJSON behaves the same as if the AtomicBool is a builtin.bool. // MarshalJSON behaves the same as if the AtomicBool is a builtin.bool.

View File

@ -72,6 +72,43 @@ func TestSetTo(t *testing.T) {
} }
} }
func TestToggle(t *testing.T) {
t.Parallel()
v := New()
_ = 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 TestRace(t *testing.T) { func TestRace(t *testing.T) {
repeat := 10000 repeat := 10000
var wg sync.WaitGroup var wg sync.WaitGroup
@ -104,6 +141,22 @@ func TestRace(t *testing.T) {
wg.Wait() wg.Wait()
} }
func TestSetToIfAfterMultipleToggles(t *testing.T) {
v := New() // false
v.Toggle() // true
v.Toggle() // false
v.Toggle() // true
// As v is true, it should now be flipped to false
v.SetToIf(true, false)
expected := false
if v.IsSet() != expected {
t.Fatalf("Toggling the value atleast 3 times, until it's true, `SetToIf(true, false)` should flip v to false, expected: %v, got %v", expected, v.IsSet())
}
}
func TestJSONCompatibleWithBuiltinBool(t *testing.T) { func TestJSONCompatibleWithBuiltinBool(t *testing.T) {
for _, value := range []bool{true, false} { for _, value := range []bool{true, false} {
// Test bool -> bytes -> AtomicBool // Test bool -> bytes -> AtomicBool
@ -181,6 +234,14 @@ func ExampleAtomicBool() {
cond.SetToIf(new, old) // Sets to `new` only if the Boolean matches the `old`, returns whether succeeded cond.SetToIf(new, old) // Sets to `new` only if the Boolean matches the `old`, returns whether succeeded
} }
func BenchmarkAtomicBoolToggle(b *testing.B) {
v := New()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = v.Toggle()
}
}
// Benchmark Read // Benchmark Read
func BenchmarkMutexRead(b *testing.B) { func BenchmarkMutexRead(b *testing.B) {