I've been playing with Google's go language for the last week or so, and I have to say its a lovely language. I generally grab the mailing list for anything new I get into, and today someone was looking for a way to wait for a set of go-routines to complete before continuing on in the main thread. Seeing as how common this sort of thing is, I decided to write up a quick library for it.
To use the library first issue a dispatch.New()
to acquire a new dispatch mechanism. Then attach any number ( technically upto uint
) of processes to it it via dispatchInstance.Go( func(){ other_process_here() } )
. Any number of other processes ( still technically uint
) can wait on the runners to finish before executing.
Anyway, here it is, and hope it's useful for someone.
In dispatch.go :
package dispatch
import "sync"
type Manager interface {
Go( func() )
Wait()
}
type manager struct {
lock sync.Mutex
running uint
waiting uint
wakeup chan bool
}
func New() *manager {
m := new(manager)
m.wakeup = make(chan bool)
return m
}
func (m *manager) Go( fn func() ) {
m.lock.Lock()
m.running++
m.lock.Unlock()
go func(){
fn()
m.lock.Lock()
m.running--
if (m.running == 0) && (m.waiting > 0) {
oc := m.wakeup
nc := make(chan bool)
i := m.waiting
go func(){
for ; i > 0 ; i-- {
oc <- true
}
}()
m.wakeup = nc
m.waiting = 0
}
m.lock.Unlock()
}()
}
func (m *manager) Wait() {
wait := false
m.lock.Lock()
if m.running > 0 {
m.waiting++
wait = true
}
m.lock.Unlock()
if wait {
<- m.wakeup
}
}
And some example usage in main.go :
package main
import "fmt"
import "rand"
import "time"
import "./dispatch"
func main () {
w := dispatch.New()
for i := 0 ; i < 100 ; i++ {
c := i
w.Go( func(){
time.Sleep( rand.Int63n( 1000000000 ) )
fmt.Print( c , "\n" )
w.Go( func(){
time.Sleep( rand.Int63n( 1000000000 ) )
fmt.Print( c , " - second effect\n")
})
})
}
fmt.Print( "All Launched\n" )
w2 := dispatch.New()
for i := 0 ; i < 5 ; i++ {
c := i
w2.Go( func(){
w.Wait()
time.Sleep( rand.Int63n( 1000000000 ) )
fmt.Print("[ " , c , "] This should happen after the first set\n")
})
}
fmt.Print( "Second set all launched\n" )
w.Wait()
for i := 10 ; i < 15 ; i++ {
c := i
w.Go( func(){
time.Sleep( rand.Int63n( 1000000000 ) )
fmt.Print("[ " , c , "] reusing first queue\n")
})
}
fmt.Print( "Main thread past first set\n" )
w2.Wait()
fmt.Print( "Main thread past second set\n" )
w.Wait()
fmt.Print( "Main thread past reuse of first queue\n" )
}
I really like this language. It feels like somebody took many of the best facets of C, javascript and erlang and tucked them into a single package. The static duck-typing is beautiful.
1 comment:
a few comments on your code, which is generally fine AFAICS.
> type Manager interface {
> Go(func())
> Wait()
> }
why bother defining the Manager interface?
just return the Manager struct.
> go func(){
> for ; i > 0 ; i-- {
> oc <- true
> }
> }()
this doesn't need to be run in its own goroutine,
as it's already in a different goroutine from the waiters,
and we know they're all currently waiting.
> if wait {
> <- m.wakeup
> }
> }
you need to store m.wakeup in a temporary
variable inside the lock, otherwise there's a race
condition here.
here's another version of your code:
i also changed it so that no initialisation is necessary, so the New function becomes redundant.
you'll need to run it through gofmt to de-blogger-fy it...
package dispatch
import "sync"
type Manager struct {
lock sync.Mutex
running uint
waiting uint
wakeup chan bool
}
func (m *Manager) Go(fn func()) {
m.lock.Lock()
m.running++
m.lock.Unlock()
go func() {
fn()
m.lock.Lock()
m.running--
if m.running == 0 && m.waiting > 0 {
for ; m.waiting > 0; m.waiting-- {
m.wakeup <- true
}
m.wakeup = make(chan bool)
}
m.lock.Unlock()
}()
}
func (m *Manager) Wait() {
wait := false
m.lock.Lock()
if m.wakeup == nil {
m.wakeup = make(chan bool)
}
c := m.wakeup
if m.running > 0 {
m.waiting++
wait = true
}
m.lock.Unlock()
if wait {
<-c
}
}
Post a Comment