Control of Concurrent Tasks for Golang's Road to Growth

Posted by toolman on Fri, 08 Oct 2021 21:09:51 +0200

Control of concurrent tasks

In the previous section, we learned about the common concurrency modes of goroutine, channel, and go. In this article, we will explain the control of concurrent tasks in the GoLanguage.

  1. Non-blocking wait
    Non-blocking wait is often used to determine which channel data transfers faster. We use select to determine if the channel is blocked, return empty strings, false if blocked, and return values and true from the channel if blocked for fees:
    //Non-blocking wait
    func nonBlockWait(c chan string) (string, bool){
    select{
    case m := <- c:
       return m,true
    default:
       return "", false
     }
    }
    Call:
    func main() {
    m1 := msgGen("service1")
    m2 := msgGen("sercive2")
    for{
       fmt.Println(<-m1)
       if m, ok := nonBlockWait(m2); ok{
          fmt.Println(m)
       }else{
          fmt.Println("no mssage from sercive2")
       }
    }
    }

mssGen method:

func msgGen(name string) chan string {
   c := make(chan string)
   go func(){
      i := 0
  for {
         time.Sleep(time.Duration(rand.Intn(5000)) * time.Millisecond)
         c <- fmt.Sprintf("service: %s, message : %d", name, i)
         i++
      }
   }()
   return c
}

Print results:
service: service1, message : 0
no mssage from sercive2
service: service1, message : 1
service: sercive2, message : 0
service: service1, message : 2
service: sercive2, message : 1
service: service1, message : 3
no mssage from sercive2
service: service1, message : 4
no mssage from sercive2
service: service1, message : 5
no mssage from sercive2

2. Timeout waiting
Similar to the logical and non-blocking wait, we need to use the time.after channel to control time:

func timeoutWait(c chan string, tm time.Duration) (string, bool){
   select{
   case m := <-c:
      return m, true
 case <- time.After(tm):
      return "", false
  }
}

Call:

func main() {
   m1 := msgGen("service1")
   m2 := msgGen("sercive2")
   for{
      fmt.Println(<-m1)
      if m, ok := timeoutWait(m2, time.Second); ok{
         fmt.Println(m)
      }else{
         fmt.Println("timeout")
      }
   }
}

mssGen method unchanged
Print results:
service: service1, message : 0
service: sercive2, message : 0
service: service1, message : 1
timeout
service: service1, message : 2
service: sercive2, message : 1
service: service1, message : 3
timeout
service: service1, message : 4
service: sercive2, message : 2

  1. Task Interrupt/Exit
    At the end of the main, all goroutines will be killed, but if you don't finish the task halfway, we want the outside layer to tell us, "You're leaving." How do we do that? Here we need to bring done s in again, and the main goroutine sends messages to the goroutine
    This is the core code and logic for task interrupt/exit:
func msgGen(name string, done chan struct{}) chan string {  //done can also use bool, struct {} has no data inside, saving more space than bool
  c := make(chan string)
   go func(){
      i := 0
  for {
         select {
         case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond):  //Every time.Duration (rand.Intn(5000))* time.Millisecond time will send data to c
            c <- fmt.Sprintf("service: %s, message : %d", name, i)
         case <-done:  //When data is sent to the done in main, the program executes the case and exits at last
            fmt.Println("cleaing up")
            return
  }
         i++
      }

   }()
   return c
}

main:

func main() {
   done := make(chan struct{})
   m1 := msgGen("service1", done)
   for i := 0; i < 5; i++{   //Print five times to end
      fmt.Println(<-m1)
      if m, ok := timeoutWait(m1, time.Second); ok{
         fmt.Println(m)
      }else{
         fmt.Println("timeout")
      }
   }
   //Main notifies other goroutine s that main will end sending data to the done
   done <- struct{}{} //The first {} is the structure definition and the second {} is the initialization
  time.Sleep(time.Second)   //Prevent main from exiting instantly
}

Print results:
service: service1, message : 0
timeout
service: service1, message : 1
timeout
service: service1, message : 2
timeout
service: service1, message : 3
timeout
service: service1, message : 4
timeout
cleaing up

  1. Exit gracefully
    In 3.When it comes to task interruption, during this process, the main (main goroutine) only notifies other goroutines that I am going back and there is no real interaction, which is not so elegant. Now we need to exit gracefully, such as: male: I am going; female: Okay, you can go. This is not very elegant, answer each other, so let's do it now:
  • Method 1: Add a chan boo type dones based on Interrupt Task to respond and see the code:
func msgGen(name string, done chan struct{}, dones chan bool) chan string {  //done can also use bool, struct {} has no data inside, saving more space than bool
  c := make(chan string)
   go func(){
      i := 0
  for {
         select {
         case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond):
            c <- fmt.Sprintf("service: %s, message : %d", name, i)
         case <-done:
            fmt.Println("cleaing up")
            time.Sleep(2 * time.Second)
            fmt.Println("cleaing done")
            dones <- true
 return  }
         i++
      }

   }()
   return c
}
func main() {
   done := make(chan struct{})
   dones := make(chan bool)
   m1 := msgGen("service1", done, dones)
   for i := 0; i < 5; i++{
      fmt.Println(<-m1)
      if m, ok := timeoutWait(m1, time.Second); ok{
         fmt.Println(m)
      }else{
         fmt.Println("timeout")
      }
   }
   done <- struct{}{} //The first {} is the structure definition and the second {} is the initialization
  <-dones
}

Print results:
service: service1, message : 0
timeout
service: service1, message : 1
timeout
service: service1, message : 2
service: service1, message : 3
service: service1, message : 4
timeout
service: service1, message : 5
service: service1, message : 6
cleaing up
cleaing done

  • Method 2
    There is no need to add parameters, just a done of type chan struct {} in
    Add: done <- struct {}{} to go func {...}(), see the code
func msgGen(name string, done chan struct{}) chan string {  //done can also use bool, struct {} has no data inside, saving more space than bool
  c := make(chan string)
   go func(){
      i := 0
  for {
         select {
         case <-time.After(time.Duration(rand.Intn(2000)) * time.Millisecond):
            c <- fmt.Sprintf("service: %s, message : %d", name, i)
         case <-done:
            fmt.Println("cleaing up")
            time.Sleep(2 * time.Second)
            fmt.Println("cleaing done")
            done <- struct{}{}
 return  }
         i++
      }

   }()
   return c

}
func main() {
   done := make(chan struct{})
   dones := make(chan bool)
   m1 := msgGen("service1", done, dones)
   for i := 0; i < 5; i++{
      fmt.Println(<-m1)
      if m, ok := timeoutWait(m1, time.Second); ok{
         fmt.Println(m)
      }else{
         fmt.Println("timeout")
      }
   }
   done <- struct{}{} //The first {} is the structure definition and the second {} is the initialization
  <-done
}

Print results:
service: service1, message : 0
timeout
service: service1, message : 1
timeout
service: service1, message : 2
service: service1, message : 3
service: service1, message : 4
timeout
service: service1, message : 5
service: service1, message : 6
cleaing up
cleaing done

The content of golang concurrent programming is described here. Concurrent programming differs from traditional programming in that concurrent programming jumps a lot and is more difficult to understand. Go on, everyone!

Topics: Go