Skip to content

Commit

Permalink
feat: refactor whole structure consisting with sync package (#2)
Browse files Browse the repository at this point in the history
- extract the interface consisting with sync.Mutex and sync.RWMutex .
- Refactor the coding style with the convention in Go.
- Add examples.
  • Loading branch information
viney-shih authored Jul 24, 2021
1 parent f6d51ab commit a8c3844
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 100 deletions.
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# go-lock

[![GoDoc](https://godoc.org/github.com/viney-shih/go-lock?status.svg)](https://godoc.org/github.com/viney-shih/go-lock)
[![GoDev](https://img.shields.io/badge/go.dev-doc-007d9c?style=flat-square&logo=read-the-docs)](https://pkg.go.dev/github.com/viney-shih/go-lock?tab=doc)
[![Build Status](https://travis-ci.com/viney-shih/go-lock.svg?branch=master)](https://travis-ci.com/github/viney-shih/go-lock)
[![Go Report Card](https://goreportcard.com/badge/github.com/viney-shih/go-lock)](https://goreportcard.com/report/github.com/viney-shih/go-lock)
Expand Down Expand Up @@ -41,39 +40,51 @@ import (
)

func main() {
// set RWMutex with CAS mechanism (CASMutex).
var rwMut lock.RWMutex = lock.NewCASMutex()
// set default value
casMut := lock.NewCASMutex()
count := int32(0)

casMut.Lock()
// block here
rwMut.Lock()
go func() {
time.Sleep(50 * time.Millisecond)
fmt.Println("Now is", atomic.AddInt32(&count, 1)) // Now is 1
casMut.Unlock()
rwMut.Unlock()
}()

// waiting for previous goroutine releasing the lock, and locking it again
casMut.Lock()
rwMut.Lock()
fmt.Println("Now is", atomic.AddInt32(&count, 2)) // Now is 3

// TryLock without blocking
fmt.Println("Return", casMut.TryLock()) // Return false, because the lock is not released.
// Return false, because the lock is not released.
fmt.Println("Return", rwMut.TryLock())

// RTryLockWithTimeout without blocking
fmt.Println("Return", casMut.RTryLockWithTimeout(50*time.Millisecond)) // Return false, because the lock is not released.
// Return false, because the lock is not released.
fmt.Println("Return", rwMut.RTryLockWithTimeout(50*time.Millisecond))

// TryLockWithContext without blocking
ctx, cancel := context.WithTimeout(context.TODO(), 50*time.Millisecond)
defer cancel()
// Return false, because the lock is not released.
fmt.Println("Return", rwMut.TryLockWithContext(ctx))

// release the lock in the end.
casMut.Unlock()
rwMut.Unlock()

// Output:
// Now is 1
// Now is 3
// Return false
// Return false
// Return false
}
```

- [More examples](./cas_test.go)
- [Full API documentation](https://godoc.org/github.com/viney-shih/go-lock)
- [Full API documentation](https://pkg.go.dev/github.com/viney-shih/go-lock?tab=doc)

## Benchmarks
- Run on MacBook Pro (Retina, 15-inch, Mid 2015) 2.5 GHz Quad-Core Intel Core i7 16 GB 1600 MHz DDR3 using Go 1.15.2
Expand Down
119 changes: 58 additions & 61 deletions cas.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,37 +11,22 @@ import (
"golang.org/x/sync/semaphore"
)

// CASMutex provides interfaces of read-write spinlock and read-write trylock with CAS mechanism.
type CASMutex interface {
// Lock acquires the write lock.
// If it is currently held by others, Lock will wait until it has a chance to acquire it.
Lock()
// TryLock attempts to acquire the write lock without blocking.
// Return false if someone is holding it now.
TryLock() bool
// TryLockWithTimeout attempts to acquire the write lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
TryLockWithTimeout(time.Duration) bool
// TryLockWithContext attempts to acquire the write lock, blocking until resources
// are available or ctx is done (timeout or cancellation)
TryLockWithContext(ctx context.Context) bool
// Unlock releases the write lock
Unlock()

// RLock acquires the read lock.
// If it is currently held by others writing, RLock will wait until it has a chance to acquire it.
RLock()
// RTryLock attempts to acquire the read lock without blocking.
// Return false if someone is writing it now.
RTryLock() bool
// RTryLockWithTimeout attempts to acquire the read lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
RTryLockWithTimeout(time.Duration) bool
// RTryLockWithContext attempts to acquire the read lock, blocking until resources
// are available or ctx is done (timeout or cancellation)
RTryLockWithContext(ctx context.Context) bool
// RUnlock releases the read lock
RUnlock()
// CASMutex is the struct implementing RWMutex with CAS mechanism.
type CASMutex struct {
state casState
turnstile *semaphore.Weighted

broadcastChan chan struct{}
broadcastMut sync.RWMutex
}

// NewCASMutex returns CASMutex
func NewCASMutex() *CASMutex {
return &CASMutex{
state: casStateNoLock,
turnstile: semaphore.NewWeighted(1),
broadcastChan: make(chan struct{}),
}
}

type casState int32
Expand All @@ -53,7 +38,7 @@ const (
casStateReadLock // >= 1
)

func (m *casMutex) getState(n int32) casState {
func (m *CASMutex) getState(n int32) casState {
switch st := casState(n); {
case st == casStateWriteLock:
fallthrough
Expand All @@ -67,22 +52,14 @@ func (m *casMutex) getState(n int32) casState {
}
}

type casMutex struct {
state casState
turnstile *semaphore.Weighted

broadcastChan chan struct{}
broadcastMut sync.RWMutex
}

func (m *casMutex) listen() <-chan struct{} {
func (m *CASMutex) listen() <-chan struct{} {
m.broadcastMut.RLock()
defer m.broadcastMut.RUnlock()

return m.broadcastChan
}

func (m *casMutex) broadcast() {
func (m *CASMutex) broadcast() {
newCh := make(chan struct{})

m.broadcastMut.Lock()
Expand All @@ -93,7 +70,7 @@ func (m *casMutex) broadcast() {
close(ch)
}

func (m *casMutex) tryLock(ctx context.Context) bool {
func (m *CASMutex) tryLock(ctx context.Context) bool {
for {
broker := m.listen()
if atomic.CompareAndSwapInt32(
Expand All @@ -118,7 +95,9 @@ func (m *casMutex) tryLock(ctx context.Context) bool {
}
}

func (m *casMutex) TryLockWithContext(ctx context.Context) bool {
// TryLockWithContext attempts to acquire the lock, blocking until resources
// are available or ctx is done (timeout or cancellation).
func (m *CASMutex) TryLockWithContext(ctx context.Context) bool {
if err := m.turnstile.Acquire(ctx, 1); err != nil {
// Acquire failed due to timeout or cancellation
return false
Expand All @@ -129,13 +108,17 @@ func (m *casMutex) TryLockWithContext(ctx context.Context) bool {
return m.tryLock(ctx)
}

func (m *casMutex) Lock() {
// Lock acquires the lock.
// If it is currently held by others, Lock will wait until it has a chance to acquire it.
func (m *CASMutex) Lock() {
ctx := context.Background()

m.TryLockWithContext(ctx)
}

func (m *casMutex) TryLock() bool {
// TryLock attempts to acquire the lock without blocking.
// Return false if someone is holding it now.
func (m *CASMutex) TryLock() bool {
if !m.turnstile.TryAcquire(1) {
return false
}
Expand All @@ -145,14 +128,17 @@ func (m *casMutex) TryLock() bool {
return m.tryLock(nil)
}

func (m *casMutex) TryLockWithTimeout(duration time.Duration) bool {
// TryLockWithTimeout attempts to acquire the lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
func (m *CASMutex) TryLockWithTimeout(duration time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()

return m.TryLockWithContext(ctx)
}

func (m *casMutex) Unlock() {
// Unlock releases the lock.
func (m *CASMutex) Unlock() {
if ok := atomic.CompareAndSwapInt32(
(*int32)(unsafe.Pointer(&m.state)),
int32(casStateWriteLock),
Expand All @@ -164,7 +150,7 @@ func (m *casMutex) Unlock() {
m.broadcast()
}

func (m *casMutex) rTryLock(ctx context.Context) bool {
func (m *CASMutex) rTryLock(ctx context.Context) bool {
for {
broker := m.listen()
n := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.state)))
Expand Down Expand Up @@ -203,7 +189,9 @@ func (m *casMutex) rTryLock(ctx context.Context) bool {
}
}

func (m *casMutex) RTryLockWithContext(ctx context.Context) bool {
// RTryLockWithContext attempts to acquire the read lock, blocking until resources
// are available or ctx is done (timeout or cancellation).
func (m *CASMutex) RTryLockWithContext(ctx context.Context) bool {
if err := m.turnstile.Acquire(ctx, 1); err != nil {
// Acquire failed due to timeout or cancellation
return false
Expand All @@ -214,13 +202,17 @@ func (m *casMutex) RTryLockWithContext(ctx context.Context) bool {
return m.rTryLock(ctx)
}

func (m *casMutex) RLock() {
// RLock acquires the read lock.
// If it is currently held by others writing, RLock will wait until it has a chance to acquire it.
func (m *CASMutex) RLock() {
ctx := context.Background()

m.RTryLockWithContext(ctx)
}

func (m *casMutex) RTryLock() bool {
// RTryLock attempts to acquire the read lock without blocking.
// Return false if someone is writing it now.
func (m *CASMutex) RTryLock() bool {
if !m.turnstile.TryAcquire(1) {
return false
}
Expand All @@ -230,14 +222,17 @@ func (m *casMutex) RTryLock() bool {
return m.rTryLock(nil)
}

func (m *casMutex) RTryLockWithTimeout(duration time.Duration) bool {
// RTryLockWithTimeout attempts to acquire the read lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
func (m *CASMutex) RTryLockWithTimeout(duration time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()

return m.RTryLockWithContext(ctx)
}

func (m *casMutex) RUnlock() {
// RUnlock releases the read lock.
func (m *CASMutex) RUnlock() {
n := atomic.AddInt32((*int32)(unsafe.Pointer(&m.state)), -1)
switch m.getState(n) {
case casStateUndefined, casStateWriteLock:
Expand All @@ -247,11 +242,13 @@ func (m *casMutex) RUnlock() {
}
}

// NewCASMutex returns CASMutex lock
func NewCASMutex() CASMutex {
return &casMutex{
state: casStateNoLock,
turnstile: semaphore.NewWeighted(1),
broadcastChan: make(chan struct{}),
}
// RLocker returns a Locker interface that implements the Lock and Unlock methods
// by calling CASMutex.RLock and CASMutex.RUnlock.
func (m *CASMutex) RLocker() sync.Locker {
return (*rlocker)(m)
}

type rlocker CASMutex

func (r *rlocker) Lock() { (*CASMutex)(r).RLock() }
func (r *rlocker) Unlock() { (*CASMutex)(r).RUnlock() }
52 changes: 22 additions & 30 deletions chan.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,32 @@ import (
"time"
)

// ChanMutex provides interfaces of spinlock and trylock implemented by channel.
type ChanMutex interface {
// Lock acquires the lock.
// If it is currently held by others, Lock will wait until it has a chance to acquire it.
Lock()
// TryLock attempts to acquire the lock without blocking.
// Return false if someone is holding it now.
TryLock() bool
// TryLockWithTimeout attempts to acquire the lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
TryLockWithTimeout(time.Duration) bool
// TryLockWithContext attempts to acquire the lock, blocking until resources
// are available or ctx is done (timeout or cancellation)
TryLockWithContext(ctx context.Context) bool
// Unlock releases the lock
Unlock()
// ChanMutex is the struct implementing Mutex by channel.
type ChanMutex struct {
lockChan chan struct{}
}

type chanMutex struct {
lockChan chan struct{}
// NewChanMutex returns ChanMutex.
func NewChanMutex() *ChanMutex {
return &ChanMutex{
lockChan: make(chan struct{}, 1),
}
}

func (m *chanMutex) Lock() {
// Lock acquires the lock.
// If it is currently held by others, Lock will wait until it has a chance to acquire it.
func (m *ChanMutex) Lock() {
m.lockChan <- struct{}{}
}

func (m *chanMutex) Unlock() {
// Unlock releases the lock.
func (m *ChanMutex) Unlock() {
<-m.lockChan
}

func (m *chanMutex) TryLock() bool {
// TryLock attempts to acquire the lock without blocking.
// Return false if someone is holding it now.
func (m *ChanMutex) TryLock() bool {
select {
case m.lockChan <- struct{}{}:
return true
Expand All @@ -44,7 +39,9 @@ func (m *chanMutex) TryLock() bool {
}
}

func (m *chanMutex) TryLockWithContext(ctx context.Context) bool {
// TryLockWithContext attempts to acquire the lock, blocking until resources
// are available or ctx is done (timeout or cancellation).
func (m *ChanMutex) TryLockWithContext(ctx context.Context) bool {
select {
case m.lockChan <- struct{}{}:
return true
Expand All @@ -54,16 +51,11 @@ func (m *chanMutex) TryLockWithContext(ctx context.Context) bool {
}
}

func (m *chanMutex) TryLockWithTimeout(duration time.Duration) bool {
// TryLockWithTimeout attempts to acquire the lock within a period of time.
// Return false if spending time is more than duration and no chance to acquire it.
func (m *ChanMutex) TryLockWithTimeout(duration time.Duration) bool {
ctx, cancel := context.WithTimeout(context.Background(), duration)
defer cancel()

return m.TryLockWithContext(ctx)
}

// NewChanMutex returns ChanMutex lock
func NewChanMutex() ChanMutex {
return &chanMutex{
lockChan: make(chan struct{}, 1),
}
}
Loading

0 comments on commit a8c3844

Please sign in to comment.