Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang: How can I stop the execution of a for loop from outside the loop?

Tags:

go

I am using an infinite for loop with a label. Outside the scope of the for loop, I have a scheduled function running as a go routine. I want to break the for loop from the scheduled function when a certain condition is met. How can I accomplish the same ? This is what I am trying, which obviously won't work due to scope issue.

package main

import (
  "fmt"
  "time"
  "sync"
)

func main() {
  count := 0
  var wg sync.WaitGroup
  wg.Add(1)
  t := time.NewTicker(time.Second*1)

  go func (){
    for {
        fmt.Println("I will print every second", count)
        count++ 
        if count > 5 {
          break myLoop;
          wg.Done()
        }
        <-t.C
    }  
  }()

  i := 1

  myLoop:
  for {
    fmt.Println("iteration", i)
    i++
  }

  wg.Wait()
  fmt.Println("I will execute at the end")

}
like image 637
Mandeep Singh Avatar asked Jun 09 '16 15:06

Mandeep Singh


2 Answers

Make a signalling channel.

quit := make(chan struct{}{})

Close it when you want to break a loop.

go func (){
    for {
        fmt.Println("I will print every second", count)
        count++ 
        if count > 5 {
          close(quit)
          wg.Done()
          return
        }
        <-t.C
    }  
  }()

Read on closed channel returns immediately with zero value (but we dont need it in this case). Otherwise reading from it blocks and select pass execution to "default" case.

 myLoop:
  for {
    select {
    case <- quit:
      break myLoop
    default:
      fmt.Println("iteration", i)
      i++
    }
  }
like image 53
Darigaaz Avatar answered Oct 02 '22 13:10

Darigaaz


Darigaaz's answer works for a single goroutine, but closing a closed channel panics (and you also don't need a waitgroup in that instance). If you have multiple goroutines, and want the loop to exit after all of them have finished, use a waitgroup with a closer routine:

https://play.golang.org/p/RhmUzWhneT

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    quitCh := make(chan struct{})

    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go func(i int) {
            count := 1
            t := time.NewTicker(time.Millisecond)
            for count <= 5 {
                fmt.Printf("Goroutine %v iteration %v\n", i, count)
                count++
                <-t.C
            }
            wg.Done()
        }(i)
    }

    // This is the closer routine.
    go func() {
        wg.Wait()
        close(quitCh)
    }()

    t := time.NewTicker(500 * time.Microsecond)
loop:
    for i := 1; ; i++ { // this is still infinite
        select {
        case <-quitCh:
            break loop // has to be named, because "break" applies to the select otherwise
        case <-t.C:
            fmt.Println("Main iteration", i)
        }
    }
    fmt.Println("End!")

}

As an alternative to the named loop style, you can instead use a fallthrough break in that select:

    for i := 1; ; i++ { // this is still infinite
        select {
        case <-quitCh:
            // fallthrough
        case <-t.C:
            fmt.Println("Main iteration", i)
            continue
        }
        break // only reached if the quitCh case happens
    }
like image 24
Kaedys Avatar answered Oct 02 '22 13:10

Kaedys