简体   繁体   中英

Read from Go buffered channel if channel is full or after specific time

I want to process from channel if channel is full or certain time is elapsed. My use case is similar to existing question and I have tried to modify the existing ans

My code is at https://go.dev/play/p/HaGZ9HHqj0i ,

package main

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

type Audit struct {
    ID int
}

const batchSize = 5

var batch = make([]Audit, 0, batchSize)

func upsertBigQueryAudits(audits []Audit) {
    fmt.Printf("Processing batch of %d\n", len(audits))
    for _, a := range audits {
        fmt.Printf("%d ", a.ID)
    }
    fmt.Println()
    batch = []Audit{}
}
func process(full <-chan struct{}) {
    ticker := time.NewTicker(1 * time.Nanosecond)

    for {
        select {
        case <-full:
            fmt.Println("From full")
            upsertBigQueryAudits(batch)
        case <-ticker.C:
            fmt.Println("From ticker")
            if len(batch) > 0 {
                fmt.Println("From ticker sending batch")
                upsertBigQueryAudits(batch)
            }
        }
    }
}
func processAudits(audits <-chan Audit, full chan<- struct{}, batchSize int) {
    for audit := range audits {
        batch = append(batch, audit)
        if len(batch) == cap(batch) {
            // upsertBigQueryAudits(batch)
            fmt.Println("Sending full")
            full <- struct{}{}

        }
    }

}

func produceAudits(x int, to chan Audit) {
    for i := 0; i < x; i++ {
        to <- Audit{
            ID: i,
        }
    }
}

func main() {
    var wg sync.WaitGroup
    audits := make(chan Audit)
    full := make(chan struct{})
    wg.Add(1)
    go func() {
        defer wg.Done()
        process(full)
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        processAudits(audits, full, batchSize)
    }()
    wg.Add(1)
    go func() {
        defer wg.Done()
        produceAudits(25, audits)
        close(audits)
    }()
    wg.Wait()
    fmt.Println("Complete")
}

Here I want to process the batch when it is filled with batchSize elements Or when certain time (for eg 1 nano second) is elapsed.

Edit: Added my concern inline. This is sample output.

timeout running program
Sending full <--- this line comes twice
Sending full
From full
Processing batch of 10 <--- instead of 5 it is processing 10 items
0 1 2 3 4 5 6 7 8 9 
From full
Processing batch of 1
10 
Sending full
Sending full
From full
Processing batch of 1 <-- instead of 5 it is processing 1 items. It does not process more than 1 item.
11 
From full
Processing batch of 0

...
...
Sending full
Sending full
From full
Processing batch of 1 <-- instead of 5 it is processing 1 items
24 
From full
Processing batch of 0

From ticker <-- this line comes only after consuming 25 items.  
From ticker
From ticker
From ticker

I found answer at https://elliotchance.medium.com/batch-a-channel-by-size-or-time-in-go-92fa3098f65

package main

import (
    "context"
    "fmt"
    "time"
)

func BatchStringsCtx[T any](ctx context.Context, values <-chan T, maxItems int, maxTimeout time.Duration) chan []T {
    batches := make(chan []T)

    go func() {
        defer close(batches)

        for keepGoing := true; keepGoing; {
            var batch []T
            expire := time.After(maxTimeout)
            for {
                select {
                case <-ctx.Done():
                    keepGoing = false
                    goto done

                case value, ok := <-values:
                    if !ok {
                        keepGoing = false
                        goto done
                    }

                    batch = append(batch, value)
                    if len(batch) == maxItems {
                        goto done
                    }

                case <-expire:
                    goto done
                }
            }

        done:
            if len(batch) > 0 {
                batches <- batch
            }
        }
    }()

    return batches
}

func main() {
    strings := make(chan string)
    go func() {
        strings <- "hello"
        strings <- "there" // hit limit of 2

        strings <- "how"
        time.Sleep(15 * time.Millisecond) // hit timeout

        strings <- "are"
        strings <- "you" // hit limit of 2

        // A really long time without any new values.
        // The context was cancelled around 300ms,
        // before this sleep finished.
        time.Sleep(500 * time.Millisecond)

        strings <- "doing?" // never read

        close(strings)
    }()

    //ctx, cancel := context.WithTimeout(context.Background(), 300 * time.Millisecond)
    ctx := context.Background()

    start := time.Now()
    batches := BatchStringsCtx[string](ctx, strings, 2, 10*time.Millisecond)
    for batch := range batches {
        fmt.Println(time.Now().Sub(start), batch)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM