Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A stackable, Context-aware locking mechanism similar to sync.Mutex (but much better)

License

NotificationsYou must be signed in to change notification settings

tep/sync-ctxlock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

import "toolman.org/sync/ctxlock"

Package ctxlock provides a stackable, Context-aware locking mechanism thatallows locks to be freely requested - without blocking - if a parent in thecall stack already holds the lock. Additionally, since this mechanism isContext aware, all blocked goroutines waiting to acquire a lock will beresumed when the given Context enters a cancelled state.

The common use case is for a set of methods requiring syncronization whereone or more may be called either directly (when it would need to acquire thelock itself) or indirectly (from another method that has previous acquiredthe lock).

As an example, consider the following construct:

type Foo struct {  ctxlock.ContextLock  // other fields}func (f *Foo) One(ctx context.Context) (err error) {  if ctx, err = f.Lock(ctx); err != nil {    return err  }  defer f.Unlock(ctx)  // ...do things...  if f.other() {    if err := f.Two(ctx); err != nil {      return err    }    // ...lock is still held here  }  return nil}func f *Foo) Two(ctx context.Context) (err error) {  if ctx, err = f.Lock(ctx); err != nil {    return err  }  defer f.Unlock(ctx)  // ...do different things...  return nil}

In this example, type Foo embeds a ContextLock which is referenced by themethods One and Two, both of which may be called directly where each willacquire the embedded lock. However, under certain conditions, One alsocalls Two. This would result in a deadlock if type Foo used sync.Mutex asits locking mechanism since Two would block indefinitely waiting to acquirethe lock held by its caller, method One.

This deadlock is avoided by ContextLock since the Context passed to methodTwo's call to f.Lock is the same Context returned by One's call to f.Lockand it informs ContextLock to not block because the lock holder is in thecurrent call stack.

When Two is called directly, its deferred call to f.Unlock will release thelock. But when Two is called by One, the ContextLock remains held until Oneitself also returns (making its deferred call to f.Unlock). This lockingpattern functions as expected to an arbitrary depth.

Note that this pattern remains valid as long as each method in the callstack is in the same thread of execution. Synchronization fails for in-stackcalls to Lock made from goroutines other than the one that originallyacquired the lock. See the Clear method for how to avoid this problem.

ctxlock.go

typeContextLockstruct {// contains filtered or unexported fields}

ContextLock is a Context aware mutex that avoids blocking if the lock ispresently held by a parent in its call stack. This behavior is accomplishedthrough reference counts tracked in the chain of Contexts provided to andreturned by calls to ContextLock's methods.Note that ContextLock is initialized lazily so its zero value is valid andits lock is unheld.

func (*ContextLock)Lock

func (c \*ContextLock)Lock(ctx context.Context) (context.Context,error)

Lock locks the receiver c. If the receiver is not currently locked, the lockis acquired and this method returns immediately. If the reciever is alreadylocked and the provided context indicates that the lock holder is a parentin the call stack, Lock will not block but will return immediately witha new Context updated to reflect an increased lock reference count. Ifhowever, the provided context is unaware of the receiver and its lock isalready held, Lock will block until the lock is released or ctx entersa cancelled state.

If the returned error is nil, the lock has been successfully acquired. Thiserror is only non-nil if the provided context has entered the cancelledstate and the error value will be the results from ctx.Err(). Note howeverthat regardless of the error value, the returned Context will never be nil.

func (*ContextLock)Unlock

func (c \*ContextLock)Unlock(ctx context.Context) (context.Context,error)

Unlock decrements the reference count for the receiver's lock. The lockwill remain held as long as this reference count is greater than zero.Once the reference count falls to zero, the lock will be released.

The returned error will only be non-nil if the provided Context has enteredthe cancelled state and the error value will be the results from ctx.Err().The returned Context will never be nil.

func (*ContextLock)Clear

func (c \*ContextLock)Clear(ctx context.Context) (context.Context,error)

Clear checks the receiver's current lock state and, if the lock is currentlyheld, returns a copy of ctx with its lock reference count cleared to zero --but leaves the receiver in a locked state. If the lock is not currently held,ctx itself is returned.

The common use case for this method is when a lock holder needs to spawn newgoroutines that will execute methods requiring synchronization using thereceiver's lock. In this situation the lock holder can call Clear to geta new Context that can be used in it's spawned goroutines. As long as thereceiver's lock remains held, the child goroutines will block in their owncalls to Lock.

The returned error will only be non-nil if the provided Context has enteredthe cancelled state and the error value will be the results from ctx.Err().The returned Context will never be nil.

About

A stackable, Context-aware locking mechanism similar to sync.Mutex (but much better)

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp