Concurrency in Go (Part 3)

The most common use case is when developers are trying to send multiple HTTP requests to different endpoints. Let’s say we want to send to 10 endpoints. The average time to execute each endpoint is 100ms. We will get 10 endpoints x 100ms = 1000ms if we do it sequentially. This is a raw calculation. It does not include how we parse the response yet. Let’s solve this case properly.
First, let's reproduce the problem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
It looks like our code is clean. That code was executed for around 3.4s. Take a look at the code carefully. What is the bug?
Okay, here is a better version.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
That code successfully reduces the execution time from around 3.4s to around 840ms. Here are the points:
- Use
errgroup.Groupto group those executions and catch the error later. It is better than usinggo func() {}()because theerrgroup.Groupmade us able to catch the error cleanly. - Use
make([]product, n)andproducts[i-1] = productinstead of[]product{}andproducts = append(products, product). Use array instead of slice. So we get indexes and can assign value to them. - Using a single
*http.Clientis no problem. We don't need many HTTP clients (eg;[]*http.Client) to handle many requests because a single*http.Clientreuses TCP connections efficiently via connection pooling. Creating multiple clients may lead to unnecessary connection duplications, increasing system resource usage.
The code above is an example of using errgroup.Group. You can use the same technique for gRPC, GraphQL, etc.