4

I am new to Go and am looking for the correct way of using net/http or fasthttp with goroutines. Unfortunately there are not many fasthttp client examples out there.

I found the following code: (Example1)

package main

import (
    "bufio"
    "fmt"
    "github.com/valyala/fasthttp"
    "log"
    "net"
    "os"
    "sync"
    "time"
)

func grabPage(fastClient *fasthttp.Client, i int, wg *sync.WaitGroup) {
    defer wg.Done()
    _, body, err := fastClient.GetTimeout(nil, "https://en.wikipedia.org/wiki/Immanuel_Kant", time.Duration(time.Second*20))
    if err != nil {
        log.Fatal(err)
    }
    f, err := os.Create(fmt.Sprintf("./data/%d.txt", i))
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    w := bufio.NewWriter(f)
    w.Write(body)
}

func main() {
    var wg sync.WaitGroup
    total := 500

    c := &fasthttp.Client{
        Dial: func(addr string) (net.Conn, error) {
            return fasthttp.DialTimeout(addr, time.Second*10)
        },
        MaxConnsPerHost: total,
    }

    wg.Add(total)
    for index := 0; index < total; index++ {
        go grabPage(c, index, &wg)
    }
    wg.Wait()
}

In this code the developer creates a fasthttp.Client instance in the main() function and passes it to the goroutine using go grabPage(c, ...). For my understanding, this way you create one instance and all the requests use this one instance to do the job.

On another page, a developer uses something like that: (Example2)

func grabPage(i int, wg *sync.WaitGroup) {
    defer wg.Done()

    fastClient := &fasthttp.Client{
        Dial: func(addr string) (net.Conn, error) {
            return fasthttp.DialTimeout(addr, time.Second*10)
        },
        MaxConnsPerHost: 500,
    }

    _, body, err := fastClient.GetTimeout(nil, "https://en.wikipedia.org/wiki/Immanuel_Kant", time.Duration(time.Second*20))
    if err != nil {
        log.Fatal(err)
    }
    f, err := os.Create(fmt.Sprintf("./data/%d.txt", i))
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    w := bufio.NewWriter(f)
    w.Write(body)
}

The big question is, are both solutions correct? Or does the Example2 solution really create a new instance and use a lot of memory for every goroutine?

I made the examples out of snippets for my question, in the Example2 for sure defer are missing. This is not part of the question.

A small side question: (fastClient *fasthttp.Client, i int, wg *sync.WaitGroup) -> fastClient and wg are pointers, so why call grabPage(c, index, &wg) and not grabPage(&c, index, &wg)?

Marc
  • 19,394
  • 6
  • 47
  • 51
ChrisG
  • 202
  • 2
  • 13

1 Answers1

5

The big answer: both are correct (as in they work just fine), just different.

Per the docs, a fasthttp.Client is safe for concurrent use so sharing one instance is fine. It may run into concurrent connection limits but that might not be an issue.

The second example does have some overhead and will not be able to reuse connections or parameters, but again this could be a use case where it does not matter (if I only perform two operations, saving on the overhead might not be worth optimizing for).

For the second part of the question:

  • c is already a *fasthttp.Client, so there's not need to take its address (&fasthttp.Client returns a pointer to a new fasthttp.Client)
  • wg is a plain sync.WaitGroup so the address must be taken
Marc
  • 19,394
  • 6
  • 47
  • 51
  • Thank you for your answer and for correcting my question. So with 15000 and more httprequests (request parameters always same) it should be more efficient to create one instance and reuse it - agree? – ChrisG Jun 02 '20 at 09:52
  • It should definitely be more efficient (in terms of memory as well as connection reuse). Though of course the best way is to test it. – Marc Jun 02 '20 at 09:53
  • Ok, yes of course, i will test it. I was not sure if Go has a mechanism to bundle new instances in goroutines (already on compilation time). Something like `fastClient := &fasthttp.Client` will make a global instance and pass all the future instance requests to it (like a pool). Very hard to explain, i hope you understand what my thoughts was. – ChrisG Jun 02 '20 at 10:00