Golang go keyword
last modified May 7, 2025
This tutorial explains how to use the go keyword in Go. We'll
cover goroutine basics with practical examples of concurrent execution.
The go keyword starts a new goroutine, which is a lightweight thread managed by the Go runtime. Goroutines enable concurrent execution of functions.
In Go, go is used before function calls to execute them
concurrently. Goroutines are more efficient than OS threads and enable
highly concurrent programs.
Basic goroutine example
The simplest use of go creates a goroutine from a function
call. This example demonstrates concurrent execution.
Note: Using time.Sleep to wait for goroutines is only suitable for simple demonstrations. In production code, use sync.WaitGroup or channels for proper synchronization.
package main
import (
"fmt"
"time"
)
func sayHello() {
fmt.Println("Hello from goroutine")
}
func main() {
go sayHello()
time.Sleep(100 * time.Millisecond)
fmt.Println("Hello from main")
}
The sayHello function runs concurrently with the main function.
We use time.Sleep to wait for the goroutine to complete.
Multiple goroutines
We can create multiple goroutines to execute functions concurrently. This example shows three goroutines running simultaneously.
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
for i := 1; i <= 3; i++ {
go worker(i)
}
time.Sleep(2 * time.Second)
fmt.Println("All workers completed")
}
Each worker goroutine runs independently. The sleep in main ensures all goroutines complete before the program exits.
Anonymous function goroutines
We can create goroutines from anonymous functions. This is useful for quick concurrent operations.
package main
import (
"fmt"
"time"
)
func main() {
go func() {
fmt.Println("Running in goroutine")
}()
time.Sleep(100 * time.Millisecond)
fmt.Println("Running in main")
}
The anonymous function executes concurrently with the main function. This pattern is common for short-lived goroutines.
Goroutines with parameters
We can pass parameters to goroutines just like regular function calls. This example demonstrates parameter passing.
package main
import (
"fmt"
"time"
)
func printMessage(msg string) {
fmt.Println(msg)
}
func main() {
go printMessage("First message")
go printMessage("Second message")
time.Sleep(100 * time.Millisecond)
fmt.Println("Main message")
}
Both goroutines receive their parameters normally. The order of output may vary between runs due to scheduling.
Goroutines and Shared Memory
Goroutines can access shared variables, but this requires synchronization to
prevent race conditions. Without synchronization, multiple goroutines modifying
a shared variable simultaneously can lead to unpredictable behavior. One way to
ensure thread safety is by using a sync.Mutex.
A sync.Mutex (mutual exclusion) ensures that only one goroutine can
modify the shared variable at a time. The mutex must be locked before accessing
the shared resource and unlocked afterward. This prevents concurrent goroutines
from interfering with each other and ensures consistent results.
package main
import (
"fmt"
"sync"
)
var counter int
var mutex sync.Mutex // Declare a mutex
func increment() {
mutex.Lock() // Acquire the lock before modifying the shared variable
counter++
fmt.Println("Incremented to", counter)
mutex.Unlock() // Release the lock after modification
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait() // Wait for all goroutines to complete
fmt.Println("Final counter:", counter)
}
Using sync.Mutex prevents race conditions by ensuring only one
goroutine can modify counter at a time. The key features include:
- Using
mutex.Lockandmutex.UnlockEnsures mutual exclusion when modifyingcounter. - Adding a
sync.WaitGroupEnsures the program waits for all goroutines to finish execution before printing the final value. - Using
defer wg.DoneEnsures each goroutine signals its completion.
Without proper synchronization, multiple goroutines may attempt to modify
counter at the same time, leading to unexpected results such as
incorrect final values or inconsistent increments. This is known as a
race condition, where the outcome depends on the unpredictable
timing of concurrent executions.
Using a sync.Mutex ensures safe updates to shared resources, making
the code reliable and consistent across multiple executions.
Waiting for goroutines with WaitGroup
The sync.WaitGroup provides a better way to wait for goroutines
than sleeping. This example demonstrates proper synchronization.
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// Simulate work
time.Sleep(200 * time.Millisecond)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers completed")
}
WaitGroup tracks goroutine completion. Add increments
the counter, Done decrements it, and Wait blocks until
zero.
Goroutines with channels
Channels provide safe communication between goroutines. This example shows goroutines sending and receiving data.
package main
import "fmt"
func produceMessages(ch chan<- string) {
for i := 0; i < 5; i++ {
ch <- fmt.Sprintf("Message %d", i)
}
close(ch)
}
func main() {
ch := make(chan string)
go produceMessages(ch)
for msg := range ch {
fmt.Println("Received:", msg)
}
fmt.Println("All messages received")
}
The producer goroutine sends messages through the channel. The main goroutine receives them until the channel is closed.
Source
This tutorial covered the go keyword in Go with practical
examples of goroutine creation and management.
Author
List all Golang tutorials.