Go 何时关闭工作池并发模式中的结果通道?

Go 何时关闭工作池并发模式中的结果通道?,go,concurrency,Go,Concurrency,我试图了解golang中的并发是如何工作的,因为我试图批量处理一个大型JSON文件,但我不确定何时应该关闭通道。下面是代码 package main import ( "fmt" "os" "time" ) type Order struct { Recipe string `json:"recipe"` PostCode string `j

我试图了解golang中的并发是如何工作的,因为我试图批量处理一个大型JSON文件,但我不确定何时应该关闭通道。下面是代码

package main

import (
    "fmt"
   "os"
   "time"
)

type Order struct {
    Recipe   string         `json:"recipe"`
    PostCode string         `json:"postcode"`
    Delivery TimeWithFormat `json:"delivery"`
}

type TimeWithFormat struct {
    WeekDay   string
    StartTime time.Time
    EndTime   time.Time
    Valid   bool
}

func main() {
 input, err := os.Open("test.json")
 if err != nil {
    fmt.Println(err)
 }

concurrency := 20
inGest := make(chan []Order)
ret := make(chan map[string]int)

for x := 0; x < concurrency; x++ {
    go processRecipesWorker(inGest, ret)
}

go ParseInMemory(input, inGest)

finalMap := make(map[string]int)
for v := range ret {
    // Combine all incoming results
    for recipe, occurrence := range v {
        if v, ok := finalMap[recipe]; ok {
            finalMap[recipe] = occurrence + v
        } else {
            finalMap[recipe] = occurrence
        }
    }
}

 //Further processing on finalMap

}

func ParseInMemory(input *os.File, ingest chan []Order) {
 data, _ := ioutil.ReadAll(input)
 var orders []Order

 if err := json.Unmarshal(data, &orders); err != nil {
    log.Fatal(err.Error())
}

batch := 100000
for i := 0; i < len(orders); i += batch {
    j := i + batch
    if j > len(orders) {
        j = len(orders)
    }
    ingest <- orders[i:j]
 }
 close(ingest)
}

func processRecipesWorker(in <-chan []helpers.Order, r chan map[string]int) {
 result := make(map[string]int)
 for _, order := range <-in {
    if v, ok := result[order.Recipe]; ok {
        result[order.Recipe] = v + 1
    } else {
        result[order.Recipe] = 1
    }
 }
 r <- result
}
主程序包
进口(
“fmt”
“操作系统”
“时间”
)
类型顺序结构{
配方字符串`json:“配方”`
邮政编码字符串`json:“邮政编码”`
传递时间格式为`json:“传递”`
}
键入TimeWithFormat结构{
平日字符串
开始时间,开始时间
结束时间,时间
有效布尔
}
func main(){
输入,err:=os.Open(“test.json”)
如果错误!=零{
fmt.Println(错误)
}
并发性:=20
摄取:=制造(chan[]订单)
ret:=make(chan map[string]int)
对于x:=0;xlen(订单){
j=len(订单)
}

摄取您现在非常接近:

for _, order := range <-in {
    ...
}

你现在很接近了:

for _, order := range <-in {
    ...
}

在您的特定示例中,如果您知道要启动的goroutine的数量,则可以不使用
WaitGroup
方法来执行此操作

您可以找到一个修改代码的示例,为了简洁起见,我省略了您从文件中读取的部分

它归结为两个主要的修改。首先,您需要一个额外的通道(在我的示例中,它被称为
quit
)来跟踪每个goroutine完成工作的时间。此外,还需要一个用于完成goroutine的计数器(
doneJobs
)。 因此,您的
processRecipesWorker
方法应该如下所示:

func processRecipesWorker(in <-chan []Order, r chan map[string]int, quit chan<- int) {
 result := make(map[string]int)
 for orders := range in {
  for _, order := range orders {  
   if v, ok := result[order.Recipe]; ok {
        result[order.Recipe] = v + 1
    } else {
        result[order.Recipe] = 1
    }
    }
 }
 r <- result
 quit <- 1
}
通过使用
break loop
语句,您可以同时从
select
for
循环中断开循环


总之,@torek使用
WaitGroup
的解决方案是一个很好的解决方案,它提供了Go中使用的一般模式。你可以在关于Go管道的文章中看到这一点,特别是在扇出扇入部分。这个答案只是提供了另一种运行程序的方法,而不使用
WaitGroup
方法。

在您的特定示例中,如果您知道要启动的goroutine的数量,则可以不使用
WaitGroup
方法来执行此操作

您可以找到一个修改代码的示例,为了简洁起见,我省略了您从文件中读取的部分

它归结为两个主要的修改。首先,您需要一个额外的通道(在我的示例中,它被称为
quit
)来跟踪每个goroutine完成工作的时间。此外,还需要一个用于完成goroutine的计数器(
doneJobs
)。 因此,您的
processRecipesWorker
方法应该如下所示:

func processRecipesWorker(in <-chan []Order, r chan map[string]int, quit chan<- int) {
 result := make(map[string]int)
 for orders := range in {
  for _, order := range orders {  
   if v, ok := result[order.Recipe]; ok {
        result[order.Recipe] = v + 1
    } else {
        result[order.Recipe] = 1
    }
    }
 }
 r <- result
 quit <- 1
}
通过使用
break loop
语句,您可以同时从
select
for
循环中断开循环


总之,@torek使用
WaitGroup
的解决方案是一个很好的解决方案,它提供了Go中使用的一般模式。你可以在关于Go管道的文章中看到这一点,特别是在扇出扇入部分。这个答案只是提供了另一种运行程序的方法,而不使用
WaitGroup
方法。

一般规则是“当没有任何内容可写入时关闭通道”。这就是你对
摄取
频道所做的,但当然,在那里,很容易知道什么时候没有东西可以写入,因为只有一个写入程序。
ret
频道有一个问题:有
并发
工作者在处理它。那么,在完成写入后如何关闭它?你需要有最后一个writer告诉某人/某事他已经做了。但是哪一个是最后一个呢?输入
sync.WaitGroup
…通过
sync.WaitGroup
实体,您可以创建一个WaitGroup,
wg
,您可以重复向其添加1(或一些已知值,例如
并发性
)。然后你剥离工人。当每个工人完成后,他会调用
wg.done()
。然后你再剥离一个goroutine,它只调用
wg.Wait()
,然后是
close(channel)
wg.Wait()
等待所有工人调用
done()
(此时计数变为零,
wg.Wait()
返回)。这表示没有其他人在写,所以现在您有了最后一个goroutine来关闭通道。@torek我尝试使用
WaitGroup()
,但问题是我的工作人员何时应该调用
.Done())
因为我的批处理可以超过我的
并发数。
.Done()
的调用导致部分数据处理。一般规则是“在没有可写入的内容时关闭通道”。这就是你对
摄取
频道所做的,但当然,在那里,很容易知道什么时候没有东西可以写入,因为只有一个写入程序。
ret
频道有一个问题:有
并发
工作者在处理它。那么,在完成写入后如何关闭它?你需要有最后一个writer告诉某人/某事他已经做了。但是哪一个是最后一个呢?输入
sync.WaitGroup
…通过
sync.WaitGroup
实体,您可以创建一个WaitGroup,
wg
,您可以在其中重复添加1(或一些已知值,如
loop:
for{
  select {

    case v := <- ret:
      // Combine all incoming results
      for recipe, occurrence := range v {
        if v, ok := finalMap[recipe]; ok {
            finalMap[recipe] = occurrence + v
        } else {
            finalMap[recipe] = occurrence
        }
      }
    case <-quit:
      doneJobs++
      fmt.Println(doneJobs)
      if doneJobs >= concurrency {
       break loop
      }
  }
}