Why Switch from PHP to Go? Practical Concurrency Patterns for High‑Load Services
The article explains why a backend team moved from PHP to Go for high‑concurrency live‑streaming services and demonstrates two Go concurrency patterns—sync.WaitGroup and errgroup—with code examples and common pitfalls.
Reasons for Choosing Go
As a backend developer who previously used PHP extensively, the author found PHP comfortable but unsuitable for high‑concurrency live‑streaming workloads. The main reasons for migrating to Go were:
PHP cannot handle the required concurrency – the standard php‑fpm model creates a new process per request, which is too heavy for real‑time streaming.
Industry trend – major companies such as Tencent, Baidu, Didi, and others have been adopting Go.
Go’s simplicity – the language is easy to pick up, allowing the author to start writing production code after only a couple of weeks.
Concurrency Problem Solved by Go
In a typical PHP implementation, fetching various pieces of data for a live‑room (version info, basic room info, user info, equity info, statistics) is done sequentially, causing the total response time to equal the sum of all sub‑tasks. Go’s goroutine model enables these tasks to run in parallel, reducing the overall latency to the duration of the longest sub‑task.
Method 1: Using sync.WaitGroup
// request entry point
func main() {
var (
VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
)
ctx := context.Background()
GoNoErr(ctx,
func() { VersionDetail = 1; time.Sleep(1 * time.Second); fmt.Println("task 1") },
func() { LiveDetail = 2; time.Sleep(2 * time.Second); fmt.Println("task 2") },
func() { UserDetail = 3; time.Sleep(3 * time.Second); fmt.Println("task 3") },
func() { EquityDetail = 4; time.Sleep(4 * time.Second); fmt.Println("task 4") },
func() { StatisticsDetail = 5; time.Sleep(5 * time.Second); fmt.Println("task 5") },
)
fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}
func GoNoErr(ctx context.Context, functions ...func()) {
var wg sync.WaitGroup
for _, f := range functions {
wg.Add(1)
go func(fn func()) { fn(); wg.Done() }(f)
}
wg.Wait()
}This approach launches a goroutine for each function and waits for all to finish.
Method 2: Using errgroup Library
// request entry point
func main() {
var (
VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail int
err error
)
ctx := context.Background()
err = GoErr(ctx,
func() error { VersionDetail = 1; time.Sleep(1 * time.Second); fmt.Println("task 1"); return nil },
func() error { LiveDetail = 2; time.Sleep(2 * time.Second); fmt.Println("task 2"); return nil },
func() error { UserDetail = 3; time.Sleep(3 * time.Second); fmt.Println("task 3"); return nil },
func() error { EquityDetail = 4; time.Sleep(4 * time.Second); fmt.Println("task 4"); return nil },
func() error { StatisticsDetail = 5; time.Sleep(5 * time.Second); fmt.Println("task 5"); return nil },
)
if err != nil { fmt.Println(err); return }
fmt.Println(VersionDetail, LiveDetail, UserDetail, EquityDetail, StatisticsDetail)
}
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() error { return f() })
}
return eg.Wait()
}The errgroup variant also propagates the first error encountered, making it suitable for tasks where failure handling matters.
Common Pitfall with Closures
When launching goroutines inside a loop, capturing the loop variable directly leads to all goroutines referencing the same final value. Two safe patterns are shown:
Assign the loop variable to a new local variable before launching the goroutine (as in Method 2).
Use the index‑based loop ( for i := range functions) and capture functions[i].
Incorrect code that captures the loop variable without copying results in unexpected behavior, illustrated by screenshots in the original article.
Conclusion
Both sync.WaitGroup and errgroup provide straightforward ways to split a parent task into multiple concurrent subtasks in Go, dramatically reducing latency for high‑throughput services such as live streaming. Understanding closure capture rules is essential to avoid subtle bugs when spawning goroutines inside loops.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
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.
