Home > Backend Development > Golang > How to Maximize Concurrent HTTP Requests in Go Using Goroutines and Worker Pools?

How to Maximize Concurrent HTTP Requests in Go Using Goroutines and Worker Pools?

Patricia Arquette
Release: 2024-12-02 01:40:10
Original
909 people have browsed it

How to Maximize Concurrent HTTP Requests in Go Using Goroutines and Worker Pools?

How to Maximize Concurrent HTTP Requests in Go

Many programming languages and frameworks provide tools for making HTTP requests, but if you need to send a large number of requests simultaneously, it's essential to understand how to maximize concurrency to optimize performance. This article will delve into the intricacies of "maxing out" concurrent HTTP requests in Go, utilizing goroutines to unleash the full potential of your system's processing capabilities.

The Problem:

Let's consider a scenario where we want to generate a million HTTP requests to a specific URL as quickly as possible, using multiple goroutines. However, the code provided in the initial post resulted in errors due to file descriptor limits being exceeded. This is a common issue when attempting to handle a large number of concurrent requests.

The Solution:

To effectively maximize concurrency, we can address the file descriptor limitation by employing a buffered channel as a semaphore mechanism within a worker pool model. Here's a breakdown of the solution:

  • Worker Pool:

    • Create a worker pool that manages the number of goroutines handling HTTP requests.
    • Each worker routine processes one request at a time and signals when it's ready for another.
  • Semaphore Channel:

    • Use a buffered channel with a limited capacity to control the number of simultaneous HTTP requests.
    • When a worker routine completes a request, it signals the semaphore channel, allowing another request to be processed.
  • Dispatcher:

    • The dispatcher continuously generates HTTP requests and sends them to the worker pool via a request channel.
    • As workers become available, the dispatcher pulls requests from the channel and assigns them to workers.
  • Consumer:

    • A separate goroutine monitors the response channel and increments the connection count for each valid response. It also calculates the average response time.

Optimized Code:

package main

import (
    "flag"
    "fmt"
    "log"
    "net/http"
    "runtime"
    "time"
)

var (
    reqs int
    max  int
)

func init() {
    flag.IntVar(&reqs, "reqs", 1000000, "Total requests")
    flag.IntVar(&max, "concurrent", 200, "Maximum concurrent requests")
}

type Response struct {
    *http.Response
    err error
}

// Dispatcher
func dispatcher(reqChan chan *http.Request) {
    defer close(reqChan)
    for i := 0; i < reqs; i++ {
        req, err := http.NewRequest("GET", "http://localhost/", nil)
        if err != nil {
            log.Println(err)
        }
        reqChan <- req
    }
}

// Worker Pool
func workerPool(reqChan chan *http.Request, respChan chan Response) {
    t := &amp;http.Transport{}
    for i := 0; i < max; i++ {
        go worker(t, reqChan, respChan)
    }
}

// Worker
func worker(t *http.Transport, reqChan chan *http.Request, respChan chan Response) {
    for req := range reqChan {
        resp, err := t.RoundTrip(req)
        r := Response{resp, err}
        respChan <- r
    }
}

// Consumer
func consumer(respChan chan Response) (int64, int64) {
    var (
        conns int64
        size  int64
    )
    for conns < int64(reqs) {
        select {
        case r, ok := <-respChan:
            if ok {
                if r.err != nil {
                    log.Println(r.err)
                } else {
                    size += r.ContentLength
                    if err := r.Body.Close(); err != nil {
                        log.Println(r.err)
                    }
                }
                conns++
            }
        }
    }
    return conns, size
}

func main() {
    flag.Parse()
    runtime.GOMAXPROCS(runtime.NumCPU())
    reqChan := make(chan *http.Request)
    respChan := make(chan Response)
    start := time.Now()
    go dispatcher(reqChan)
    go workerPool(reqChan, respChan)
    conns, size := consumer(respChan)
    took := time.Since(start)
    ns := took.Nanoseconds()
    av := ns / conns
    average, err := time.ParseDuration(fmt.Sprintf("%d", av) + "ns")
    if err != nil {
        log.Println(err)
    }
    fmt.Printf("Connections:\t%d\nConcurrent:\t%d\nTotal size:\t%d bytes\nTotal time:\t%s\nAverage time:\t%s\n", conns, max, size, took, average)
}
Copy after login

This improved code combines the elements discussed earlier to create a highly efficient worker pool-based system for sending a large volume of HTTP requests concurrently. By carefully controlling the number of concurrent requests through the semaphore channel, we can avoid any issues related to file descriptor limits and maximize the utilization of our system's resources.

In summary, by utilizing goroutines, a semaphore channel, a worker pool, and a dedicated consumer for handling responses, we can effectively "max out" the concurrent HTTP requests in Go. This approach enables us to conduct performance tests and stress tests effectively, pushing our systems to the limits and gaining valuable insights into their capabilities.

The above is the detailed content of How to Maximize Concurrent HTTP Requests in Go Using Goroutines and Worker Pools?. For more information, please follow other related articles on the PHP Chinese website!

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template