小能豆

How to create asynchronous iterator in golang - but currently deadlocking

go

I have this which I have faith is close to working as desired. What I am trying to do is range over a sequence, but be able to call the next item yourself and asynchronously

sync:

for r := range even.Sequence() {
    if !r.Done {
        r.Contnue()
    }
}

or async:

   for r := range even.Sequence() {
        go func(){
          if !r.Done {
            r.Contnue()
           }
        }()
    }

here is the full code in main:

package main

import "fmt"

type HasNext[T any] interface {
    Next() (bool, T)
}

type Nexter[T int] struct {
    Val int
    End int
}

type Ret struct {
    Done    bool
    Value   int
    Contnue func()
}

func (n *Nexter[int]) Sequence() chan Ret {

    var c = make(chan Ret)
    even := newEven()

    var x func()

    x = func() {
        fmt.Println("got here 3")
        <-c
        fmt.Println("got here 4")
        fmt.Println("got here 5")
        var b, v = even.Next()
        c <- Ret{b, v, x}
        if b {
            close(c)
        }
    }

    go func() {
        var b, v = even.Next()
        fmt.Println("got here 1")
        c <- Ret{b, v, x}
        fmt.Println("got here 2")
    }()

    return c

}

func (n *Nexter[int]) Next() (bool, int) {
    n.Val = n.Val + 1
    if n.Val > n.End {
        return true, int(n.Val)
    }
    return false, int(n.Val)
}

func newEven() *Nexter[int] {
    n := 0
    return &Nexter[int]{n, 5}
}

func main() {


    even := newEven()


    for r := range even.Sequence() {

        fmt.Println("value:", r.Value)
        if !r.Done {
            fmt.Println("continuing...")
            r.Contnue()
        }
    }

}

but currently I get this output:

```golang
got here 1
got here 2
value: 1
continuing…
got here 3
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
/Users/me/codes/vibeirl/wss/test/iterator/main.go:83 +0x66
```

so clearly it’s deadlocking somewhere but not sure where/how exactly


阅读 76

收藏
2023-12-19

共1个答案

小能豆

The issue you’re encountering is related to the channel communication and synchronization in your Sequence method. The deadlock occurs because you’re trying to receive from the channel (<-c) before any other goroutine has a chance to send data to it. The goroutine that sends data (go func() {...}) is blocked until someone reads from the channel.

To fix this, you need to ensure that you have separate goroutines for sending and receiving data. Here’s an updated version of your Sequence method:

func (n *Nexter[int]) Sequence() chan Ret {
    c := make(chan Ret)

    go func() {
        var b, v = n.Next()
        fmt.Println("got here 1")
        c <- Ret{b, v, nil} // Initial send
        fmt.Println("got here 2")

        for {
            r := <-c
            fmt.Println("got here 3")

            if r.Done {
                close(c)
                return
            }

            go func() {
                var b, v = n.Next()
                c <- Ret{b, v, nil} // Send the next value
                fmt.Println("got here 4")
            }()
        }
    }()

    return c
}

In this version, I removed the x function and modified the loop in the goroutine to handle both initial and subsequent sends. The initial send is performed before entering the loop, and subsequent sends are performed in a new goroutine. This should prevent the deadlock issue you were facing.

However, keep in mind that using a channel in this way may not be the most idiomatic Go code. Channels are typically used for communication between goroutines, and the structure of your Sequence method might be better suited for a more straightforward loop without the need for a channel. If your intention is to iterate over a sequence asynchronously, you might consider using goroutines and a sync.WaitGroup for better synchronization.

2023-12-19