【问题标题】:Bi-directional communication with a Channel与 Channel 的双向通信
【发布时间】:2020-08-24 07:22:16
【问题描述】:

在下面的代码中:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func getPage(url string) (int, error) {
    resp, err := http.Get(url)
    if err != nil {
        return 0, err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return 0, err
    }

    return len(body), nil
}

func getter(urlChan chan string, size chan int) {
    url := <-urlChan
    length, err := getPage(url)
    if err == nil {
        size <- length
        urlChan <- url
    }
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    sizeChan := make(chan int)
    urlChan := make(chan string)

    for _, url := range urls {
        urlChan <- url
        go getter(urlChan, sizeChan)
    }

    for i := 0; i < len(urls); i++ {
        fmt.Printf("%s has length %d\n", <-urlChan, <-sizeChan)
    }
}

我使用urlChan 作为双向,以知道计算给定大小(sizeChan)的 url(urlChan)。

对于getter() go-routine,在将第一个字符串参数url 设置为urlChan 后,以下输出显示了挂起情况:

$ go install github.com/shamhub/cs61a
$ 
$ 
$ bin/cs61a 

1) 如何在通道上进行双向通信?

2) 如何分析给定进程的 go-routines 状态?

【问题讨论】:

  • 向进程发送 SIGQUIT 以找出进程挂起的位置。通过频道发送url 是不必要的复杂。通过将url 作为参数而不是作为通过通道接收的值传递来简化代码:func getter(url string, size chan int) { ... }
  • 这是不正确的,这将导致不可预测的结果,因为推送到 url 和大小通道可能会在您生成的多个例程中交错。我找不到你会使用相同的通道进行写入然后读取的情况。您的返回频道应该是嵌入了您想要发回的所有必要信息的类型(即:type xx struct { url string; len int; })。
  • @CeriseLimón 是的,我已经这样做了,但是如何将大小与主 goroutine 中的 url 关联起来?
  • 使用struct { url string; size int } 作为通道元素类型。如果您需要知道urls 中的索引,则将索引传递给getter 并使用struct { index int; size int } 作为通道元素类型。
  • @CeriseLimón 我是否需要在main() 中创建元素类型为struct { index int; size int } 的频道并传递给getter()

标签: go channel


【解决方案1】:

正如其他人已经指出的那样,您可以更简单地解决这个问题:

type sized struct {
    url    string
    length int
    err    error
}

func sizer(url string, result chan<- sized, wg *sync.WaitGroup) {
    defer wg.Done()
    length, err := getPage(url)
    result <- sized{url, length, err}
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    ch := make(chan sized)
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go sizer(url, ch, &wg)
    }
    go func() {
        wg.Wait()
        close(ch)
    }()

    for result := range ch {
        if result.err != nil {
            fmt.Printf("%s: %s\n", result.url, result.err)
        } else {
            fmt.Printf("%s: length = %d\n", result.url, result.length)
        }
    }
}

Complete example on playground, though it can't dial out, so not really useful there.

如何在通道上进行双向通信?

你不能。

嗯,这并不完全正确。让我们改为:您不应该

假设我们有一个频道:

var ch chan T

对于某些类型的 T。

频道本身是天生的……好吧,我认为非定向是正确的词:

  • 任何人都可以使用ch &lt;- val将任何类型T的值放入通道中
  • 任何人都可以使用var &lt;- ch从通道中取出任何类型为 T 的值

这显然不是单向的,但我认为将其称为 双向 会产生误导:它有“放入”和“取出”的一面,都集中在一个通道实例中,但频道不与任何特定的用户相关联。您可以将通道值复制到有方向的实例(请参阅What's the point of one-way channels in Go?),在任一方向,但两者实际上都只是对底层非定向通道对象的引用。1 然后,您可以将原始通道或其单向化副本传递给任何用户,或将其中任何一个视为任何人都可以使用的“全局”(宽范围)变量。

现在让我们添加两个实体,我们将其称为 X 和 Y。如果通道 ch缓冲,则 X 和 Y 中的一个或两个都可以放入项目,然后一个或两个都可以放入A和B可以取出物品。项目将按照进入的顺序出现,序列化访问;如果通道已满,则尝试将项目放入将被阻止;但是项目不会专门 X 转移到 Y。特别是:

// Y: paused or stopped

// do this in X when ch is initially empty:
ch <- T{}
v := <-ch

在这里,X 将一个零值T 放入通道,然后将其取出。这不是定向的。 X 得到了它自己的数据。

如果通道为空,但也未满,X 或 Y 可以向其中添加一些内容(作为队列),然后从队列的前面取出一些内容。但这只是将通道用作队列。 (见Is it possible to use Go's buffered channel as a thread-safe queue?

如果这个队列有一些它肯定缺少的特性,你可以使用一个队列进行双向通信(参见Is two way comm possible using a single message queue in C)。但它没有它们。如果你真的需要双向通信,答案很明显:使用两个队列。指定一个队列(一个通道)作为 X-sends-to-Y 通道,另一个作为 Y-sends-to-X 通道。


1底层通道可通过unsafe 访问/恢复,但除非您真的知道自己在做什么,否则不要这样做。向您传递单向实例的人可能不打算让您使用通道的另一“边”。

【讨论】:

  • OK... 所以,如果我们不使用wg,那么range ch 将阻塞main()
  • 好的...您的回答没有解决问题...urlChan &lt;- url 中的getsetter() 出了什么问题?
  • goroutine(G) 是否阻塞http.Get() 的 io 块上的 OS 线程(M)?或者 goroutine(G) 是否通过使 OS 线程 (M) 可用于另一个 go-routine 而从 io 块上的上下文 (P) 中删除?
  • 除了您自己的 cmets 之外,我在此页面上没有看到任何 getsetter,所以这完全无法回答。至于您的urlChan &lt;- url:在您的循环的第一次旅行中,您正在一个无缓冲的通道上发送,没有读取该通道的功能。队列的容量为零,所以是的,此时发送块。
  • 没有读取该频道的功能? &lt;-urlChan 正在阅读 main() go-routine
【解决方案2】:

你必须阅读和练习 go tour,我相信下面代码中使用的每个细节都会在那里解释,但是,这里有一个解决方案

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "sync"
)

func getPage(url string) (int, error) {
    resp, err := http.Get(url)
    if err != nil {
        return 0, err
    }

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return 0, err
    }

    return len(body), nil
}

type result struct {
    url string
    len int
    err error
}

func main() {
    urls := []string{"http://www.google.com/", "http://www.yahoo.com",
        "http://www.bing.com", "http://bbc.co.uk"}

    resChan := make(chan result)
    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            l, err := getPage(url)
            resChan <- result{url: url, len: l, err: err}
        }(url)
    }

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

    for r := range resChan {
        if r.err != nil {
            log.Printf("failed to fetch %q: %v\n", r.url, r.err)
            continue
        }
        fmt.Printf("%s has length %d\n", r.url, r.len)
    }
}

这并不理想,因为它不会限制并发传出请求的数量。

关于,2) How to profile the status of go-routines for a given process?

请参阅 pprof 工具。有一个文档https://golang.org/doc/diagnostics.html

https://stackoverflow.com/a/19145992/4466350

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-06-29
    • 1970-01-01
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多