Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement spinlock #334

Merged
merged 1 commit into from
May 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ package util
import (
"errors"
"fmt"
"github.com/qlcchain/go-qlc/config"
"path/filepath"
"strconv"
"strings"

"github.com/qlcchain/go-qlc/config"

"github.com/abiosoft/ishell"
"github.com/fatih/color"
)
Expand Down
6 changes: 3 additions & 3 deletions common/event/event_bus_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"reflect"
"sync"

"github.com/qlcchain/go-qlc/common/hashmap"
hm "github.com/qlcchain/go-qlc/common/sync/hashmap"
)

// DefaultEventBus - box for handlers and callbacks.
Expand Down Expand Up @@ -43,11 +43,11 @@ func New() EventBus {
var (
once sync.Once
eb EventBus
cache *hashmap.HashMap
cache *hm.HashMap
)

func init() {
cache = hashmap.New(50)
cache = hm.New(50)
}

func SimpleEventBus() EventBus {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package hashmap

import "testing"
import (
"testing"
)

func TestListNew(t *testing.T) {
l := NewList()
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions common/sync/spinlock/spinlock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2019 QLC Chain Team
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/

package spinlock

import (
"runtime"
"sync"
"sync/atomic"
)

// SpinLock implements a simple atomic spin lock, the zero value for a SpinLock is an unlocked spinlock.
type SpinLock struct {
_ sync.Mutex // for copy protection compiler warning
f uint32
}

// Lock locks sl. If the lock is already in use, the caller blocks until Unlock is called
func (sl *SpinLock) Lock() {
for !sl.TryLock() {
runtime.Gosched() //allow other goroutines to do stuff.
}
}

// Unlock unlocks sl, unlike [Mutex.Unlock](http://golang.org/pkg/sync/#Mutex.Unlock),
// there's no harm calling it on an unlocked SpinLock
func (sl *SpinLock) Unlock() {
atomic.StoreUint32(&sl.f, 0)
}

// TryLock will try to lock sl and return whether it succeed or not without blocking.
func (sl *SpinLock) TryLock() bool {
return atomic.CompareAndSwapUint32(&sl.f, 0, 1)
}

func (sl *SpinLock) String() string {
if atomic.LoadUint32(&sl.f) == 1 {
return "Locked"
}
return "Unlocked"
}
200 changes: 200 additions & 0 deletions common/sync/spinlock/spinlock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Copyright (c) 2019 QLC Chain Team
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/

package spinlock

import (
"fmt"
"sync"
"testing"
"time"
)

func TestTryLock(t *testing.T) {
var sl SpinLock
if !sl.TryLock() {
t.Error("TryLock failed.")
}
}

func TestLock(t *testing.T) {
var sl SpinLock
sl.Lock()

if sl.TryLock() {
t.Error("TryLock() returned true when it shouldn't have, w t f mate?")
}

sl.Unlock()

if !sl.TryLock() {
t.Error("TryLock() returned false when it shouldn't have.")
}

}

type SpinMap struct {
SpinLock
m map[int]bool
}

func (sm *SpinMap) Add(i int) {
sm.Lock()
sm.m[i] = true
sm.Unlock()
}

func (sm *SpinMap) Get(i int) (b bool) {
sm.Lock()
b = sm.m[i]
sm.Unlock()
return
}

type MutexMap struct {
sync.Mutex
m map[int]bool
}

func (mm *MutexMap) Add(i int) {
mm.Lock()
mm.m[i] = true
mm.Unlock()
}

func (mm *MutexMap) Get(i int) (b bool) {
mm.Lock()
b = mm.m[i]
mm.Unlock()
return
}

type RWMutexMap struct {
sync.RWMutex
m map[int]bool
}

func (mm *RWMutexMap) Add(i int) {
mm.Lock()
mm.m[i] = true
mm.Unlock()
}

func (mm *RWMutexMap) Get(i int) (b bool) {
mm.RLock()
b = mm.m[i]
mm.RUnlock()
return
}

const N = 1e3

var (
sm = SpinMap{m: map[int]bool{}}
mm = MutexMap{m: map[int]bool{}}
rwmm = RWMutexMap{m: map[int]bool{}}
)

func BenchmarkSpinL(b *testing.B) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(N * 2)
for i := 0; i < N; i++ {
go func(i int) {
sm.Add(i)
wg.Done()
}(i)
go sm.Get(i)

go func(i int) {
sm.Get(i - 1)
sm.Get(i + 1)
wg.Done()
}(i)
}
}
wg.Wait()
}

func BenchmarkMutex(b *testing.B) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(N * 2)
for i := 0; i < N; i++ {
go func(i int) {
mm.Add(i)
wg.Done()
}(i)
go mm.Get(i)
go func(i int) {
mm.Get(i - 1)
mm.Get(i + 1)
wg.Done()
}(i)
}
}
wg.Wait()
}

func BenchmarkRWMutex(b *testing.B) {
var wg sync.WaitGroup
for i := 0; i < b.N; i++ {
wg.Add(N * 2)
for i := 0; i < N; i++ {
go func(i int) {
rwmm.Add(i)
wg.Done()
}(i)
go mm.Get(i)
go func(i int) {
rwmm.Get(i - 1)
rwmm.Get(i + 1)
wg.Done()
}(i)
}
}
wg.Wait()
}

func testLock(threads, n int, l sync.Locker) time.Duration {
var wg sync.WaitGroup
wg.Add(threads)

var count1 int
var count2 int

start := time.Now()
for i := 0; i < threads; i++ {
go func() {
for i := 0; i < n; i++ {
l.Lock()
count1++
count2 += 2
l.Unlock()
}
wg.Done()
}()
}
wg.Wait()
dur := time.Since(start)
if count1 != threads*n {
panic("mismatch")
}
if count2 != threads*n*2 {
panic("mismatch")
}
return dur
}

func TestSpinLock(t *testing.T) {
fmt.Printf("[1] spinlock %4.0fms\n", testLock(1, 1000000, &SpinLock{}).Seconds()*1000)
fmt.Printf("[1] mutex %4.0fms\n", testLock(1, 1000000, &sync.Mutex{}).Seconds()*1000)
fmt.Printf("[4] spinlock %4.0fms\n", testLock(4, 1000000, &SpinLock{}).Seconds()*1000)
fmt.Printf("[4] mutex %4.0fms\n", testLock(4, 1000000, &sync.Mutex{}).Seconds()*1000)
fmt.Printf("[8] spinlock %4.0fms\n", testLock(8, 1000000, &SpinLock{}).Seconds()*1000)
fmt.Printf("[8] mutex %4.0fms\n", testLock(8, 1000000, &sync.Mutex{}).Seconds()*1000)
}
5 changes: 3 additions & 2 deletions test/pledge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ package test
import (
"encoding/hex"
"encoding/json"
"github.com/qlcchain/go-qlc/common/types"
"github.com/qlcchain/go-qlc/rpc/api"
"os"
"path/filepath"
"testing"

"github.com/qlcchain/go-qlc/common/types"
"github.com/qlcchain/go-qlc/rpc/api"

"github.com/qlcchain/go-qlc/chain/services"
"github.com/qlcchain/go-qlc/config"
"github.com/qlcchain/go-qlc/ledger/process"
Expand Down
2 changes: 1 addition & 1 deletion trie/node_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
package trie

import (
"github.com/qlcchain/go-qlc/common/hashmap"
"github.com/qlcchain/go-qlc/common/sync/hashmap"
"github.com/qlcchain/go-qlc/common/types"
)

Expand Down
1 change: 1 addition & 0 deletions vm/vmstore/vm_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package vmstore
import (
"bytes"
"errors"

"github.com/qlcchain/go-qlc/trie"

"github.com/dgraph-io/badger"
Expand Down