Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to close a channel inside recursive function

Tags:

go

I tried to find a similar question but I couldn't, so here I am asking:


I'm using close(ch) inside a recursive function. I need to close channel to terminate a range loop. However, since the function is recursive, the close is run multiple times which gives me:

panic: close of closed channel

If I comment-out the close(ch) statement, I receive:

fatal error: all goroutines are asleep - deadlock!

Please find below my code:

package main

import "golang.org/x/tour/tree"
import "fmt"

// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    ch<-(t.Value)
    if t.Left != nil { Walk(t.Left, ch) }
    if t.Right != nil { Walk(t.Right, ch) }
    close(ch) // => How to close a channel inside recursive function? ***
    return
}

func main() {
    ch := make(chan int)
    go Walk(tree.New(1), ch)
    for i := range ch {
        fmt.Print(i, " ")
    }
}
like image 898
user3405291 Avatar asked Apr 19 '18 14:04

user3405291


People also ask

How do you stop a recursive function?

Stopping Condition for Recursion If we don't mention any condition to break the recursive call, the function will keep calling itself infinitely. We use the if...else statement (or similar approach) to break the recursion. Normally, a recursive function has two branches: One for recursive calls.

How do you end a recursive loop?

You break out of recursion by having conditions under which you simply don't perform new recursive calls, and making sure that those conditions are eventually always met. You can write a recursive function that contains pass : def fac(n):

Do you need to close channels?

It's OK to leave a Go channel open forever and never close it. When the channel is no longer used, it will be garbage collected. Note that it is only necessary to close a channel if the receiver is looking for a close. Closing the channel is a control signal on the channel indicating that no more data follows.


2 Answers

Close the channel outside of the recursive function:

func Walk(t *tree.Tree, ch chan int) {
    ch<-(t.Value)
    if t.Left != nil { Walk(t.Left, ch) }
    if t.Right != nil { Walk(t.Right, ch) }
}

func main() {
    ch := make(chan int)
    go func() {
        defer close(ch)
        Walk(tree.New(1), ch)
    }()
    for i := range ch {
        fmt.Print(i, " ")
    }
}

Edit: This answer follows a common Go idiom of using defer for cleanup actions. As comments have noted, the defer is not necessary. The body of the anonymous function can also be written as:

        Walk(tree.New(1), ch)
        close(ch)
like image 179
Bayta Darell Avatar answered Sep 20 '22 17:09

Bayta Darell


Cerise Limón's answer works fine. I just wanted to mention that it's not mandatory to use defer inside the closure:

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
)

func Walk(t *tree.Tree, ch chan int) {
    if t.Left != nil { Walk(t.Left, ch) }
    ch <- t.Value
    if t.Right != nil { Walk(t.Right, ch) }
}

func main() {
    ch := make(chan int)

    go func() {
        Walk(tree.New(1), ch)
        close(ch)
    }()

    for i := range ch {
        fmt.Print(i, " ")
    }
}

After the Walk function finishes, the channel is closed, and both actions are performed in a separated goroutine.

If we use

go Walk(tree.New(1), ch)
close(ch)

We are starting a goroutine but closing the channel immediately in the current one.


Since I wanted to close the channel inside the recursive function, I ended up doing this:

func Walk(t *tree.Tree, ch chan int, closeAtTheEnd bool) {
    if t.Left != nil {
        // fmt.Println("Walk the Left of", t.Value)
        Walk(t.Left, ch, false)
    } 

    // fmt.Println("Send to the channel:", t.Value)
    ch <- t.Value

    if t.Right != nil {
        // fmt.Println("Walk the Right of", t.Value)
        Walk(t.Right, ch, false)
    } 

    if closeAtTheEnd {
        close(ch)
    }
}

That way, recursive calls won't close the channel, and only the first call of Walk is allowed to do that (the tree starts at the root node and closes the channel after walking both directions).

func useWalkAndPrintTreeValues(k int) {
    c := make(chan int, 10)

    go Walk(tree.New(k), c, true)

    for x := range c {
        fmt.Print(x, " ")   
    }
}
like image 41
JCarlosR Avatar answered Sep 19 '22 17:09

JCarlosR