Goroutines
Goroutines are lightweight threads managed by the Go runtime. Use the go keyword to start a new goroutine:
go process(a, b, c)
Arguments are evaluated in the current goroutine, while execution occurs in the new goroutine. Goroutines share the same address space, requiring synchronization for shared memory acess.
package main
import (
"fmt"
"time"
)
func display(msg string) {
for i := 0; i < 3; i++ {
time.Sleep(150 * time.Millisecond)
fmt.Println(msg)
}
}
func main() {
go display("apple")
display("orange")
}
// Output order varies due to concurrent execution
Channels
Channels are typed conduits for synchronized communication between goroutines:
ch <- val // Send value to channel
val := <-ch // Receive value from channel
channel := make(chan int) // Create channel
Send and receive operations block until both sides are ready, anabling synchronization without explicit locks.
package main
import "fmt"
func calculate(values []int, resChan chan int) {
total := 0
for _, n := range values {
total += n
}
resChan <- total
}
func main() {
numbers := []int{5, -2, 10, -7, 3, 1}
resultChan := make(chan int)
go calculate(numbers[:3], resultChan)
go calculate(numbers[3:], resultChan)
part1, part2 := <-resultChan, <-resultChan
fmt.Println(part1, part2, part1+part2)
}
// Output: 13 -3 10
Buffered Channels
Buffered channels accept limited values without blocking:
bufChan := make(chan string, 4) // Channel with 4-value buffer
package main
import "fmt"
func main() {
bufChan := make(chan int, 3)
bufChan <- 10
bufChan <- 20
bufChan <- 30
fmt.Println(<-bufChan)
fmt.Println(<-bufChan)
fmt.Println(<-bufChan)
}
Channel Closing and Range
Senders can close channels to indicate no more values:
close(ch)
val, open := <-ch // Check if channel is open
for item := range ch { ... } // Receive until closed
package main
import "fmt"
func sequenceGen(count int, seqChan chan int) {
a, b := 0, 1
for i := 0; i < count; i++ {
seqChan <- a
a, b = b, a+b
}
close(seqChan)
}
func main() {
seqChan := make(chan int, 8)
go sequenceGen(8, seqChan)
for num := range seqChan {
fmt.Println(num)
}
}
Select Statement
select enables multiplexing channel operations:
select {
case msg := <-ch1:
// Handle message
case ch2 <- data:
// Send data
case <-quitChan:
// Exit
}
package main
import "fmt"
func sequenceProducer(seqChan, stopChan chan int) {
x, y := 0, 1
for {
select {
case seqChan <- x:
x, y = y, x+y
case <-stopChan:
fmt.Println("Terminating")
return
}
}
}
func main() {
seqChan := make(chan int)
stopChan := make(chan int)
go func() {
for i := 0; i < 7; i++ {
fmt.Println(<-seqChan)
}
stopChan <- 0
}()
sequenceProducer(seqChan, stopChan)
}
Default Selection
Default cases execute when no other channel operations are ready:
select {
case data := <-inputChan:
// Process data
default:
// Non-blocking fallback
}
package main
import (
"fmt"
"time"
)
func main() {
tick := time.Tick(200 * time.Millisecond)
timeout := time.After(1000 * time.Millisecond)
for {
select {
case <-tick:
fmt.Println("Tick")
case <-timeout:
fmt.Println("Timeout!")
return
default:
fmt.Println("Waiting...")
time.Sleep(100 * time.Millisecond)
}
}
}
Mutex Synchronization
sync.Mutex provides mutual exclusion for shared resources:
package main
import (
"fmt"
"sync"
"time"
)
type ProtectedCounter struct {
counts map[string]int
lock sync.Mutex
}
func (pc *ProtectedCounter) Increment(key string) {
pc.lock.Lock()
defer pc.lock.Unlock()
pc.counts[key]++
}
func (pc *ProtectedCounter) Value(key string) int {
pc.lock.Lock()
defer pc.lock.Unlock()
return pc.counts[key]
}
func main() {
counter := ProtectedCounter{counts: make(map[string]int)}
for i := 0; i < 500; i++ {
go counter.Increment("mykey")
}
time.Sleep(time.Second)
fmt.Println(counter.Value("mykey"))
}
// Output: 500