Goroutines
By Syncfusion®, Inc. · Last updated
Concurrency in Go: concurrency vs parallelism, starting goroutines with go, waiting with WaitGroup, anonymous goroutines, what happens when main ends, and concurrent processing.
What Is Concurrency?
Concurrency = a program working on many jobs during the same period of time, switching between them.
Goroutine = a very light worker that runs a function at the same time as the rest of the program.
Go was built for this. Starting a goroutine takes one word: go.
Simple lesson: concurrency lets one program handle many jobs at once, and goroutines are how Go does it.
A Real-World Example
Think of the school canteen at lunch break.
- One cook, one job at a time: make tea, then wait for it, then make sandwiches, then fry samosas. The line grows long.
- One cook who switches: while the tea boils, make sandwiches. While the samosas fry, serve tea. Much faster.
- Three cooks: each makes one item, truly at the same moment.
Now the mapping:
- Switching between jobs = concurrency.
- Three cooks at the same moment = parallelism.
- Each job a cook works on = a goroutine.
Goroutines are much lighter than operating-system threads.
- A new goroutine starts with only a few kilobytes of memory.
- A program can run thousands, even millions, of them.
- Go's scheduler decides which goroutine runs on which CPU core.
What You Will Learn
In this lesson, you will learn:
- What concurrency is, and how it differs from parallelism.
- How to start goroutines with the
gokeyword. - How to wait for goroutines to finish.
- How to start goroutines from anonymous functions.
- What happens to goroutines when
mainends. - A common use: processing many items at once.
Concurrency Fundamentals
Concurrency vs Parallelism
Two ideas that are often mixed up
- Go programs are written concurrently.
- On a computer with many cores, Go also runs them in parallel.
- In this browser lab, goroutines take turns on one thread. They are concurrent, but not parallel.
Simple lesson: concurrency is many jobs in progress, parallelism is many jobs at the same instant.
Goroutines
The go Keyword
Put go in front of a function call, and that call runs in a new goroutine.
go sendReminder("Asha") // runs at the same time
fmt.Println("main goes on")
maindoes not wait forsendReminderto finish.mainitself runs in a goroutine too: the main goroutine.
Running Concurrent Tasks
Pick a mode and press Run.
Compare the three modes:
- No go keyword: the total time is the sum of all jobs.
- go + WaitGroup: the total time is the slowest job.
- go, but no Wait: the program ends before the work is done.
Creating Goroutines and Waiting for Them
sync.WaitGroup = a counter that lets main wait until a group of goroutines is done. You will learn it fully in Synchronization.
wg.Add(1)before starting each goroutine.defer wg.Done()inside the goroutine.wg.Wait()blocks until the counter is back to zero.
Three goroutines, then wait
package main
import (
"fmt"
"sync"
"time"
)
func markPapers(class string, minutes int, wg *sync.WaitGroup) {
defer wg.Done()
time.Sleep(time.Duration(minutes) * 10 * time.Millisecond)
fmt.Println(class, "done")
}
func main() {
var wg sync.WaitGroup
wg.Add(3)
go markPapers("8A", 3, &wg)
go markPapers("8B", 1, &wg)
go markPapers("8C", 2, &wg)
wg.Wait()
fmt.Println("all done")
}
8B done
8C done
8A done
all done
- The shortest job finishes first, not the first one started.
- One after another, the jobs would take 30 + 10 + 20 = 60 ms. Together, they take about 30 ms: the slowest one.
- Pass the WaitGroup as a pointer:
&wg. Copying it would break it.
Goroutine Execution
- The order goroutines run in is not fixed. Never depend on it.
- Above, the sleeps made the order clear. Without them, any order is possible.
- If you need a fixed order, collect results and sort them, or use channels. See Channels.
Simple lesson: go f() starts f at the same time, and a WaitGroup lets main wait for it.
Anonymous Goroutines
Goroutines with Anonymous Functions
Often the goroutine's work is short, so it is written as a function literal. See Functions.
Squares worked out in goroutines
go func(n int) { ... }(i)starts a goroutine running a function literal.- Passing
ias an argument gives each goroutine its own copy. - Since Go 1.22, each loop trip also gets its own
i, so usingidirectly is safe too. - Each goroutine writes a different slot. Writing the same variable needs a lock. See Synchronization.
Results in a fixed order
package main
import (
"fmt"
"strings"
"sync"
)
func main() {
names := []string{"asha", "ravi", "meena"}
badges := make([]string, len(names))
var wg sync.WaitGroup
for i, name := range names {
wg.Add(1)
go func() {
defer wg.Done()
badges[i] = strings.ToUpper(name) + " (8A)"
}()
}
wg.Wait()
fmt.Println(badges)
}
[ASHA (8A) RAVI (8A) MEENA (8A)]
Simple lesson: start short jobs with go func() { ... }(), and give each goroutine its own place to write.
Goroutine Behavior
Concurrent Execution
The order can change
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for _, name := range []string{"Asha", "Ravi", "Meena"} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("hello from", name)
}()
}
wg.Wait()
}
- On a real computer, the three lines may come out in any order.
- In this lab, goroutines take turns on one thread, so the order often looks the same. Do not rely on it.
Program Termination and Goroutines
When main returns, the whole program ends, even if other goroutines are still working.
main does not wait by itself
package main
import (
"fmt"
"time"
)
func main() {
go func() {
time.Sleep(50 * time.Millisecond)
fmt.Println("this line is usually never printed")
}()
fmt.Println("main is done")
}
mainprints and returns at once.- The goroutine is stopped before its
Println. - Always wait for goroutines you care about:
WaitGroupor channels.
Never use time.Sleep in main to "wait long enough" for goroutines.
It is either too long (slow) or too short (lost work). Use sync.WaitGroup or channels.
Easy way to remember
go f()= start f, do not wait.wg.Add,wg.Done,wg.Wait= wait for a group.- When
mainends, every goroutine ends.
Simple lesson: goroutines run in no fixed order, and they all stop when main returns.
Common Use Cases
Concurrent Processing
Concurrent processing = splitting a big job into pieces that run at the same time.
Good fits:
- checking many websites or files at once
- resizing many photos
- handling many users' requests on a server
- sending many notifications
Check every class's attendance at once
package main
import (
"fmt"
"sync"
)
func countPresent(marks []bool) int {
n := 0
for _, present := range marks {
if present {
n++
}
}
return n
}
func main() {
classes := map[string][]bool{
"8A": {true, true, false, true},
"8B": {true, false, false},
"8C": {true, true, true},
}
order := []string{"8A", "8B", "8C"}
present := make([]int, len(order))
var wg sync.WaitGroup
for i, name := range order {
wg.Add(1)
go func() {
defer wg.Done()
present[i] = countPresent(classes[name])
}()
}
wg.Wait()
for i, name := range order {
fmt.Println(name, "present:", present[i])
}
}
8A present: 3
8B present: 1
8C present: 3
- One goroutine per class.
- Each writes its own slot in
present, so there is no clash. - Printing happens after
wg.Wait(), in a fixed order.
Simple lesson: split independent work into goroutines, give each its own result slot, and wait for them all.
TL;DR
- Concurrency = many jobs in progress. Parallelism = many jobs at the same instant.
- Goroutine = a light worker. Start one with
go f(). - main does not wait. When it returns, all goroutines stop.
Wait with
sync.WaitGroup:Add(n),defer Done(),Wait().- Pass it as
&wg.
- Pass it as
- Anonymous goroutine =
go func() { ... }(). - Order is not fixed. Write results to separate slots, or use channels.
- Use = concurrent processing of independent jobs.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
No wg.Wait() | main waits for goroutines | It does not | Work is lost when main ends. |
time.Sleep to wait | A sleep is enough | Use a WaitGroup | Flaky, slow programs. |
Passing wg by value | It is fine | Pass &wg | Wait never returns, or Done panics. |
wg.Add(1) inside the goroutine | It counts in time | Call Add before go | Wait may return too early. |
| Expecting output in start order | Goroutines run in order | Order is not fixed | Confusing, changing output. |
| Many goroutines writing one variable | It is safe | It is a data race | Wrong totals. See Synchronization. |
Check Yourself
Exercises
What to Remember
- Concurrency is many jobs in progress. Parallelism is many jobs at the same instant.
go f()starts a goroutine. It does not wait.- Use
sync.WaitGroupto wait:Add,Done,Wait. - Goroutines run in no fixed order. Give each its own result slot.
- When
mainreturns, every goroutine stops.
Next: Channels. You will learn how goroutines send values to each other safely.