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

我正在通过编写一个 HTTP 测试客户端来学习 Go,比如 Apache 的ab. 下面的代码看起来非常简单:我创建了一个可配置数量的 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 个 goroutine。如果每个 HTTP 请求都创建一个,这是有道理的,如何执行会在循环中创建许多 goroutine 的操作?或者是完全不相关的问题。


叮当猫咪
浏览 187回答 1
1回答

当年话下

我认为导致挂起的顺序是:http.Get在makeRequests失败(连接拒绝,请求超时,等等),返回一个nil响应和误差值错误被忽略并makeRequests转移到下一个请求如果发生任何错误,将makeRequests少于预期数量的结果发布到resultChan该for .. range .. chan环路deployRequests从来没有打破,因为results.total总是小于messages一种解决方法是:如果http.Get返回错误值,请将nil响应发布到resultChan:&nbsp; &nbsp; resp, err := http.Get(url)&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; resultChan <- nil&nbsp; &nbsp; } else if resp != nil {&nbsp; &nbsp; &nbsp; &nbsp; resultChan <- resp&nbsp; &nbsp; }在 中deployRequests,如果for循环从 中读取 nil 值,则将其resultChan计为错误:for response := range resultChan {&nbsp; &nbsp; if response == nil {&nbsp; &nbsp; &nbsp; &nbsp; results.errors += 1&nbsp; &nbsp; } else if response.StatusCode != 200 {&nbsp; &nbsp; // ...
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go