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.Lock
andmutex.Unlock
Ensures mutual exclusion when modifyingcounter
. - Adding a
sync.WaitGroup
Ensures the program waits for all goroutines to finish execution before printing the final value. - Using
defer wg.Done
Ensures 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.