Speed Up Go Programs with Goroutines and Channels: From Seconds to Milliseconds
This article demonstrates how Go's goroutines and channels can transform a sequential task that takes seconds per operation into a highly concurrent solution that completes millions of operations in just a few seconds, complete with code examples and performance benchmarks.
Sequential Execution in Go
Computers can perform many tasks quickly, but a simple Go function that sleeps for one second illustrates the cost of sequential execution. Running the function ten times takes about ten seconds, and a million repetitions would require roughly ten days.
func doSomething() {
time.Sleep(time.Second)
}
func doManyThings(n int) {
for i := 0; i < n; i++ {
doSomething()
}
}
func main() {
doManyThings(10)
}Benchmarking this code shows a runtime of around 10 seconds for ten calls:
$ time go run main.go
real 0m10.256sIntroducing Concurrency with Goroutines
By launching each call in its own goroutine, the work can be performed in parallel on multiple CPU cores, dramatically reducing total execution time.
func doManyThings(n int) {
var wg sync.WaitGroup // concurrency‑safe counter
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
doSomething()
}()
}
wg.Wait()
}
func main() {
doManyThings(1_000_000)
}On a typical laptop this concurrent version finishes one million one‑second sleeps in about 2.7 seconds, a speed‑up of several orders of magnitude.
$ time go run main.go
real 0m2.667sCollecting Results with Channels
When each goroutine returns a value (for example, an error), a channel can be used to gather the results without needing a shared counter.
func doSomething() error {
time.Sleep(time.Second)
if rand.Intn(100) == 0 { // ~1% failure rate
return errors.New("we got a problem")
}
return nil
}
func doManyThings(n int) []error {
ch := make(chan error, n) // buffered channel holds all results
for i := 0; i < n; i++ {
go func() {
ch <- doSomething()
}()
}
var errs []error
for i := 0; i < n; i++ {
if err := <-ch; err != nil {
errs = append(errs, err)
}
}
return errs
}
func main() {
n := 1_000_000
errs := doManyThings(n)
fmt.Printf("Doing %d things there were %d errors.
", n, len(errs))
}This approach eliminates the need for explicit counters; the second loop that reads from the channel implicitly waits for all goroutines to finish, and a buffered channel ensures that sends never block even if the receiver hasn't started reading yet.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
