Last active
August 29, 2015 13:56
-
-
Save sidazhang/9100294 to your computer and use it in GitHub Desktop.
Semaphore that times out if it is not unlocked within a time frame
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"sync" | |
"sync/atomic" | |
"time" | |
) | |
type Empty struct { | |
} | |
type Glock struct { | |
Locks map[string]*Lock | |
LocksLock sync.RWMutex | |
} | |
func NewGlock() *Glock { | |
return &Glock{make(map[string]*Lock), sync.RWMutex{}} | |
} | |
// lock and check if a matching lock exists | |
// if it does return it | |
// if not create a new lock | |
// note: matching lock requires the same key and the same size; if size is different, it will be | |
// replaced | |
func (g *Glock) GetLock(key string, size int) *Lock { | |
g.LocksLock.RLock() | |
l, ok := g.Locks[key] | |
g.LocksLock.RUnlock() | |
// in most cases a lock already exists so we use a read lock | |
// to confirm that | |
if ok && cap(l.Semaphore) == size { | |
return l | |
} else { | |
// in this case a lock does not exist | |
// let's acquire write lock and check again and see if someone else created one already | |
g.LocksLock.Lock() | |
if l, ok := g.Locks[key]; ok && cap(l.Semaphore) == size { | |
// someone created one in the meantime, let's return that | |
return l | |
} else { | |
// lock still does not exist; let's create one | |
l := &Lock{make(chan Empty, size), make(map[int64]bool), int64(1), sync.Mutex{}} | |
g.Locks[key] = l | |
return l | |
} | |
g.LocksLock.Unlock() | |
} | |
return nil | |
} | |
// note: no overflow on int64 - we will probably never get to the end of int64 | |
type Lock struct { | |
Semaphore chan Empty | |
TimeoutSet map[int64]bool | |
Id int64 | |
TimeoutSetLock sync.Mutex | |
} | |
// increment id | |
func (b *Lock) Lock() int64 { | |
id := atomic.AddInt64(&b.Id, 1) | |
b.TimeoutSetLock.Lock() | |
// increment the id each time | |
// impossible to get a collision | |
// so we don't need to check whether key exists | |
b.TimeoutSet[id] = true | |
b.TimeoutSetLock.Unlock() | |
time.AfterFunc(1000*time.Millisecond, func() { | |
b.Unlock(id) | |
}) | |
return id | |
} | |
// Blocking lock | |
func (b *Lock) BLock() int64 { | |
b.Semaphore <- Empty{} | |
return b.Lock() | |
} | |
// Non Blocking lock | |
func (b *Lock) NLock() int64 { | |
select { | |
case b.Semaphore <- Empty{}: | |
return b.Lock() | |
// it is full - move on | |
default: | |
// we initialize each lock at 1 | |
// id 0 means that the lock is full | |
return int64(0) | |
} | |
return int64(0) | |
} | |
func (b *Lock) Unlock(id int64) { | |
b.TimeoutSetLock.Lock() | |
n := len(b.TimeoutSet) | |
delete(b.TimeoutSet, id) | |
deleted := n != len(b.TimeoutSet) | |
b.TimeoutSetLock.Unlock() | |
if deleted { | |
<-b.Semaphore | |
} | |
} | |
func main() { | |
g := NewGlock() | |
l := g.GetLock("SomeKey", 2) | |
for i := 0; i < 10; i++ { | |
k := l.BLock() | |
fmt.Println(k) | |
} | |
// now let's wait all of these locks to timeout | |
time.Sleep(2 * time.Second) | |
// we are now starting fresh | |
for i := 0; i < 10; i++ { | |
k := l.NLock() | |
if k == int64(0) { | |
fmt.Println("It is full") | |
} | |
} | |
fmt.Println("done") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment