-
-
Save thalesfsp/853c0071f7d8b031a41ea120edd47a6d to your computer and use it in GitHub Desktop.
Bounded Parallel Get Requests in Golang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"net/http" | |
"sort" | |
"time" | |
) | |
// a struct to hold the result from each request including an index | |
// which will be used for sorting the results after they come in | |
type result struct { | |
index int | |
res http.Response | |
err error | |
} | |
// boundedParallelGet sends requests in parallel but only up to a certain | |
// limit, and furthermore it's only parallel up to the amount of CPUs but | |
// is always concurrent up to the concurrency limit | |
func boundedParallelGet(urls []string, concurrencyLimit int) []result { | |
// this buffered channel will block at the concurrency limit | |
semaphoreChan := make(chan struct{}, concurrencyLimit) | |
// this channel will not block and collect the http request results | |
resultsChan := make(chan *result) | |
// make sure we close these channels when we're done with them | |
defer func() { | |
close(semaphoreChan) | |
close(resultsChan) | |
}() | |
// keen an index and loop through every url we will send a request to | |
for i, url := range urls { | |
// start a go routine with the index and url in a closure | |
go func(i int, url string) { | |
// this sends an empty struct into the semaphoreChan which | |
// is basically saying add one to the limit, but when the | |
// limit has been reached block until there is room | |
semaphoreChan <- struct{}{} | |
// send the request and put the response in a result struct | |
// along with the index so we can sort them later along with | |
// any error that might have occoured | |
res, err := http.Get(url) | |
result := &result{i, *res, err} | |
// now we can send the result struct through the resultsChan | |
resultsChan <- result | |
// once we're done it's we read from the semaphoreChan which | |
// has the effect of removing one from the limit and allowing | |
// another goroutine to start | |
<-semaphoreChan | |
}(i, url) | |
} | |
// make a slice to hold the results we're expecting | |
var results []result | |
// start listening for any results over the resultsChan | |
// once we get a result append it to the result slice | |
for { | |
result := <-resultsChan | |
results = append(results, *result) | |
// if we've reached the expected amount of urls then stop | |
if len(results) == len(urls) { | |
break | |
} | |
} | |
// let's sort these results real quick | |
sort.Slice(results, func(i, j int) bool { | |
return results[i].index < results[j].index | |
}) | |
// now we're done we return the results | |
return results | |
} | |
// we'll use the init function to set up the benchmark | |
// by making a slice of 100 URLs to send requets to | |
var urls []string | |
func init() { | |
for i := 0; i < 100; i++ { | |
urls = append(urls, "http://httpbin.org/get") | |
} | |
} | |
// the main function sets up an anonymous benchmark func | |
// that will time how long it takes to get all the URLs | |
// at the specified concurrency level | |
// | |
// and you should see something like the following printed | |
// depending on how fast your computer and internet is | |
// | |
// 5 bounded parallel requests: 100/100 in 5.533223255 | |
// 10 bounded parallel requests: 100/100 in 2.5115351219 | |
// 25 bounded parallel requests: 100/100 in 1.189462884 | |
// 50 bounded parallel requests: 100/100 in 1.17430002 | |
// 75 bounded parallel requests: 100/100 in 1.001383863 | |
// 100 bounded parallel requests: 100/100 in 1.3769354 | |
func main() { | |
benchmark := func(urls []string, concurrency int) string { | |
startTime := time.Now() | |
results := boundedParallelGet(urls, concurrency) | |
seconds := time.Since(startTime).Seconds() | |
tmplate := "%d bounded parallel requests: %d/%d in %v" | |
return fmt.Sprintf(tmplate, concurrency, len(results), len(urls), seconds) | |
} | |
fmt.Println(benchmark(urls, 10)) | |
fmt.Println(benchmark(urls, 25)) | |
fmt.Println(benchmark(urls, 50)) | |
fmt.Println(benchmark(urls, 75)) | |
fmt.Println(benchmark(urls, 100)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment