Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/7.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Go 在按顺序执行之前,在通道中等待N个项目_Go_Channel_Goroutine - Fatal编程技术网

Go 在按顺序执行之前,在通道中等待N个项目

Go 在按顺序执行之前,在通道中等待N个项目,go,channel,goroutine,Go,Channel,Goroutine,所以我是新来的!但我有一个想法,我想尝试一下 我希望有一个go例程,它接受来自通道的字符串,但只有在它接收到N个字符串之后,它才应该在这些字符串上执行 我四处寻找类似的问题或案例,但我只发现其中的想法是并行执行多个例程并等待聚合结果 我考虑过创建一个数组并将其传递给一个足够长的例程的想法。然而,我希望保持一定的关注点分离,并在接收端控制这一点 我的问题是 这种坏习惯是出于某种原因吗? 有没有更好的方法,是什么 func main() { ch := make(chan string)

所以我是新来的!但我有一个想法,我想尝试一下

我希望有一个go例程,它接受来自通道的字符串,但只有在它接收到N个字符串之后,它才应该在这些字符串上执行

我四处寻找类似的问题或案例,但我只发现其中的想法是并行执行多个例程并等待聚合结果

我考虑过创建一个数组并将其传递给一个足够长的例程的想法。然而,我希望保持一定的关注点分离,并在接收端控制这一点

我的问题是

这种坏习惯是出于某种原因吗? 有没有更好的方法,是什么

func main() {
    ch := make(chan string)
    go func() {
        tasks := []string{}
        for {
            tasks = append(tasks,<- ch)

            if len(tasks) < 3 {
                fmt.Println("Queue still to small")
            }
            if len(tasks) > 3 {
                for i := 0; i < len(tasks); i++ {
                    fmt.Println(tasks[i])
                }
            }
        }
    }()

    ch <- "Msg 1"
    time.Sleep(time.Second)
    ch <- "Msg 2"
    time.Sleep(time.Second)
    ch <- "Msg 3"
    time.Sleep(time.Second)
    ch <- "Msg 4"
    time.Sleep(time.Second)
}

编辑以获得更简单更准确的示例。

根据一些注释,您所寻找的似乎是某种形式的批处理

批处理有几种情况,您可能希望将批处理一起发送:

批量大小足够大 已经过了足够的时间,应该刷新部分批次 您给出的示例不考虑第二种情况。这可能会导致一些尴尬的行为,如果你只是从来没有冲洗,因为你退出获得负载

因此,我建议您查看库,例如,或者只使用通道、计时器和select语句

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    go func() {
        tasks := []string{}
        timer := time.NewTimer(time.Second) // Adjust this based on a reasonable user experience
        for {
            select {
            case <-timer.C:
                fmt.Println("Flush partial batch due to time")
                flush(tasks)
                tasks = nil
                timer.Reset(time.Second)
            case data := <-ch:
                tasks = append(tasks, data)

                // Reset the timer for each data point so that we only flush
                // partial batches when we stop receiving data.
                if !timer.Stop() {
                    <-timer.C
                }
                timer.Reset(time.Second)

                // Guard clause to for batch size
                if len(tasks) < 3 {
                    fmt.Println("Queue still too small")
                    continue
                }

                flush(tasks)
                tasks = nil // reset tasks
            }
        }
    }()

    ch <- "Msg 1"
    time.Sleep(time.Second)
    ch <- "Msg 2"
    time.Sleep(time.Second)
    ch <- "Msg 3"
    time.Sleep(time.Second)
    ch <- "Msg 4"
    time.Sleep(time.Second)
}

func flush(tasks []string) {
    // Guard against emtpy flushes
    if len(tasks) == 0 {
        return
    }

    fmt.Println("Flush")
    for _, t := range tasks {
        fmt.Println(t)
    }
}

根据一些评论,看起来您正在寻找的是某种形式的批处理

批处理有几种情况,您可能希望将批处理一起发送:

批量大小足够大 已经过了足够的时间,应该刷新部分批次 您给出的示例不考虑第二种情况。这可能会导致一些尴尬的行为,如果你只是从来没有冲洗,因为你退出获得负载

因此,我建议您查看库,例如,或者只使用通道、计时器和select语句

package main

import (
    "fmt"
    "time"
)

func main() {
    ch := make(chan string)
    go func() {
        tasks := []string{}
        timer := time.NewTimer(time.Second) // Adjust this based on a reasonable user experience
        for {
            select {
            case <-timer.C:
                fmt.Println("Flush partial batch due to time")
                flush(tasks)
                tasks = nil
                timer.Reset(time.Second)
            case data := <-ch:
                tasks = append(tasks, data)

                // Reset the timer for each data point so that we only flush
                // partial batches when we stop receiving data.
                if !timer.Stop() {
                    <-timer.C
                }
                timer.Reset(time.Second)

                // Guard clause to for batch size
                if len(tasks) < 3 {
                    fmt.Println("Queue still too small")
                    continue
                }

                flush(tasks)
                tasks = nil // reset tasks
            }
        }
    }()

    ch <- "Msg 1"
    time.Sleep(time.Second)
    ch <- "Msg 2"
    time.Sleep(time.Second)
    ch <- "Msg 3"
    time.Sleep(time.Second)
    ch <- "Msg 4"
    time.Sleep(time.Second)
}

func flush(tasks []string) {
    // Guard against emtpy flushes
    if len(tasks) == 0 {
        return
    }

    fmt.Println("Flush")
    for _, t := range tasks {
        fmt.Println(t)
    }
}

我可以看出,批量结果是多么有用。但它确实需要一个定制的解决方案。有很多方法可以解决这个问题——我试着使用Sync.WaitGroup,但结果很糟糕。似乎使用sync.Mutex来锁定批处理函数是最好的方法。但是,当互斥是最好的答案时,imo应该触发对设计的重新检查,因为在imo,它应该是最后一个选项

package main

import (
    "context"
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {

    ctx, canc := context.WithCancel(context.Background())
    acc := NewAccumulator(4, ctx)
    go func() {
        for i := 0; i < 10; i++ {
            acc.Write("hi")
        }
        canc()
    }()

    read := acc.ReadChan()
    for batch := range read {
        fmt.Println(batch)
    }
    fmt.Println("done")
}

type Accumulator struct {
    count    int64
    size     int
    in       chan string
    out      chan []string
    ctx      context.Context
    doneFlag int64
    mu   sync.Mutex
}

func NewAccumulator(size int, parentCtx context.Context) *Accumulator {
    a := &Accumulator{
        size: size,
        in:   make(chan string, size),
        out:  make(chan []string, 1),
        ctx:  parentCtx,
    }

    go func() {
        <-a.ctx.Done()
        atomic.AddInt64(&a.doneFlag, 1)
        close(a.in)
        a.mu.Lock()
        a.batch()
        a.mu.Unlock()
        close(a.out)
    }()
    return a
}

func (a *Accumulator) Write(s string) {
    if atomic.LoadInt64(&a.doneFlag) > 0 {
        panic("write to closed accumulator")
    }
    a.in <- s
    atomic.AddInt64(&a.count, 1)
    a.mu.Lock()
    if atomic.LoadInt64(&a.count) == int64(a.size) {
        a.batch()
    }
    a.mu.Unlock()
}

func (a *Accumulator) batch() {
    batch := make([]string, 0)
    for i := 0; i < a.size; i++ {
        msg := <-a.in
        if msg != "" {
            batch = append(batch, msg)
        }
    }
    fmt.Println("batching", batch)
    a.out <- batch
    atomic.StoreInt64(&a.count, 0)
}

func (a *Accumulator) ReadChan() <-chan []string {
    return a.out
}

最好是有一个累积字符串的切片,当该切片达到一定大小时,就开始一些处理

。但它确实需要一个定制的解决方案。有很多方法可以解决这个问题——我试着使用Sync.WaitGroup,但结果很糟糕。似乎使用sync.Mutex来锁定批处理函数是最好的方法。但是,当互斥是最好的答案时,imo应该触发对设计的重新检查,因为在imo,它应该是最后一个选项

package main

import (
    "context"
    "fmt"
    "sync"
    "sync/atomic"
)

func main() {

    ctx, canc := context.WithCancel(context.Background())
    acc := NewAccumulator(4, ctx)
    go func() {
        for i := 0; i < 10; i++ {
            acc.Write("hi")
        }
        canc()
    }()

    read := acc.ReadChan()
    for batch := range read {
        fmt.Println(batch)
    }
    fmt.Println("done")
}

type Accumulator struct {
    count    int64
    size     int
    in       chan string
    out      chan []string
    ctx      context.Context
    doneFlag int64
    mu   sync.Mutex
}

func NewAccumulator(size int, parentCtx context.Context) *Accumulator {
    a := &Accumulator{
        size: size,
        in:   make(chan string, size),
        out:  make(chan []string, 1),
        ctx:  parentCtx,
    }

    go func() {
        <-a.ctx.Done()
        atomic.AddInt64(&a.doneFlag, 1)
        close(a.in)
        a.mu.Lock()
        a.batch()
        a.mu.Unlock()
        close(a.out)
    }()
    return a
}

func (a *Accumulator) Write(s string) {
    if atomic.LoadInt64(&a.doneFlag) > 0 {
        panic("write to closed accumulator")
    }
    a.in <- s
    atomic.AddInt64(&a.count, 1)
    a.mu.Lock()
    if atomic.LoadInt64(&a.count) == int64(a.size) {
        a.batch()
    }
    a.mu.Unlock()
}

func (a *Accumulator) batch() {
    batch := make([]string, 0)
    for i := 0; i < a.size; i++ {
        msg := <-a.in
        if msg != "" {
            batch = append(batch, msg)
        }
    }
    fmt.Println("batching", batch)
    a.out <- batch
    atomic.StoreInt64(&a.count, 0)
}

func (a *Accumulator) ReadChan() <-chan []string {
    return a.out
}

最好是有一个累积字符串的切片,当该切片达到一定大小时,就开始一些处理

我不知道这是不好的做法,但这是非常不寻常的,看起来像一个XY问题。你到底想用这个做什么?你在解决什么问题?我正试图为对比特币核心节点的sendmany rpc调用积累数据。现在我在想,这可能是更容易与一个串比函数。。。这个想法是,如果我有足够的请求,将它们作为一笔付款发送给几个人,我可以支付较低的费用。1持久存储可能更适合这种情况。如果应用程序可能由于崩溃而退出,则应用程序将丢失付款。2为了避免添加任意大的延迟,您可能希望按时间而不是按计数进行分块。3忽略场景中可能出现的这些问题,代码就可以了。@ThunderCat谢谢!感觉像是一个非常需要的精神检查和伟大的建议!我想这样说。我在下面添加的代码正在生产中,运行速度很快。我不知道这是一个不好的做法,但它非常不寻常,看起来像一个XY问题。你到底想用这个做什么?你在解决什么问题?我正试图为对比特币核心节点的sendmany rpc调用积累数据。现在我在想,这可能是更容易与一个串比函数。。。这个想法是,如果我有足够的请求,将它们作为一笔付款发送给几个人,我可以支付较低的费用。1持久存储可能更适合这种情况。如果应用程序可能由于崩溃而退出,则应用程序将丢失付款。2为了避免添加任意大的延迟,您可能希望按时间而不是按计数进行分块。3忽略场景中可能出现的这些问题,代码就可以了。@ThunderCat谢谢!感觉像是一个非常需要的精神检查和伟大的建议!我想这样说。我在下面添加的代码正在生产中,运行v-fast。我认为最好使用互斥体或waitgroups,以便
“不需要计时器。”dustinevan我想我不明白你的意思。您能详细说明一下吗?这并不是完全客观的,但go的并发结构允许开发人员协调系统,其中数据通过反应模型流动。e、 g.你在需要做某事的时候做某事,而不是问是否需要做某事,并在回答是的时候做。使用计时器是一种轮询模式,而不是被动模式。再次看到这一点。在这种情况下,只获取单个数据片段的速度是否与持续时间相同?时间秒是否允许?当数据可用时,它不受速率限制。计时器用于刷新部分批次。如果通道总是接收数据,计时器永远不会启动。我认为最好使用互斥或等待组,这样就不需要计时器了。@dustinevan我想我不明白你的意思。您能详细说明一下吗?这并不是完全客观的,但go的并发结构允许开发人员协调系统,其中数据通过反应模型流动。e、 g.你在需要做某事的时候做某事,而不是问是否需要做某事,并在回答是的时候做。使用计时器是一种轮询模式,而不是被动模式。再次看到这一点。在这种情况下,只获取单个数据片段的速度是否与持续时间相同?时间秒是否允许?当数据可用时,它不受速率限制。计时器用于刷新部分批次。如果通道始终接收数据,则计时器从不触发