GO: Scheduling Precedence Graphs

Introduction to Precedence Graphs

  • A Precedence Graph specifies the timing relationships between multiple goroutines.
  • An arrow (or sequence of arrows) A → B indicates that B cannot start execution before A is finished.
  • In the diagram epsilon cannot be executed until beta, gamma and delta have completed.
  • STOP cannot execute until both alpha and epsilon have completed.
  • There are no arrows between beta, gamma and delta so they can execute in an arbitrary order.
  • The following schedules are legal under this precedence graph:
    • START, alpha, beta, gamma, delta, epsilon, STOP
    • START, beta, alpha, delta, gamma, epsilon, STOP
    • START, delta, gamma, beta, epsilon, alpha, STOP
  • The following schedules are illegal:
    • START, epsilon, beta, gamma, delta, alpha, STOP, (epsilon cannot execute before beta or gamma or delta.)
    • START, beta, delta, epsilon, gamma, alpha, STOP, (epsilon cannot execute until all of beta, gamma, delta have completed.)
    • START, delta, gamma, beta, epsilon, STOP, alpha, (alpha and epsilon must complete before STOP.)

Scheduling with workGroups

  • goroutine4.go implements the goroutines alpha…epsilon. These sleep a random number of milliseconds and each prints out its name.
  • The seed for the random number generator is taken from the command line.
  • Two waitgroups wg1, wg2 are used for synchronization.
  • START and STOP are executed as functions.
  • Repeatedly running the code yields legal sequences of execution, despite the random sleep intervals and the inherent non-determinism of goroutine execution order.
  • This is because the waitGroup synchronization imposes the correct sequence of execution.
/* goroutine4.go
   synchronizing an arbitrary graph */
   
package main
import ("fmt"; "sync"; "math/rand"; "os"; "strconv"; "time")

func START() {
   fmt.Printf("\nSTART")
}
func STOP() {
   fmt.Printf("\nSTOP")
}
func alpha(wg *sync.WaitGroup) {
   time.Sleep(time.Duration(rand.Intn(100))*
      time.Millisecond)
   fmt.Printf("\nalpha")
   wg.Done()
}
func beta(wg *sync.WaitGroup) {
   time.Sleep(time.Duration(rand.Intn(100))*
      time.Millisecond)
   fmt.Printf("\nbeta")
   wg.Done()
}
func gamma(wg *sync.WaitGroup) {
   time.Sleep(time.Duration(rand.Intn(100))*
      time.Millisecond)
   fmt.Printf("\ngamma")
   wg.Done()
}
func delta(wg *sync.WaitGroup) {
   time.Sleep(time.Duration(rand.Intn(100))*
      time.Millisecond)
   fmt.Printf("\ndelta")
   wg.Done()
}
func epsilon(wg *sync.WaitGroup) {
   time.Sleep(time.Duration(rand.Intn(100))*
      time.Millisecond)
   fmt.Printf("\nepsilon")
   wg.Done()
}

func main() {
   if len(os.Args) < 2 {
      fmt.Printf("usage <filename> seed\n")
      return
   }
   seed, _  := strconv.Atoi(os.Args[1])
   rand.Seed(int64(seed))

   var  wg1,  wg2 sync.WaitGroup
   START()
   wg1.Add(2)
   go alpha(&wg1) 
   wg2.Add(3)
   go beta(&wg2) 
   go gamma(&wg2) 
   go delta(&wg2) 
   wg2.Wait()
   go epsilon(&wg1)
   wg1.Wait()
   STOP()
}

Review

  • Complex networks of goroutines can be synchronized using multiple waitGroups.
  • Some thought is required to correctly execute the goroutines under the precedence relationships implied by the networks.
  • The following exercises give you a chance to further explore these ideas.
    • Write GO programs to implement the precedence graphs shown, as was done in goroutine4.go. You must verify that the schedules produced are legal by running the codes with different seeds.