【问题标题】:Confusion regarding channel directions and blocking in Go关于 Go 中的通道方向和阻塞的混淆
【发布时间】:2017-09-29 02:29:51
【问题描述】:

在函数定义中,如果通道是一个没有方向的参数,它是否必须发送或接收一些东西?

func makeRequest(url string, ch chan<- string, results chan<- string) {
    start := time.Now()

    resp, err := http.Get(url)
    defer resp.Body.Close()
    if err != nil {
        fmt.Printf("%v", err)
    }

    resp, err = http.Post(url, "text/plain", bytes.NewBuffer([]byte("Hey")))
    defer resp.Body.Close()

    secs := time.Since(start).Seconds()

    if err != nil {
        fmt.Printf("%v", err)
    }
    // Cannot move past this.
    ch <- fmt.Sprintf("%f", secs) 
    results <- <- ch
}

func MakeRequestHelper(url string, ch chan string, results chan string, iterations int) {
    for i := 0; i < iterations; i++ {
        makeRequest(url, ch, results)
    }
    for i := 0; i < iterations; i++ {
        fmt.Println(<-ch)
    }
}

func main() {
    args := os.Args[1:]
    threadString := args[0]
    iterationString := args[1]
    url := args[2]
    threads, err := strconv.Atoi(threadString)
    if err != nil {
        fmt.Printf("%v", err)
    }
    iterations, err := strconv.Atoi(iterationString)
    if err != nil {
        fmt.Printf("%v", err)
    }

    channels := make([]chan string, 100)
    for i := range channels {
        channels[i] = make(chan string)
    }

    // results aggregate all the things received by channels in all goroutines
    results := make(chan string, iterations*threads)

    for i := 0; i < threads; i++ {
        go MakeRequestHelper(url, channels[i], results, iterations)

    }

    resultSlice := make([]string, threads*iterations)
    for i := 0; i < threads*iterations; i++ {
        resultSlice[i] = <-results
    }
}

在上面的代码中,

ch

似乎阻塞了每个执行 makeRequest 的 goroutine。

我是 Go 并发模型的新手。我知道从通道发送和接收会阻塞,但很难发现这段代码中的阻塞是什么。

【问题讨论】:

  • gobyexample.com/channels 有更多频道示例
  • 只有在错误为零时才执行defer resp.Body.Close()。否则 resp 为 nil 并且你的程序会崩溃。

标签: go concurrency goroutine


【解决方案1】:

我不太确定你在做什么......看起来真的很复杂。我建议您阅读如何使用频道。

https://tour.golang.org/concurrency/2

话虽如此,您的代码中发生了很多事情,因此将其简化为更简单的内容要容易得多。 (可以进一步简化)。我离开 cmets 来理解代码。

package main

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

// using structs is a nice way to organize your code
type Worker struct {
    wg        sync.WaitGroup
    semaphore chan struct{}
    result    chan Result
    client    http.Client
}

// group returns so that you don't have to send to many channels
type Result struct {
    duration float64
    results  string
}

// closing your channels will stop the for loop in main
func (w *Worker) Close() {
    close(w.semaphore)
    close(w.result)
}

func (w *Worker) MakeRequest(url string) {
    // a semaphore is a simple way to rate limit the amount of goroutines running at any single point of time
    // google them, Go uses them often
    w.semaphore <- struct{}{}
    defer func() {
        w.wg.Done()
        <-w.semaphore
    }()

    start := time.Now()

    resp, err := w.client.Get(url)
    if err != nil {
        log.Println("error", err)
        return
    }
    defer resp.Body.Close()

    // don't have any examples where I need to also POST anything but the point should be made
    // resp, err = http.Post(url, "text/plain", bytes.NewBuffer([]byte("Hey")))
    // if err != nil {
    // log.Println("error", err)
    // return
    // }
    // defer resp.Body.Close()

    secs := time.Since(start).Seconds()

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("error", err)
        return
    }
    w.result <- Result{duration: secs, results: string(b)}
}

func main() {
    urls := []string{"https://facebook.com/", "https://twitter.com/", "https://google.com/", "https://youtube.com/", "https://linkedin.com/", "https://wordpress.org/",
        "https://instagram.com/", "https://pinterest.com/", "https://wikipedia.org/", "https://wordpress.com/", "https://blogspot.com/", "https://apple.com/",
    }

    workerNumber := 5
    worker := Worker{
        semaphore: make(chan struct{}, workerNumber),
        result:    make(chan Result),
        client:    http.Client{Timeout: 5 * time.Second},
    }

    // use sync groups to allow your code to wait for
    // all your goroutines to finish
    for _, url := range urls {
        worker.wg.Add(1)
        go worker.MakeRequest(url)
    }

    // by declaring wait and close as a seperate goroutine
    // I can get to the for loop below and iterate on the results
    // in a non blocking fashion
    go func() {
        worker.wg.Wait()
        worker.Close()
    }()

    // do something with the results channel
    for res := range worker.result {
        fmt.Printf("Request took %2.f seconds.\nResults: %s\n\n", res.duration, res.results)
    }
}

【讨论】:

  • 非常感谢您的回复并花时间编写非常干净的代码!我将尝试更多地了解信号量和并发性。我浏览了关于 goroutines、Effective Go、Go by example、Go concurrency patterns talk by Rob Pike 和 Go tour 的 Donovan 和 Kernighan 书籍章节,但我想理解这些概念需要更多练习。我试图模拟当大量用户同时发送 GET 和 POST 请求时我的玩具服务器会发生什么。所有这些都是家庭作业的一部分,我是唯一一个使用 Go 的人。
  • 这些都是很棒的资源,你很快就会学习 Go。
  • 上面还有一些修改可以做。我会为等待组的东西写一些包装方法。例如。 worker.wg.Add(1) 有点恶心,worker.Add(1) 会更好。我没有做太多的日志记录,但需要做一些事情。我建议你使用这样的东西来记录。 github.com/sirupsen/logrus 也在 goroutines 中使用日志而不是 fmt。 log 包可以安全地同时使用。考虑在浮点数 golang.org/pkg/time/#Durationflag 上使用 time.Duration @ 在 os.Args golang.org/pkg/flag
  • 另外,如果你正在测试一个用 Go 编写的服务器,上面的代码不会真正加载太多。信号量会阻止这种情况,您还需要一个无限循环,该循环要么在某些超时或信号时中断。 (两者都是 Go 中定义的通道)。您还可以使用 siege joedog.org/siege-home 或类似 github.com/btfak/sniper.. 的其他类似的软件包来调整您的服务器。
  • w.result &lt;- Result{duration: secs, results: string(b)} 不会阻止吗?它不会导致所有 go-routines 卡在那个点上,从而永远不会调用 wg.done() 吗?
【解决方案2】:

channels 中的通道是 nil(不执行 make;您制作切片而不是通道),因此任何发送或接收都会阻塞。我不确定你到底想在这里做什么,但这是基本问题。

请参阅https://golang.org/doc/effective_go.html#channels,了解频道的工作原理。

【讨论】:

  • 感谢您的回复!即使我在通道切片中的每个通道上调用makech &lt;-&lt;-results 似乎仍然处于阻塞状态。我正在尝试创建指定数量的 goroutine。每个 goroutine 在一个 url 上调用 GET 和 POST 指定次数,并记录完成每个 goroutine 每次迭代所花费的时间。
  • 如果通道没有缓冲,任何发送或接收都会阻塞,直到相应的接收或发送执行完毕,因此这些操作必须在不同的 goroutine 中。您有一个 goroutine 尝试发送,然后尝试在通道上接收。
猜你喜欢
  • 1970-01-01
  • 2015-02-25
  • 2017-04-21
  • 2015-12-08
  • 2019-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多