Go并发模型基础
Go语言的并发基于CSP(Communicating Sequential Processes)模型,核心原则是”不要通过共享内存来通信,而要通过通信来共享内存”。goroutine是Go运行时管理的轻量级线程,创建成本约2KB栈空间,单机可轻松运行数十万个。channel是goroutine间通信的管道,编译器保证类型安全,运行时保证并发安全。
// goroutine基本用法
go func() {
fmt.Println("并发执行")
}()
// channel通信
ch := make(chan string, 10) // 缓冲channel,容量10
ch <- "message" // 发送
msg := <-ch // 接收
goroutine虽轻量,但不受控创建会导致资源耗尽。生产环境必须使用goroutine池化技术控制并发度。
goroutine池化实现
Worker Pool模式通过固定数量的worker goroutine处理任务队列,限制并发度、复用goroutine、提供背压机制:
package pool
import (
"context"
"sync"
)
type Task func() error
type Pool struct {
workers int
taskCh chan Task
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
errCh chan error
}
func NewPool(workers int, queueSize int) *Pool {
ctx, cancel := context.WithCancel(context.Background())
p := &Pool{
workers: workers,
taskCh: make(chan Task, queueSize),
ctx: ctx,
cancel: cancel,
errCh: make(chan error, workers),
}
p.start()
return p
}
func (p *Pool) start() {
for i := 0; i < p.workers; i++ {
p.wg.Add(1)
go func(workerID int) {
defer p.wg.Done()
for {
select {
case task, ok := <-p.taskCh:
if !ok {
return
}
if err := task(); err != nil {
select {
case p.errCh <- err:
default: // 错误通道满则丢弃
}
}
case <-p.ctx.Done():
return
}
}
}(i)
}
}
func (p *Pool) Submit(task Task) bool {
select {
case p.taskCh <- task:
return true
case <-p.ctx.Done():
return false
}
}
func (p *Pool) Shutdown() {
close(p.taskCh) // 关闭任务通道,worker消费完剩余任务后退出
p.wg.Wait()
close(p.errCh)
}
func (p *Pool) Cancel() {
p.cancel() // 通知所有worker立即退出
p.wg.Wait()
}
func (p *Pool) Errors() <-chan error {
return p.errCh
}
使用示例——并发处理1000个URL请求,限制最多50个并发:
func main() {
pool := NewPool(50, 100) // 50个worker,任务队列容量100
var successCount int64
urls := generateURLs(1000)
for _, url := range urls {
u := url
pool.Submit(func() error {
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
atomic.AddInt64(&successCount, 1)
}
return nil
})
}
pool.Shutdown()
// 收集错误
for err := range pool.Errors() {
log.Printf("task error: %v", err)
}
fmt.Printf("成功: %d/1000\n", successCount)
}
Channel通信模式
Channel不仅是数据管道,通过组合可以实现多种并发控制模式:
Fan-Out/Fan-In模式:一个生产者将任务分发到多个worker(Fan-Out),多个worker的结果汇聚到一个channel(Fan-In):
func fanOutFanIn(ctx context.Context, input <-chan int, workers int) <-chan int {
// Fan-Out:启动多个worker
workerChs := make([]chan int, workers)
for i := 0; i < workers; i++ {
ch := make(chan int)
workerChs[i] = ch
go func(ch chan int) {
defer close(ch)
for val := range input {
result := process(val)
select {
case ch <- result:
case <-ctx.Done():
return
}
}
}(ch)
}
// Fan-In:合并所有worker输出
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range workerChs {
wg.Add(1)
go func(ch chan int) {
defer wg.Done()
for val := range ch {
out <- val
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Pipeline模式:多个处理阶段串联,每个阶段是一个goroutine,通过channel连接:
func generate(ctx context.Context, nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
select {
case out <- n:
case <-ctx.Done():
return
}
}
}()
return out
}
func square(ctx context.Context, in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
select {
case out <- n * n:
case <-ctx.Done():
return
}
}
}()
return out
}
func filter(ctx context.Context, in <-chan int, predicate func(int) bool) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in {
if predicate(n) {
select {
case out <- n:
case <-ctx.Done():
return
}
}
}
}()
return out
}
// 串联使用
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
pipeline := filter(ctx, square(ctx, generate(ctx, 1,2,3,4,5)), func(n int) bool { return n > 5 })
for result := range pipeline {
fmt.Println(result) // 9, 16, 25
}
限流器设计
限流防止突发流量压垮下游服务。Go的time.Ticker实现令牌桶限流,golang.org/x/time/rate提供更完整的实现:
import "golang.org/x/time/rate"
// 令牌桶限流器:每秒产生100个令牌,桶容量200(允许短时突发)
limiter := rate.NewLimiter(100, 200)
func handler(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
return
}
// 处理请求
}
// 更精细的限流:按IP限流
type IPRateLimiter struct {
ips sync.Map
rate rate.Limit
burst int
}
func NewIPRateLimiter(r rate.Limit, burst int) *IPRateLimiter {
return &IPRateLimiter{rate: r, burst: burst}
}
func (l *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
if val, ok := l.ips.Load(ip); ok {
return val.(*rate.Limiter)
}
limiter := rate.NewLimiter(l.rate, l.burst)
actual, _ := l.ips.LoadOrStore(ip, limiter)
return actual.(*rate.Limiter)
}
// HTTP中间件
func (l *IPRateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := strings.Split(r.RemoteAddr, ":")[0]
if !l.GetLimiter(ip).Allow() {
http.Error(w, "too many requests", 429)
return
}
next.ServeHTTP(w, r)
})
}
滑动窗口限流实现:
type SlidingWindow struct {
mu sync.Mutex
requests []time.Time
limit int
window time.Duration
}
func NewSlidingWindow(limit int, window time.Duration) *SlidingWindow {
return &SlidingWindow{limit: limit, window: window}
}
func (s *SlidingWindow) Allow() bool {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
cutoff := now.Add(-s.window)
// 移除窗口外的请求记录
i := 0
for i < len(s.requests) && s.requests[i].Before(cutoff) {
i++
}
s.requests = s.requests[i:]
if len(s.requests) >= s.limit {
return false
}
s.requests = append(s.requests, now)
return true
}
并发安全与竞态检测
Go的竞态检测器(Race Detector)是排查并发bug最有效的工具。编译时加 -race 标志即可启用:
go build -race -o app .
go test -race ./...
竞态检测器在运行时监控所有内存访问,发现多个goroutine同时读写同一变量且至少一个为写操作时报告。性能开销约5-10倍,仅用于测试环境。
常见并发安全陷阱与修复:
// 错误:多个goroutine并发写map
var counter = make(map[string]int)
// fatal error: concurrent map writes
// 修复方案1:sync.Mutex
var counterMu sync.Mutex
func inc(key string) {
counterMu.Lock()
counter[key]++
counterMu.Unlock()
}
// 修复方案2:sync.Map(读多写少场景性能更好)
var counter sync.Map
func inc(key string) {
for {
val, ok := counter.Load(key)
if !ok {
counter.Store(key, 1)
return
}
if counter.CompareAndSwap(key, val, val.(int)+1) {
return
}
}
}
// 修复方案3:channel聚合(最符合Go风格)
type update struct {
key string
delta int
}
func counterAggregator(updates <-chan update) map[string]int {
counts := make(map[string]int)
for u := range updates {
counts[u.key] += u.delta
}
return counts
}
Go高并发编程的核心是在goroutine池化、channel通信模式、限流设计之间找到平衡。池化控制资源消耗上限,channel保证数据流转安全,限流保护下游服务。三者组合使用,可以构建出高性能、可预测、易调试的并发系统。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/go-gao-bing-fa-bian-cheng-shi-zhan-goroutine-chi-hua/