当前位置: 首页 > 知识库问答 >
问题:

基本goroutine和通道模式:一个通道多个goroutine

司宏伯
2023-03-14

我是新来的,想知道一些很基本的问题,我不能弄清楚。

为了发挥作用(对实际需要的抽象),我需要:

  • 用常数迭代
  • 固定的元素初始化字符串片断
  • 遍历此切片并为每个元素运行一个goroutine
  • 每个goroutine将花费一定的时间来处理元素(随机秒持续时间)
  • 作业完成后,我希望goroutine将结果推送到一个通道
  • 然后我需要捕获来自这个通道的所有结果(在一个从主goroutine调用的函数中),将它们附加到最后一个切片中,然后,在它完成后
  • 打印最终切片的长度并添加一些基本的时间跟踪

毫不奇怪,总的程序时间总是或多或少地等于const max_sec_sleep的值,因为所有的处理都是并行的。

但是呢:

  1. 接收部分:

在所有的迭代之后,我应该关闭通道的某个地方吗?但我肯定会在至少一次gouroute完成之前关闭它,以恐慌错误结束(试图发送到关闭的通道)

如果我要插入一个done<-true模式,它会在这里吗?

我没有真正尝试waitgroups,广告我需要一种方法来捕捉所有的'return'值从goroutine,并追加到最后的切片;我没有找到一个合适的方法从一个goroutine返回,除了使用渠道。

我应该在func参数中传递通道还是让它们全局化到程序中?

package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"
)

const ITERATIONS = 200

var (
    results   chan string
    initial   []string
    formatted []string
)

func main() {
    defer timeTrack(time.Now(), "program")

    format()  //run format goroutines
    receive() //receive formatted strings

    log.Printf("final slice contains %d/%d elements", len(formatted), len(initial))
}

//gets all results from channel and appends them to formatted slice
func receive() {
    for i := 0; i < ITERATIONS; i++ {
        select {
        case result := <-results:
            formatted = append(formatted, result)
        }
    }
}

//loops over initial slice and runs a goroutine per element
//that does some formatting operation and then pushes result to channel
func format() {
    for i := 0; i < ITERATIONS; i++ {
        go func(i int) {
            //simulate some formatting code that can take a while
            sleep := time.Duration(rand.Intn(10)) * time.Second
            time.Sleep(sleep)
            //append formatted string to result chan
            results <- fmt.Sprintf("%s formatted", initial[i])
        }(i)
    }

}

//initialize chans and inital slice
func init() {
    results = make(chan string, ITERATIONS)
    for i := 0; i < ITERATIONS; i++ {
        initial = append(initial, fmt.Sprintf("string #%d", i))
    }
}

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    log.Printf("%s took %s", name, elapsed)
}


共有1个答案

沈国安
2023-03-14

我找不到一个简单的for(ever)循环来包装select,有两个case(一个从结果通道接收,另一个类似case<-done从函数返回)。会是更好的模式吗?

如果所有写入器完成后通道关闭,则可以使用一个简单的for...range循环:

for result := range ch {
    ... do something with the result ...
}

为了使这个简单的变体工作,通道必须关闭,否则for循环将不会终止。

在所有的迭代之后,我应该关闭通道的某个地方吗?

如果可能的话,是的。

我没有试过服务组...

var wg Sync.WaitGroup
wg.Add(ITERATIONS)

然后你就可以生成所有的Goroutine,让它们运行。当每个运行时,它调用wg.done()来指示完成。

你那时--某个地方;where部分有点棘手-调用wg.wait()等待完成所有编写程序。当所有写入程序都指示完成时,您可以close()通道。

请注意,如果从读取通道的同一个goroutine调用wg.wait()-即将运行for result:=range...循环的goroutine--则会出现一个问题:不能同时从通道读取并等待写入器写入通道。因此,要么在循环结束后调用wg.wait(),这就太晚了;或者在循环开始之前,这太早了。

这使得问题及其解决方案变得清晰:您必须在一个goroutine中读取通道,并在另一个goroutine中执行等待然后关闭。这些goroutines中最多有一个可以是最初进入main函数的主要goroutines。

让等待然后关闭的goroutine成为自己的私人goroutine往往非常简单:

go func() {
    wg.Wait()
    close(results)
}()

例如。

func(args) {
    defer wg.Done()
    ... do the work ...
}

wg.Add(1)
go func(args) // `func` will definitely call `wg.Done`, even if `func` panics

我应该在func参数中传递通道还是让它们全局化到程序中?

在风格上,全局变量总是有点凌乱。这并不意味着你不能使用它们;这取决于你,只要记住所有的权衡。闭包变量没有那么混乱,但请记住对于循环迭代变量要小心

for i := 0; i < 10; i++ {
    go func() {
        time.Sleep(50 * time.Millisecond)
        fmt.Println(i)  // BEWARE BUG: this prints 10, not 0-9
    }()
}

行为不端。在围棋操场上试试这个;请注意,go vet现在在这里抱怨i的不良使用。

 类似资料:
  • 问题内容: 我有多个goroutine试图同时在同一频道上接收。似乎最后一个在通道上开始接收的goroutine获得了值。这是语言规范中的某个地方,还是未定义的行为? 输出: 操场上的例子 编辑: 我只是意识到它比我想的还要复杂。该消息在所有goroutine中传递。 输出: 操场上的例子 问题答案: 是的,它很复杂,但是有一些经验法则可以使事情变得简单得多。 宁愿使用 传递给go-routine

  • 问题内容: 我需要使用单个任务队列和单个结果队列来启动许多工作程序。每个工人都应该以不同的goroutine开始。我需要等到所有工作人员都将完成并且任务队列将为空后再退出程序。我已经准备了goroutine同步的小例子。主要思想是我们将排队的任务计数,并等待所有工人完成工作。但是当前的实现有时会遗漏值。为什么会发生这种情况以及如何解决问题?示例代码: 问题答案: 使用sync.WaitGroup等

  • 问题内容: 我设法用Jsch通过ssh执行了一条命令,但是当我尝试执行第二条命令时却失败了 为了调试,我将此问题归结为以下几行: 这主要是官方的Exec示例,但这给了我以下输出: 第一个命令成功执行,第二个则没有成功。 有任何想法吗 ? 问题答案: 嘿,jsch和Ubuntu完全一样。(如何)解决了?为每个执行执行新会话会浪费太多时间?此刻,我捕获了jsch异常并搜索“会话未关闭”,然后我重新连接

  • 问题内容: 我在Go中有一个返回两个值的函数。我想将其作为goroutine运行,但是我无法弄清楚创建接收两个值的通道的语法。有人能指出我正确的方向吗? 问题答案: 使用两个值的字段定义自定义类型,然后创建该类型的。 编辑:我还添加了一个使用多个通道而不是自定义类型的示例(在底部)。我不确定哪个更惯用。 例如: 然后 使用自定义类型的频道(Playground)的示例: 产生: LOREM,5 I

  • 前面两节里我们用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是$h$和$w$(像素),那么它可以表示为一个$3\times h\times w$的多维数组。我们将大小为3的这一维称为通道(channel)维。本节我们将介绍含多个输入通道或多个输出通道的卷积核。 多输入通道 当输入数据含多个通道时

  • 出于性能考虑的建议: 实践经验表明,如果你使用并行运算获得高于串行运算的效率:在协程内部已经完成的大部分工作,其开销比创建协程和协程间通信还高。 1 出于性能考虑建议使用带缓存的通道: 使用带缓存的通道可以很轻易成倍提高它的吞吐量,某些场景其性能可以提高至10倍甚至更多。通过调整通道的容量,甚至可以尝试着更进一步的优化其性能。 2 限制一个通道的数据数量并将它们封装成一个数组: 如果使用通道传递大