mirror of https://github.com/tevino/abool.git
Add v2
Go requires a separated folder for v2+ to keep the compatibility for tools that are not version-aware. https://go.dev/blog/v2-go-modules
This commit is contained in:
parent
4c6c31b893
commit
7170548ea7
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Tevin Zhang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,87 @@
|
|||
// Package abool provides atomic Boolean type for cleaner code and
|
||||
// better performance.
|
||||
package abool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// New creates an AtomicBool with default set to false.
|
||||
func New() *AtomicBool {
|
||||
return new(AtomicBool)
|
||||
}
|
||||
|
||||
// NewBool creates an AtomicBool with given default value.
|
||||
func NewBool(ok bool) *AtomicBool {
|
||||
ab := New()
|
||||
if ok {
|
||||
ab.Set()
|
||||
}
|
||||
return ab
|
||||
}
|
||||
|
||||
// AtomicBool is an atomic Boolean.
|
||||
// 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.
|
||||
type AtomicBool int32
|
||||
|
||||
// Set sets the Boolean to true.
|
||||
func (ab *AtomicBool) Set() {
|
||||
atomic.StoreInt32((*int32)(ab), 1)
|
||||
}
|
||||
|
||||
// UnSet sets the Boolean to false.
|
||||
func (ab *AtomicBool) UnSet() {
|
||||
atomic.StoreInt32((*int32)(ab), 0)
|
||||
}
|
||||
|
||||
// IsSet returns whether the Boolean is true.
|
||||
func (ab *AtomicBool) IsSet() bool {
|
||||
return atomic.LoadInt32((*int32)(ab)) == 1
|
||||
}
|
||||
|
||||
// IsNotSet returns whether the Boolean is false.
|
||||
func (ab *AtomicBool) IsNotSet() bool {
|
||||
return !ab.IsSet()
|
||||
}
|
||||
|
||||
// SetTo sets the boolean with given Boolean.
|
||||
func (ab *AtomicBool) SetTo(yes bool) {
|
||||
if yes {
|
||||
atomic.StoreInt32((*int32)(ab), 1)
|
||||
} else {
|
||||
atomic.StoreInt32((*int32)(ab), 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) {
|
||||
var o, n int32
|
||||
if old {
|
||||
o = 1
|
||||
}
|
||||
if new {
|
||||
n = 1
|
||||
}
|
||||
return atomic.CompareAndSwapInt32((*int32)(ab), o, n)
|
||||
}
|
||||
|
||||
// MarshalJSON behaves the same as if the AtomicBool is a builtin.bool.
|
||||
// NOTE: There's no lock during the process, usually it shouldn't be called with other methods in parallel.
|
||||
func (ab *AtomicBool) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ab.IsSet())
|
||||
}
|
||||
|
||||
// UnmarshalJSON behaves the same as if the AtomicBool is a builtin.bool.
|
||||
// NOTE: There's no lock during the process, usually it shouldn't be called with other methods in parallel.
|
||||
func (ab *AtomicBool) UnmarshalJSON(b []byte) error {
|
||||
var v bool
|
||||
err := json.Unmarshal(b, &v)
|
||||
|
||||
if err == nil {
|
||||
ab.SetTo(v)
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package abool
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDefaultValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := New()
|
||||
if v.IsSet() {
|
||||
t.Fatal("Empty value of AtomicBool should be false")
|
||||
}
|
||||
|
||||
v = NewBool(true)
|
||||
if !v.IsSet() {
|
||||
t.Fatal("NewValue(true) should be true")
|
||||
}
|
||||
|
||||
v = NewBool(false)
|
||||
if v.IsSet() {
|
||||
t.Fatal("NewValue(false) should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNotSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := New()
|
||||
|
||||
if v.IsSet() == v.IsNotSet() {
|
||||
t.Fatal("AtomicBool.IsNotSet() should be the opposite of IsSet()")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetUnSet(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := New()
|
||||
|
||||
v.Set()
|
||||
if !v.IsSet() {
|
||||
t.Fatal("AtomicBool.Set() failed")
|
||||
}
|
||||
|
||||
v.UnSet()
|
||||
if v.IsSet() {
|
||||
t.Fatal("AtomicBool.UnSet() failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetTo(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := New()
|
||||
|
||||
v.SetTo(true)
|
||||
if !v.IsSet() {
|
||||
t.Fatal("AtomicBool.SetTo(true) failed")
|
||||
}
|
||||
|
||||
v.SetTo(false)
|
||||
if v.IsSet() {
|
||||
t.Fatal("AtomicBool.SetTo(false) failed")
|
||||
}
|
||||
|
||||
if set := v.SetToIf(true, false); set || v.IsSet() {
|
||||
t.Fatal("AtomicBool.SetTo(true, false) failed")
|
||||
}
|
||||
|
||||
if set := v.SetToIf(false, true); !set || !v.IsSet() {
|
||||
t.Fatal("AtomicBool.SetTo(false, true) failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRace(t *testing.T) {
|
||||
repeat := 10000
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(repeat * 3)
|
||||
v := New()
|
||||
|
||||
// Writer
|
||||
go func() {
|
||||
for i := 0; i < repeat; i++ {
|
||||
v.Set()
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
|
||||
// Reader
|
||||
go func() {
|
||||
for i := 0; i < repeat; i++ {
|
||||
v.IsSet()
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
|
||||
// Writer
|
||||
go func() {
|
||||
for i := 0; i < repeat; i++ {
|
||||
v.UnSet()
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestJSONCompatibleWithBuiltinBool(t *testing.T) {
|
||||
for _, value := range []bool{true, false} {
|
||||
// Test bool -> bytes -> AtomicBool
|
||||
|
||||
// act 1. bool -> bytes
|
||||
buf, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal(%t) failed: %s", value, err)
|
||||
}
|
||||
|
||||
// act 2. bytes -> AtomicBool
|
||||
//
|
||||
// Try to unmarshall the JSON byte slice
|
||||
// of a normal boolean into an AtomicBool
|
||||
//
|
||||
// Create an AtomicBool with the oppsite default to ensure the unmarshal process did the work
|
||||
ab := NewBool(!value)
|
||||
err = json.Unmarshal(buf, ab)
|
||||
if err != nil {
|
||||
t.Fatalf(`json.Unmarshal("%s", %T) failed: %s`, buf, ab, err)
|
||||
}
|
||||
// assert
|
||||
if ab.IsSet() != value {
|
||||
t.Fatalf("Expected AtomicBool to represent %t but actual value was %t", value, ab.IsSet())
|
||||
}
|
||||
|
||||
// Test AtomicBool -> bytes -> bool
|
||||
|
||||
// act 3. AtomicBool -> bytes
|
||||
buf, err = json.Marshal(ab)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal(%T) failed: %s", ab, err)
|
||||
}
|
||||
|
||||
// using the opposite value for the same reason as the former case
|
||||
b := ab.IsNotSet()
|
||||
// act 4. bytes -> bool
|
||||
err = json.Unmarshal(buf, &b)
|
||||
if err != nil {
|
||||
t.Fatalf(`json.Unmarshal("%s", %T) failed: %s`, buf, &b, err)
|
||||
}
|
||||
// assert
|
||||
if b != ab.IsSet() {
|
||||
t.Fatalf(`json.Unmarshal("%s", %T) didn't work, expected %t, got %t`, buf, ab, ab.IsSet(), b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalJSONErrorNoWrite(t *testing.T) {
|
||||
for _, val := range []bool{true, false} {
|
||||
ab := NewBool(val)
|
||||
oldVal := ab.IsSet()
|
||||
buf := []byte("invalid-json")
|
||||
err := json.Unmarshal(buf, ab)
|
||||
if err == nil {
|
||||
t.Fatalf(`Error expected from json.Unmarshal("%s", %T)`, buf, ab)
|
||||
}
|
||||
if oldVal != ab.IsSet() {
|
||||
t.Fatal("Failed json.Unmarshal modified the value of AtomicBool which is not expected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleAtomicBool() {
|
||||
cond := New() // default to false
|
||||
any := true
|
||||
old := any
|
||||
new := !any
|
||||
|
||||
cond.Set() // Sets to true
|
||||
cond.IsSet() // Returns true
|
||||
cond.UnSet() // Sets to false
|
||||
cond.IsNotSet() // Returns true
|
||||
cond.SetTo(any) // Sets to whatever you want
|
||||
cond.SetToIf(new, old) // Sets to `new` only if the Boolean matches the `old`, returns whether succeeded
|
||||
}
|
||||
|
||||
// Benchmark Read
|
||||
|
||||
func BenchmarkMutexRead(b *testing.B) {
|
||||
var m sync.RWMutex
|
||||
var v bool
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.RLock()
|
||||
_ = v
|
||||
m.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtomicValueRead(b *testing.B) {
|
||||
var v atomic.Value
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = v.Load() != nil
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtomicBoolRead(b *testing.B) {
|
||||
v := New()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = v.IsSet()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark Write
|
||||
|
||||
func BenchmarkMutexWrite(b *testing.B) {
|
||||
var m sync.RWMutex
|
||||
var v bool
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.RLock()
|
||||
v = true
|
||||
m.RUnlock()
|
||||
}
|
||||
b.StopTimer()
|
||||
_ = v
|
||||
}
|
||||
|
||||
func BenchmarkAtomicValueWrite(b *testing.B) {
|
||||
var v atomic.Value
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.Store(true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtomicBoolWrite(b *testing.B) {
|
||||
v := New()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.Set()
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark CAS
|
||||
|
||||
func BenchmarkMutexCAS(b *testing.B) {
|
||||
var m sync.RWMutex
|
||||
var v bool
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m.Lock()
|
||||
if !v {
|
||||
v = true
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtomicBoolCAS(b *testing.B) {
|
||||
v := New()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.SetToIf(false, true)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue