【问题标题】:How to close channel when there is unknown number of inputs to it?当有未知数量的输入时如何关闭通道?
【发布时间】:2021-04-02 19:15:00
【问题描述】:

API:https://jsonmock.hackerrank.com/api/articles?page=1

package main

import (
    "fmt"
    "net/http"
    "encoding/json"
    "strconv"
    "sync"
)

type ArticleResponse struct {
    Page       int `json:"page"`
    PerPage    int `json:"per_page"`
    Total      int `json:"total"`
    TotalPages int `json:"total_pages"`
    Data []Article `json:"data"`
}

type Article struct {
    Title       string      `json:"title"`
    URL         string      `json:"url"`
    Author      string      `json:"author"`
    NumComments int         `json:"num_comments"`
    StoryID     int32 `json:"story_id"`
    StoryTitle  string `json:"story_title"`
    StoryURL    string `json:"story_url"`
    ParentID    int32 `json:"parent_id"`
    CreatedAt   int         `json:"created_at"`
}

type CommentTitle struct{
    NumberOfComments int `json:"number_of_comments"`
    Title string `json:"title"`
    Page int `json:"from_page"`
}

const (
    GET_HOST_URL = "https://jsonmock.hackerrank.com/"
    PATH = "api/articles"
)

var wg sync.WaitGroup

func main(){
    comment_title_chan := make(chan CommentTitle)
    var commentTitleSlice []CommentTitle
    
    // pilot call to get total number of pages
    totalPage := makePaginatedRequest(1, comment_title_chan, true)

    // making concurrent requests to multiple pages at once
    for j:=1;j<=totalPage;j++{
        go makePaginatedRequest(j, comment_title_chan, false)
    }
    for j:=0; j<20;j++ {
        commentTitleSlice = append(commentTitleSlice, <-comment_title_chan)
    }
    
    for _,article := range commentTitleSlice{
        fmt.Println(article.NumberOfComments, "\t\t", article.Title)
    }
}

func makePaginatedRequest(pageNo int, chunk chan CommentTitle, pilotMode bool) int{
    uri := GET_HOST_URL + PATH
    req, err := http.NewRequest("GET", uri, nil)
    if err != nil {
        fmt.Println(err)
    }
    q := req.URL.Query()
    q.Add("page", strconv.Itoa(pageNo))
    req.URL.RawQuery = q.Encode()
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error on response.\n[ERROR] -", err)
    }
    defer resp.Body.Close()
    var articleResponse ArticleResponse
    if err = json.NewDecoder(resp.Body).Decode(&articleResponse) ; err != nil{
        fmt.Println(err)
    }
    if !pilotMode{
        for _, article := range articleResponse.Data{
            if(article.Title != "" && article.NumComments != 0){
                ct := CommentTitle{article.NumComments, article.Title, pageNo}
                wg.Add(1)
                chunk <- ct
                wg.Done()
            }
        }
        wg.Wait()
    }
    return articleResponse.TotalPages
}

问题陈述:

有一个 api 在查询参数中传递页码时提供数据。我应该调用所有页面并获取所有具有有效标题和 cmets 字段数的文章。

解决方案:

第 1 步:我首先对 api 进行试点调用以了解页面数,因为它是响应 json 的一部分。

第 2 步:我启动了多个 goroutine(goroutine 数 = 总页数)

step-3:每个goroutine都会调用相应的页面,获取数据并发送到数据通道。

step-4:将channel接收到的数据附加到一个slice,slice用于进一步计算(根据文章的cmet数量排序)

问题: 我不知道记录的总数 - 有多少是有效的,因此我不知道何时从发送者关闭通道(在我的场景中是多个发送者和单个接收者)。

我尝试使用更多额外的信号通道,但我什么时候才能知道所有 goroutine 都已完成它们的工作,以便我可以发送信号以进行进一步计算?

我什至使用过 WaitGroup,但这是在单个 goroutine 级别 - 我仍然无法知道所有 goroutine 何时完成它的工作。

SO 中的另一个类似问题并没有多大帮助:Closing channel of unknown length

更新:在代码中,我将 j 循环值硬编码为 20 - 这正是我面临的问题。我不知道循环到哪里,如果我将它增加到超过 50,接收被阻止。

【问题讨论】:

  • 更新:在代码中,我将 j 循环值硬编码为 20 - 这正是我面临的问题。

标签: go concurrency goroutine


【解决方案1】:

您错误地使用了等待组。

在创建 goroutine 时添加到 waitgroup,并在单独的 goroutine 中等待所有内容完成,然后关闭通道:

for j:=1;j<=totalPage;j++{
        wg.Add(1)
        go makePaginatedRequest(j, comment_title_chan, false)
}
go func() {
  wg.Wait()
  close(comment_title_chan)
}()

当 goroutine 返回时将其标记为完成:

func makePaginatedRequest(pageNo int, chunk chan CommentTitle, pilotMode bool) int{
  defer wg.Done()
  ...

【讨论】:

  • 我同意。在更严肃的解决方案中,我会使用sync/atomic 计数器,以便每个 goroutine 在完成其工作后自动将其减 1,并且无论哪个发现它已将计数器减为零,都会关闭通道。但我仍然推荐现有的解决方案——如this answer中所述修复。
  • 感谢您的建议,但在实施您的建议后我又遇到了一个错误:恐慌:同步:负 WaitGroup 计数器
  • 您必须删除现有的等待组代码。
  • 是的,我删除了现有的等待组,这是包含您建议的代码:play.golang.org/p/DxZBtraN2JD(请不要在操场上尝试,复制到本地并运行)
  • 知道了,defer wg.Done() 应该在 if !pilotMode{..} 块内,谢谢,这行得通,我不得不考虑用另一个 goroutine 解除阻塞。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-09
  • 2019-02-12
  • 2015-09-04
相关资源
最近更新 更多