Why Switch from PHP to Go? Mastering Concurrency with WaitGroup and ErrGroup
This article explains why backend developers are moving from PHP to Go, outlines the concurrency challenges of high‑traffic live streaming services, and demonstrates two practical Go patterns—sync.WaitGroup and errgroup.Group—with code examples, while also warning about common closure pitfalls in loops.
1. Reasons to Choose Go
As a backend developer, I work most with PHP and Go. Although PHP feels comfortable and fast for simple tasks, new projects after 2021 are built with Go because:
PHP cannot meet our high‑concurrency needs – our live‑streaming service requires handling many simultaneous requests, which the traditional php‑fpm model cannot efficiently support.
Go is popular in the industry – major companies such as Tencent, Baidu, Didi and others have migrated from PHP to Go.
Go’s simplicity – the language is concise; after a couple of weeks of learning I could start writing projects.
2. How Go Solves Concurrency Problems
In PHP, fetching multiple pieces of data for a live‑room (version info, basic info, user info, equity info, statistics) must be done sequentially, causing the total latency to be the sum of all operations.
With Go, the request time is limited to the longest individual operation because the tasks run concurrently.
Two common Go patterns for this are:
Method 1: sync.WaitGroup
<code>// Request entry
func main() {
var (
VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
)
ctx := context.Background()
GoNoErr(ctx,
func() {
VersionDetail = 1 // version service info
time.Sleep(1 * time.Second)
fmt.Println("Execute first task")
},
func() {
LiveDetail = 2 // live basic info
time.Sleep(2 * time.Second)
fmt.Println("Execute second task")
},
func() {
UserDetail = 3 // user info
time.Sleep(3 * time.Second)
fmt.Println("Execute third task")
},
func() {
EquityDetail = 4 // live equity info
time.Sleep(4 * time.Second)
fmt.Println("Execute fourth task")
},
func() {
StatisticsDetail = 5 // live room statistics
time.Sleep(5 * time.Second)
fmt.Println("Execute fifth task")
},
)
fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}
// Concurrent method using WaitGroup
func GoNoErr(ctx context.Context, functions ...func()) {
var wg sync.WaitGroup
for _, f := range functions {
wg.Add(1)
// launch each function in a goroutine
go func(function func()) {
function()
wg.Done()
}(f)
}
// wait for all to finish
wg.Wait()
}</code>Method 2: errgroup.Group
<code>// Request entry
func main() {
var (
VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
err error
)
ctx := context.Background()
err = GoErr(ctx,
func() error {
VersionDetail = 1 // version service info
time.Sleep(1 * time.Second)
fmt.Println("Execute first task")
return nil
},
func() error {
LiveDetail = 2 // live basic info
time.Sleep(2 * time.Second)
fmt.Println("Execute second task")
return nil
},
func() error {
UserDetail = 3 // user info
time.Sleep(3 * time.Second)
fmt.Println("Execute third task")
return nil
},
func() error {
EquityDetail = 4 // live equity info
time.Sleep(4 * time.Second)
fmt.Println("Execute fourth task")
return nil
},
func() error {
StatisticsDetail = 5 // live room statistics
time.Sleep(5 * time.Second)
fmt.Println("Execute fifth task")
return nil
},
)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}
// Concurrent method using errgroup
func GoErr(ctx context.Context, functions ...func() error) error {
var eg errgroup.Group
for i := range functions {
f := functions[i] // capture loop variable
eg.Go(func() (err error) {
err = f()
if err != nil {
// log error if needed
}
return err
})
}
// wait for all to finish
return eg.Wait()
}</code>Both approaches launch a goroutine for each sub‑task and wait for all to finish. When using closures inside a loop, be careful to capture the loop variable correctly; otherwise all goroutines may reference the same variable, leading to unexpected results. Creating a new variable inside the loop (e.g.,
fs := f) avoids this issue.
These concurrency techniques are fundamental and widely applicable in Go projects.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
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.