在一定时间内从goroutine接收值

I have a goroutine which can generate an infinite number of values (each more suitable than the last), but it takes progressively longer to find each values. I'm trying to find a way to add a time limit, say 10 seconds, after which my function does something with the best value received so far.

This is my current "solution", using a channel and timer:

// the goroutine which runs infinitely
// (or at least a very long time for high values of depth)
func runSearch(depth int, ch chan int) {
    for i := 1; i <= depth; i++ {
        fmt.Printf("Searching to depth %v
", i)
        ch <- search(i)
    }
}

// consumes progressively better values until the channel is closed
func awaitBestResult(ch chan int) {
    var result int
    for result := range ch {
        best = result
    }

    // do something with best result here
}

// run both consumer and producer
func main() {
    timer := time.NewTimer(time.Millisecond * 2000)

    ch := make(chan int)

    go runSearch(1000, ch)
    go awaitBestResult(ch)

    <-timer.C
    close(ch)
}

This mostly works - the best result is processed after the timer ends and the channel is closed. However, I then get a panic (panic: send on closed channel) from the runSearch goroutine, since the channel has been closed by the main function.

How can I stop the first goroutine running after the timer has completed? Any help is very appreciated.

You need to ensure that the goroutine knows when it is done processing, so that it doesn't attempt to write to a closed channel, and panic.

This sounds like a perfect case for the context package:

func runSearch(ctx context.Context, depth int, ch chan int) {
    for i := 1; i <= depth; i++ {
        select {
        case <- ctx.Done()
            // Context cancelled, return
            return
        default:
        }
        fmt.Printf("Searching to depth %v
", i)
        ch <- search(i)
    }
}

Then in main():

// run both consumer and producer
func main() {
    ctx := context.WithTimeout(context.Background, 2 * time.Second)

    ch := make(chan int)

    go runSearch(ctx, 1000, ch)
    go awaitBestResult(ch)

    close(ch)
}

You are getting a panic because your sending goroutine runSearch apparently outlives the timer and it is trying to send a value on the channel which is already closed by your main goroutine. You need to devise a way to signal the sending go routine not to send any values once your timer is lapsed and before you close the channel in main. On the other hand if your search gets over sooner you also need to communicate to main to move on. You can use one channel and synchronize so that there are no race conditions. And finally you need to know when your consumer has processed all the data before you can exit main.

Here's something which may help.

package main

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

var mu sync.Mutex //To protect the stopped variable which will decide if a value is to be sent on the signalling channel
var stopped bool

func search(i int) int {
    time.Sleep(1 * time.Millisecond)
    return (i + 1)
}

// (or at least a very long time for high values of depth)
func runSearch(depth int, ch chan int, stopSearch chan bool) {

    for i := 1; i <= depth; i++ {
        fmt.Printf("Searching to depth %v
", i)
        n := search(i)
        select {
        case <-stopSearch:
            fmt.Println("Timer over! Searched till ", i)
            return
        default:
        }

        ch <- n
        fmt.Printf("Sent depth %v result for processing
", i)
    }

    mu.Lock() //To avoid race condition with timer also being
    //completed at the same time as execution of this code
    if stopped == false {
        stopped = true
        stopSearch <- true
        fmt.Println("Search completed")
    }
    mu.Unlock()

}

// consumes progressively better values until the channel is closed
func awaitBestResult(ch chan int, doneProcessing chan bool) {
    var best int

    for result := range ch {
        best = result
    }
    fmt.Println("Best result ", best)
    // do something with best result here

    //and communicate to main when you are done processing the result
    doneProcessing <- true

}

func main() {
    doneProcessing := make(chan bool)
    stopSearch := make(chan bool)

    // timer := time.NewTimer(time.Millisecond * 2000)
    timer := time.NewTimer(time.Millisecond * 12)

    ch := make(chan int)

    go runSearch(1000, ch, stopSearch)
    go awaitBestResult(ch, doneProcessing)
    select {
    case <-timer.C:
        //If at the same time runsearch is also completed and trying to send a value !
        //So we hold a lock before sending value on the channel
        mu.Lock()
        if stopped == false {
            stopped = true
            stopSearch <- true
            fmt.Println("Timer expired")
        }
        mu.Unlock()

    case <-stopSearch:
        fmt.Println("runsearch goroutine completed")
    }
    close(ch)

    //Wait for your consumer to complete processing
    <-doneProcessing
    //Safe to exit now
}

On playground. Change the value of timer to observe both the scenarios.