为什么这个并发HTTP客户端会随机崩溃?

时间:2014-03-20 13:13:36

标签: concurrency go channel

我正在通过编写像Apache的ab这样的HTTP测试客户端来学习Go。下面的代码看起来非常简单:我创建了一个可配置数量的goroutine,每个goroutine都发送一部分整体HTTP请求并记录结果。我遍历resultChan频道并检查/记录每个结果。这可以在消息数量为100时找到。但是,当我增加消息数量时,它会挂起并且htop显示该过程的VIRT为138G。

以下是相关代码:

package main

import "net/http"
import "fmt"
import "time"

const (
    SUCCESS   = iota
    TOTAL = iota
    TIMEOUT = iota
    ERROR = iota
)

type Result struct {
    successful    int
    total         int
    timeouts      int
    errors        int
    duration      time.Duration
}

func makeRequests(url string, messages int, resultChan chan<- *http.Response) {
    for i := 0; i < messages; i++ {
        resp, _ := http.Get(url)
        if resp != nil {
            resultChan <- resp
        }
    }
}

func deployRequests(url string, threads int, messages int) *Result {
    results := new (Result)
    resultChan := make(chan *http.Response)
    start := time.Now()
    defer func() {
        fmt.Printf("%s\n", time.Since(start))
    }()
    for i := 0; i < threads; i++ {
        go makeRequests(url, (messages/threads) + 1, resultChan)
    }

    for response := range resultChan {
        if response.StatusCode != 200 {
            results.errors += 1
        } else {
            results.successful += 1
        }
        results.total += 1
        if results.total == messages {
            return results
        }
    }
    return results
}

func main () {
    results := deployRequests("http://www.google.com", 10, 1000)
    fmt.Printf("Total: %d\n", results.total)
    fmt.Printf("Successful: %d\n", results.successful)
    fmt.Printf("Error: %d\n", results.errors)
    fmt.Printf("Timeouts: %d\n", results.timeouts)
    fmt.Printf("%s", results.duration)
}

显然有些事情缺失或愚蠢地完成(没有超时检查,频道是同步的等等)但是我想在修复之前让基本案例工作。编写的程序导致如此多的内存分配是什么?

据我所知,只有10个goroutines。如果每个HTTP请求创建一个,这是有意义的,如何执行将在循环中创建许多goroutine的操作?或者这个问题完全不相关。

1 个答案:

答案 0 :(得分:3)

我认为导致挂起的顺序是:

    http.Get中的
  1. makeRequests失败(拒绝连接,请求超时等),返回nil响应和错误值
  2. 忽略错误,makeRequests转到下一个请求
  3. 如果发生任何错误,makeRequests发布的结果少于预期的结果数resultChan
  4. for .. range .. chan中的deployRequests循环永不中断,因为results.total始终小于messages
  5. 一种解决方法是:

    如果http.Get返回错误值,请向nil发送resultChan个回复:

        resp, err := http.Get(url)
        if err != nil {
            resultChan <- nil
        } else if resp != nil {
            resultChan <- resp
        }
    

    deployRequests中,如果for循环从resultChan读取零值,则将其视为错误:

    for response := range resultChan {
        if response == nil {
            results.errors += 1
        } else if response.StatusCode != 200 {
    
        // ...