【Go语言学习系列32】并发编程(五):并发模式

📚 原创系列: “Go语言学习系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言学习系列导航

本文是【Go语言学习系列】的第32篇,当前位于第三阶段(进阶篇)

🚀 第三阶段:进阶篇
  1. 并发编程(一):goroutine基础
  2. 并发编程(二):channel基础
  3. 并发编程(三):select语句
  4. 并发编程(四):sync包
  5. 并发编程(五):并发模式 👈 当前位置
  6. 并发编程(六):原子操作与内存模型
  7. 数据库编程(一):SQL接口
  8. 数据库编程(二):ORM技术
  9. Web开发(一):路由与中间件
  10. Web开发(二):模板与静态资源
  11. Web开发(三):API开发
  12. Web开发(四):认证与授权
  13. Web开发(五):WebSocket
  14. 微服务(一):基础概念
  15. 微服务(二):gRPC入门
  16. 日志与监控
  17. 第三阶段项目实战:微服务聊天应用

📚 查看完整Go语言学习系列导航

📖 文章导读

在前面四篇文章中,我们深入学习了Go语言的并发原语:goroutine、channel、select语句以及sync包。今天,我们将探讨如何将这些组件组合起来,形成强大且可复用的并发模式。这些模式可以帮助我们更有效地解决各种并发编程问题。

一、什么是并发模式

并发模式是在并发环境中解决特定问题的常见结构和方法。就像设计模式帮助我们组织代码一样,并发模式帮助我们组织并发逻辑,使其更易于理解、测试和维护。

好的并发模式应该具备以下特点:

  • 清晰的责任边界
  • 良好的错误处理
  • 可控的资源使用
  • 优雅的终止机制

掌握并发模式有以下好处:

  1. 提高代码质量和可维护性
  2. 减少并发错误(如死锁、竞态条件)
  3. 提高性能和资源利用率
  4. 简化复杂并发逻辑的实现
  5. 使代码更加模块化和可重用

二、生产者-消费者模式

生产者-消费者是最基本也是最常用的并发模式之一。它将"生产数据"和"消费数据"的过程解耦,通过channel在两者之间传递数据。

2.1 基本实现

package main

import (
    "fmt"
    "sync"
    "time"
)

func producer(jobs chan<- int) {
    defer close(jobs)
    for i := 1; i <= 5; i++ {
        fmt.Printf("生产任务: %d\n", i)
        jobs <- i
        time.Sleep(time.Millisecond * 500)
    }
}

func consumer(id int, jobs <-chan int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("消费者 %d 处理任务: %d\n", id, job)
        time.Sleep(time.Second) // 模拟处理时间
    }
}

func main() {
    jobs := make(chan int, 3)
    var wg sync.WaitGroup

    // 启动一个生产者
    go producer(jobs)

    // 启动两个消费者
    for i := 1; i <= 2; i++ {
        wg.Add(1)
        go consumer(i, jobs, &wg)
    }

    wg.Wait()
    fmt.Println("所有工作完成")
}

这个例子展示了生产者-消费者模式的基本用法:

  • 生产者生成任务并发送到channel
  • 多个消费者从channel接收任务并处理
  • 生产者完成后关闭channel
  • 主goroutine等待所有消费者完成

2.2 带错误处理的生产者-消费者

现实应用中,我们通常需要处理错误:

package main

import (
    "errors"
    "fmt"
    "sync"
    "time"
)

type Job struct {
    ID  int
    Err error
}

type Result struct {
    JobID     int
    Value     string
    Err       error
}

func producer(jobs chan<- Job) {
    defer close(jobs)
    for i := 1; i <= 5; i++ {
        var err error
        if i == 3 {
            err = errors.New("模拟生产错误")
        }
        jobs <- Job{ID: i, Err: err}
        time.Sleep(time.Millisecond * 500)
    }
}

func consumer(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        if job.Err != nil {
            results <- Result{JobID: job.ID, Err: job.Err}
            continue
        }

        // 模拟处理
        time.Sleep(time.Second)
        
        var err error
        if job.ID == 4 {
            err = errors.New("模拟处理错误")
        }
        
        result := Result{
            JobID: job.ID,
            Value: fmt.Sprintf("处理结果 %d", job.ID),
            Err:   err,
        }
        
        results <- result
    }
}

func main() {
    jobs := make(chan Job, 5)
    results := make(chan Result, 5)
    
    var wg sync.WaitGroup
    
    // 启动生产者
    go producer(jobs)
    
    // 启动消费者
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go consumer(jobs, results, &wg)
    }
    
    // 收集结果
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // 处理结果
    for result := range results {
        if result.Err != nil {
            fmt.Printf("任务 %d 出错: %v\n", result.JobID, result.Err)
            continue
        }
        fmt.Printf("任务 %d 完成: %s\n", result.JobID, result.Value)
    }
}

这个增强版实现了以下改进:

  • 使用结构体传递任务和结果,包含错误信息
  • 在消费者中处理生产者可能产生的错误
  • 消费者也可能产生错误,并传递给结果收集者
  • 主goroutine处理并整理所有结果

2.3 实际应用场景

生产者-消费者模式在很多场景下非常有用:

  1. Web服务器处理请求:传入的HTTP请求作为生产者,工作池作为消费者
  2. 数据处理管道:从数据源读取数据(生产者),经过多阶段处理(消费者)
  3. 任务队列:应用程序生成后台任务(生产者),工作者处理任务(消费者)
  4. 日志处理:应用程序生成日志事件(生产者),日志处理器写入存储(消费者)

三、工作池模式 (Worker Pool)

工作池模式是生产者-消费者模式的扩展,它维护一组工作者(goroutine)来处理一系列任务。与简单的生产者-消费者模式相比,工作池模式更强调工作者的管理和任务的分发。

3.1 基本工作池

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("工作者 %d 开始处理任务 %d\n", id, job)
        time.Sleep(time.Second) // 模拟工作耗时
        fmt.Printf("工作者 %d 完成任务 %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    
    var wg sync.WaitGroup
    
    // 启动工作者
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    
    // 发送任务
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    // 等待所有工作者完成
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // 收集结果
    for result := range results {
        fmt.Printf("结果: %d\n", result)
    }
}

基本工作池的特点:

  • 预先创建固定数量的工作者
  • 所有工作者共享同一个任务队列
  • 任务完成后结果发送到结果队列
  • 主程序等待所有工作者完成并收集结果

3.2 可限流的工作池

在实际应用中,我们可能需要限制并发数量或请求速率:

package main

import (
    "fmt"
    "sync"
    "time"
)

type RateLimiter struct {
    interval time.Duration
    ticker   *time.Ticker
    stopCh   chan struct{}
}

func NewRateLimiter(rps int) *RateLimiter {
    interval := time.Second / time.Duration(rps)
    return &RateLimiter{
        interval: interval,
        ticker:   time.NewTicker(interval),
        stopCh:   make(chan struct{}),
    }
}

func (rl *RateLimiter) Allow() bool {
    select {
    case <-rl.ticker.C:
        return true
    case <-rl.stopCh:
        return false
    default:
        return false
    }
}

func (rl *RateLimiter) Stop() {
    rl.ticker.Stop()
    close(rl.stopCh)
}

func processRequest(id int, limiter *RateLimiter, wg *sync.WaitGroup) {
    defer wg.Done()
    
    start := time.Now()
    
    // 等待限流器允许
    for !limiter.Allow() {
        time.Sleep(time.Millisecond * 10)
    }
    
    // 模拟处理请求
    time.Sleep(time.Millisecond * 50)
    
    fmt.Printf("请求 %d 处理完成,等待时间: %v\n", id, time.Since(start))
}

func main() {
    // 每秒5个请求
    limiter := NewRateLimiter(5)
    defer limiter.Stop()
    
    var wg sync.WaitGroup
    
    // 模拟20个并发请求
    for i := 1; i <= 20; i++ {
        wg.Add(1)
        go processRequest(i, limiter, &wg)
    }
    
    wg.Wait()
    fmt.Println("所有请求处理完成")
}

限流工作池的关键功能:

  • 通过令牌桶或其他限流算法控制请求速率
  • 过载时请求可以等待或被拒绝
  • 防止系统资源过度使用
  • 保护下游服务免受突发流量影响

3.3 工作池的实际应用

工作池模式在以下场景特别有用:

  1. API服务器:控制对数据库或第三方服务的请求速率
  2. 批量数据处理:处理大量数据时限制内存使用
  3. 爬虫系统:控制爬取速率,避免对目标站点造成压力
  4. 任务调度系统:管理和分配计算密集型任务
  5. 微服务通信:限制服务间的调用流量

Go并发模式概览

四、管道模式 (Pipeline)

管道模式将数据处理分成多个阶段,每个阶段通过channel连接起来。这种模式特别适合处理数据流,每个阶段都可以并发执行。

4.1 基本管道

package main

import (
    "fmt"
)

// 生成器:生成整数
func generator(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            out <- n
        }
    }()
    return out
}

// 平方:计算输入值的平方
func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            out <- n * n
        }
    }()
    return out
}

// 过滤:只保留奇数
func onlyOdd(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in {
            if n%2 != 0 {
                out <- n
            }
        }
    }()
    return out
}

func main() {
    // 构建管道
    nums := generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    squares := square(nums)
    odds := onlyOdd(squares)
    
    // 消费结果
    for odd := range odds {
        fmt.Println(odd)
    }
}

基本管道模式的特点:

  • 每个阶段是一个函数,接收上一阶段的输出作为输入
  • 每个阶段在单独的goroutine中运行
  • 阶段间通过channel传递数据
  • 当输入channel关闭且处理完所有数据时,该阶段关闭其输出channel

4.2 带错误处理的管道

实际应用中,我们需要处理每个管道阶段可能出现的错误:

package main

import (
    "errors"
    "fmt"
)

type Result struct {
    Value int
    Err   error
}

func generator(nums ...int) <-chan Result {
    out := make(chan Result)
    go func() {
        defer close(out)
        for _, n := range nums {
            if n < 0 {
                out <- Result{Err: errors.New("负数不被允许")}
                return
            }
            out <- Result{Value: n}
        }
    }()
    return out
}

func square(in <-chan Result) <-chan Result {
    out := make(chan Result)
    go func() {
        defer close(out)
        for res := range in {
            if res.Err != nil {
                out <- res // 传递错误
                continue
            }
            out <- Result{Value: res.Value * res.Value}
        }
    }()
    return out
}

func main() {
    // 构建管道
    values := generator(1, 2, 3, -4, 5)
    squares := square(values)
    
    // 消费结果
    for res := range squares {
        if res.Err != nil {
            fmt.Printf("错误: %v\n", res.Err)
            continue
        }
        fmt.Println(res.Value)
    }
}

带错误处理的管道特点:

  • 每个结果包含值和可能的错误
  • 一旦发生错误,错误会沿管道传播
  • 管道后期阶段可以选择如何处理错误(传递、处理或停止)
  • 主函数可以收集处理所有错误

4.3 管道模式的优势与实际应用

管道模式的主要优势:

  • 模块化:每个处理阶段都是独立的,易于测试和维护
  • 并发处理:各阶段可以并行执行,提高吞吐量
  • 解耦:数据生产和消费解耦,提高代码可读性
  • 灵活组合:可以根据需要动态组合不同处理阶段

实际应用场景:

  1. ETL处理:数据提取、转换和加载过程
  2. 图像处理:图像加载、缩放、滤镜应用等多步骤处理
  3. 数据分析:数据读取、清洗、聚合、分析的流水线
  4. 实时数据流处理:日志分析、事件处理等

五、扇入扇出模式 (Fan-in/Fan-out)

扇出是将任务分配给多个worker并行处理,扇入是将多个结果汇总到一个channel。这种模式适合处理可以并行的任务,然后需要汇总结果的场景。

5.1 基本实现

package main

import (
    "fmt"
    "sync"
)

// 扇出:将一个输入拆分为多个并行处理
func fanOut(in <-chan int, numWorkers int) []<-chan int {
    outputs := make([]<-chan int, numWorkers)
    for i := 0; i < numWorkers; i++ {
        outputs[i] = processWorker(in, i)
    }
    return outputs
}

// 扇入:将多个输入合并到一个输出
func fanIn(inputs []<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)
    
    // 为每个输入启动一个goroutine
    for _, input := range inputs {
        wg.Add(1)
        go func(ch <-chan int) {
            defer wg.Done()
            for val := range ch {
                out <- val
            }
        }(input)
    }
    
    // 当所有输入处理完毕后关闭输出
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

// 工作者:处理输入并产生输出
func processWorker(in <-chan int, workerID int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for val := range in {
            // 模拟处理:计算平方
            result := val * val
            fmt.Printf("工作者 %d 处理 %d -> %d\n", workerID, val, result)
            out <- result
        }
    }()
    return out
}

func main() {
    // 创建输入
    input := make(chan int)
    go func() {
        defer close(input)
        for i := 1; i <= 10; i++ {
            input <- i
        }
    }()
    
    // 扇出:分配给3个工作者
    workers := fanOut(input, 3)
    
    // 扇入:汇总所有结果
    results := fanIn(workers)
    
    // 处理最终结果
    var sum int
    for res := range results {
        sum += res
    }
    
    fmt.Printf("所有结果的总和: %d\n", sum)
}

扇入扇出模式的关键组件:

  • 扇出:将一个任务分配给多个worker并行处理
  • worker:每个worker独立处理分配给它的任务
  • 扇入:将多个worker的结果合并到一个channel

5.2 应用场景

扇入扇出模式在以下场景特别有用:

  1. 并行计算:将大型计算任务拆分为多个子任务并行处理
  2. 数据聚合:从多个源收集数据并合并结果
  3. 并行API请求:同时发起多个API请求,然后合并响应
  4. 分布式系统:分散工作负载,然后聚合结果

六、超时与取消模式

在并发程序中,我们常常需要处理超时和取消操作。Go的context包和select语句提供了优雅的方式来实现这些功能。

6.1 超时与上下文取消

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// 一个可能耗时的操作
func slowOperation(ctx context.Context) (string, error) {
    select {
    case <-time.After(2 * time.Second):
        return "操作完成", nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

// 使用超时控制
func withTimeout() {
    // 创建一个带1秒超时的上下文
    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel() // 确保取消函数被调用
    
    result, err := slowOperation(ctx)
    if err != nil {
        fmt.Printf("超时示例出错: %v\n", err)
        return
    }
    fmt.Printf("超时示例结果: %s\n", result)
}

// 使用手动取消
func withCancellation() {
    ctx, cancel := context.WithCancel(context.Background())
    
    // 启动一个goroutine在1秒后取消
    time.AfterFunc(1*time.Second, cancel)
    
    result, err := slowOperation(ctx)
    if err != nil {
        fmt.Printf("取消示例出错: %v\n", err)
        return
    }
    fmt.Printf("取消示例结果: %s\n", result)
}

// 使用上下文控制多个goroutine
func workerWithContext(ctx context.Context, id int, wg *sync.WaitGroup) {
    defer wg.Done()
    
    fmt.Printf("工作者 %d 启动\n", id)
    
    select {
    case <-time.After(3 * time.Second):
        fmt.Printf("工作者 %d 完成工作\n", id)
    case <-ctx.Done():
        fmt.Printf("工作者 %d 接收到取消信号: %v\n", id, ctx.Err())
    }
}

func multipleWorkers() {
    // 创建一个2秒后自动取消的上下文
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    var wg sync.WaitGroup
    
    // 启动多个工作者
    for i := 1; i <= 3; i++ {
        wg.Add(1)
        go workerWithContext(ctx, i, &wg)
    }
    
    // 等待工作者完成或超时
    wg.Wait()
    fmt.Println("所有工作者已退出")
}

func main() {
    fmt.Println("-- 超时示例 --")
    withTimeout()
    
    fmt.Println("\n-- 取消示例 --")
    withCancellation()
    
    fmt.Println("\n-- 多工作者示例 --")
    multipleWorkers()
}

Context的核心功能:

  • 超时控制context.WithTimeoutcontext.WithDeadline
  • 手动取消context.WithCancel
  • 传递值context.WithValue
  • 优雅取消:通过Done()channel通知取消事件

6.2 超时重试模式

在网络请求等不稳定操作中,重试是常见需求:

package main

import (
    "context"
    "errors"
    "fmt"
    "math/rand"
    "time"
)

// 模拟可能失败的操作
func doOperation(ctx context.Context) (string, error) {
    // 模拟随机失败
    if rand.Float32() < 0.7 {
        return "", errors.New("操作失败")
    }
    
    select {
    case <-time.After(500 * time.Millisecond):
        return "操作成功", nil
    case <-ctx.Done():
        return "", ctx.Err()
    }
}

// 带重试的操作
func doOperationWithRetry(maxRetries int, timeout time.Duration) (string, error) {
    var lastErr error
    
    for retry := 0; retry < maxRetries; retry++ {
        ctx, cancel := context.WithTimeout(context.Background(), timeout)
        
        result, err := doOperation(ctx)
        cancel() // 及时释放资源
        
        if err == nil {
            return result, nil
        }
        
        lastErr = err
        fmt.Printf("尝试 %d 失败: %v,重试中...\n", retry+1, err)
        time.Sleep(time.Millisecond * 200 * time.Duration(retry+1)) // 退避策略
    }
    
    return "", fmt.Errorf("在 %d 次尝试后失败: %w", maxRetries, lastErr)
}

func main() {
    rand.Seed(time.Now().UnixNano())
    
    result, err := doOperationWithRetry(5, 800*time.Millisecond)
    if err != nil {
        fmt.Printf("最终错误: %v\n", err)
        return
    }
    
    fmt.Printf("最终结果: %s\n", result)
}

重试模式的关键点:

  • 指数退避:连续失败后增加重试间隔
  • 最大重试次数:防止无限重试
  • 超时控制:单次操作不应无限等待
  • 错误处理:保留最后的错误信息供调试

6.3 超时与取消的实际应用

这些模式在以下场景尤为重要:

  1. HTTP客户端:防止请求无限等待
  2. 数据库操作:控制查询执行时间
  3. 分布式系统:优雅处理服务不可用
  4. 长时间运行的操作:提供用户取消能力
  5. 资源清理:确保临时资源被正确释放

七、并发控制模式

有时我们需要精确控制并发数量或并发操作。这在资源受限的环境中尤为重要。

7.1 信号量模式

使用带缓冲的channel实现信号量:

package main

import (
    "fmt"
    "sync"
    "time"
)

// 信号量实现
type Semaphore struct {
    tokens chan struct{}
}

func NewSemaphore(limit int) *Semaphore {
    return &Semaphore{
        tokens: make(chan struct{}, limit),
    }
}

func (s *Semaphore) Acquire() {
    s.tokens <- struct{}{}
}

func (s *Semaphore) Release() {
    <-s.tokens
}

// 并发执行但限制最大并发数
func processWithLimit(items []int, concurrency int) {
    sem := NewSemaphore(concurrency)
    var wg sync.WaitGroup
    
    for i, item := range items {
        wg.Add(1)
        
        // 获取令牌
        sem.Acquire()
        
        go func(id, val int) {
            defer wg.Done()
            defer sem.Release() // 释放令牌
            
            // 模拟处理
            fmt.Printf("处理项目 %d: %d 开始\n", id, val)
            time.Sleep(time.Second)
            fmt.Printf("处理项目 %d: %d 完成\n", id, val)
        }(i, item)
    }
    
    wg.Wait()
}

func main() {
    items := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    
    fmt.Println("限制最大3个并发:")
    processWithLimit(items, 3)
}

信号量模式的特点:

  • 使用带缓冲的channel控制并发数量
  • 每次操作前获取令牌,操作后释放令牌
  • 可应用于任何需要限制并发的场景
  • 简单高效,符合Go的设计理念

7.2 并发与串行结合

有些应用需要在某些阶段并发,而在另一些阶段串行执行:

package main

import (
    "fmt"
    "sync"
    "time"
)

// 串行阶段
func prepare(data []int) []int {
    fmt.Println("准备阶段(串行)开始")
    result := make([]int, len(data))
    for i, val := range data {
        result[i] = val * 2
        time.Sleep(100 * time.Millisecond) // 模拟处理
    }
    fmt.Println("准备阶段(串行)完成")
    return result
}

// 并行阶段
func process(data []int) []int {
    fmt.Println("处理阶段(并行)开始")
    result := make([]int, len(data))
    var wg sync.WaitGroup
    
    for i, val := range data {
        wg.Add(1)
        go func(idx, value int) {
            defer wg.Done()
            // 模拟复杂处理
            time.Sleep(500 * time.Millisecond)
            result[idx] = value * value
            fmt.Printf("处理项目 %d 完成\n", idx)
        }(i, val)
    }
    
    wg.Wait()
    fmt.Println("处理阶段(并行)完成")
    return result
}

// 串行阶段
func finalize(data []int) int {
    fmt.Println("最终阶段(串行)开始")
    sum := 0
    for _, val := range data {
        sum += val
        time.Sleep(100 * time.Millisecond) // 模拟处理
    }
    fmt.Println("最终阶段(串行)完成")
    return sum
}

func main() {
    input := []int{1, 2, 3, 4, 5}
    
    // 串行-并行-串行处理流程
    prepared := prepare(input)     // 串行
    processed := process(prepared) // 并行
    result := finalize(processed)  // 串行
    
    fmt.Printf("最终结果: %d\n", result)
}

这种模式适用于:

  • 数据处理前需要预处理的场景
  • 并行处理后需要聚合结果的场景
  • 某些阶段有依赖关系,无法并行的场景

八、实战示例:异步任务系统

下面是一个更复杂的示例,结合了多种并发模式,实现一个完整的异步任务系统:

package main

import (
    "context"
    "fmt"
    "math/rand"
    "sync"
    "time"
)

// 任务状态
type TaskStatus string

const (
    StatusPending   TaskStatus = "pending"
    StatusRunning   TaskStatus = "running"
    StatusCompleted TaskStatus = "completed"
    StatusFailed    TaskStatus = "failed"
    StatusCancelled TaskStatus = "cancelled"
)

// 任务定义
type Task struct {
    ID          string
    Description string
    Status      TaskStatus
    Result      string
    Error       error
    CreatedAt   time.Time
    CompletedAt time.Time
}

// 任务系统
type TaskSystem struct {
    tasks      map[string]*Task
    queue      chan string
    semaphore  *Semaphore
    maxRetries int
    mu         sync.Mutex
    wg         sync.WaitGroup
    ctx        context.Context
    cancel     context.CancelFunc
}

// 创建新的任务系统
func NewTaskSystem(concurrency, queueSize, maxRetries int) *TaskSystem {
    ctx, cancel := context.WithCancel(context.Background())
    return &TaskSystem{
        tasks:      make(map[string]*Task),
        queue:      make(chan string, queueSize),
        semaphore:  NewSemaphore(concurrency),
        maxRetries: maxRetries,
        ctx:        ctx,
        cancel:     cancel,
    }
}

// 添加任务
func (ts *TaskSystem) AddTask(description string) string {
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    id := fmt.Sprintf("task-%d", len(ts.tasks)+1)
    
    task := &Task{
        ID:          id,
        Description: description,
        Status:      StatusPending,
        CreatedAt:   time.Now(),
    }
    
    ts.tasks[id] = task
    
    // 将任务ID放入队列
    select {
    case ts.queue <- id:
        fmt.Printf("任务 %s 已加入队列\n", id)
    default:
        task.Status = StatusFailed
        task.Error = fmt.Errorf("队列已满,无法添加任务")
        fmt.Printf("任务 %s 无法加入队列: %v\n", id, task.Error)
    }
    
    return id
}

// 获取任务状态
func (ts *TaskSystem) GetTask(id string) (Task, bool) {
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    task, exists := ts.tasks[id]
    if !exists {
        return Task{}, false
    }
    
    return *task, true
}

// 执行单个任务
func (ts *TaskSystem) executeTask(taskID string) {
    ts.mu.Lock()
    task, exists := ts.tasks[taskID]
    if !exists {
        ts.mu.Unlock()
        return
    }
    
    task.Status = StatusRunning
    ts.mu.Unlock()
    
    fmt.Printf("开始执行任务 %s: %s\n", task.ID, task.Description)
    
    // 模拟工作
    success := false
    var err error
    
    for retry := 0; retry < ts.maxRetries; retry++ {
        // 检查是否被取消
        select {
        case <-ts.ctx.Done():
            ts.mu.Lock()
            task.Status = StatusCancelled
            task.Error = fmt.Errorf("任务系统关闭")
            task.CompletedAt = time.Now()
            ts.mu.Unlock()
            return
        default:
            // 继续执行
        }
        
        // 模拟随机执行时间和成功/失败
        time.Sleep(time.Millisecond * time.Duration(500+rand.Intn(1000)))
        
        if rand.Float32() < 0.6 { // 60%成功率
            success = true
            break
        }
        
        err = fmt.Errorf("随机失败 (尝试 %d/%d)", retry+1, ts.maxRetries)
        fmt.Printf("任务 %s 尝试 %d 失败: %v\n", task.ID, retry+1, err)
        
        // 退避策略
        time.Sleep(time.Millisecond * 200 * time.Duration(retry+1))
    }
    
    ts.mu.Lock()
    defer ts.mu.Unlock()
    
    task.CompletedAt = time.Now()
    
    if success {
        task.Status = StatusCompleted
        task.Result = fmt.Sprintf("任务 %s 完成,耗时: %v", task.ID, task.CompletedAt.Sub(task.CreatedAt))
        fmt.Printf("任务 %s 成功: %s\n", task.ID, task.Result)
    } else {
        task.Status = StatusFailed
        task.Error = err
        fmt.Printf("任务 %s 最终失败: %v\n", task.ID, err)
    }
}

// 启动工作者
func (ts *TaskSystem) startWorker(id int) {
    defer ts.wg.Done()
    
    fmt.Printf("工作者 %d 已启动\n", id)
    
    for {
        select {
        case <-ts.ctx.Done():
            fmt.Printf("工作者 %d 关闭\n", id)
            return
            
        case taskID, ok := <-ts.queue:
            if !ok {
                // 队列已关闭
                fmt.Printf("工作者 %d 发现队列已关闭\n", id)
                return
            }
            
            // 获取信号量
            ts.semaphore.Acquire()
            
            // 执行任务
            fmt.Printf("工作者 %d 接收任务 %s\n", id, taskID)
            ts.executeTask(taskID)
            
            // 释放信号量
            ts.semaphore.Release()
        }
    }
}

// 启动任务系统
func (ts *TaskSystem) Start(numWorkers int) {
    for i := 1; i <= numWorkers; i++ {
        ts.wg.Add(1)
        go ts.startWorker(i)
    }
    
    fmt.Printf("任务系统已启动,使用 %d 个工作者\n", numWorkers)
}

// 停止任务系统
func (ts *TaskSystem) Stop() {
    fmt.Println("正在停止任务系统...")
    ts.cancel()
    close(ts.queue)
    ts.wg.Wait()
    fmt.Println("任务系统已停止")
}

// 信号量实现
type Semaphore struct {
    tokens chan struct{}
}

func NewSemaphore(limit int) *Semaphore {
    return &Semaphore{
        tokens: make(chan struct{}, limit),
    }
}

func (s *Semaphore) Acquire() {
    s.tokens <- struct{}{}
}

func (s *Semaphore) Release() {
    <-s.tokens
}

func main() {
    rand.Seed(time.Now().UnixNano())
    
    // 创建任务系统:最大3个并发,队列容量10,最大重试3次
    system := NewTaskSystem(3, 10, 3)
    
    // 启动5个工作者
    system.Start(5)
    
    // 添加一些任务
    for i := 1; i <= 15; i++ {
        id := system.AddTask(fmt.Sprintf("任务 %d", i))
        fmt.Printf("添加了任务: %s\n", id)
        time.Sleep(time.Millisecond * 200)
    }
    
    // 等待一段时间处理任务
    time.Sleep(10 * time.Second)
    
    // 停止任务系统
    system.Stop()
    
    // 打印最终状态
    fmt.Println("\n最终任务状态:")
    for i := 1; i <= 15; i++ {
        id := fmt.Sprintf("task-%d", i)
        task, exists := system.GetTask(id)
        if exists {
            fmt.Printf("- %s: %s, 状态: %s\n", task.ID, task.Description, task.Status)
            if task.Error != nil {
                fmt.Printf("  错误: %v\n", task.Error)
            }
            if task.Result != "" {
                fmt.Printf("  结果: %s\n", task.Result)
            }
        }
    }
}

这个异步任务系统结合了以下并发模式:

  • 生产者-消费者模式:添加任务和处理任务
  • 工作池模式:多个工作者处理任务队列
  • 信号量模式:限制并发执行任务数量
  • 超时和重试模式:任务执行失败后重试
  • 上下文取消模式:优雅关闭整个系统

九、总结

本文介绍了Go语言中常用的并发模式:

  1. 生产者-消费者模式:用于任务分发和处理
  2. 工作池模式:管理和控制一组工作者
  3. 管道模式:将数据处理分成多个阶段
  4. 扇入扇出模式:并行处理数据并汇总结果
  5. 超时与取消模式:处理超时和取消操作
  6. 并发控制模式:限制并发数量
  7. 异步任务系统:结合多种模式的综合应用

掌握这些模式,再结合goroutine、channel、select语句和sync包,可以帮助我们设计和实现高效、健壮的并发系统。

最佳实践

最后,总结一些使用并发模式的最佳实践:

  1. 明确责任:每个goroutine应有明确的责任和生命周期
  2. 错误处理:设计错误传播机制,不要丢失错误信息
  3. 资源控制:限制并发数量,防止资源耗尽
  4. 优雅关闭:提供取消机制,确保资源正确释放
  5. 避免泄漏:确保所有goroutine最终会退出
  6. 简单胜于复杂:使用最简单的能满足需求的并发模式
  7. 测试:编写并发测试,使用竞态检测器(race detector)

在下一篇文章中,我们将开始探索Go语言的原子操作与内存模型,进一步深入理解Go的并发机制。


👨‍💻 关于作者与Gopher部落

"Gopher部落"专注于Go语言技术分享,提供从入门到精通的完整学习路线。

🌟 为什么关注我们?

  1. 系统化学习路径:本系列44篇文章循序渐进,带你完整掌握Go开发
  2. 实战驱动教学:理论结合实践,每篇文章都有可操作的代码示例
  3. 持续更新内容:定期分享最新Go生态技术动态与大厂实践经验
  4. 专业技术社区:加入我们的技术交流群,与众多Go开发者共同成长

📱 关注方式

  1. 微信公众号:搜索 “Gopher部落”“GopherTribe”
  2. 优快云专栏:点击页面右上角"关注"按钮

💡 读者福利

关注公众号回复 “Go并发” 即可获取:

  • 完整Go并发编程示例代码
  • Go并发模式实战指南PDF
  • 高性能Go应用优化技巧

期待与您在Go语言的学习旅程中共同成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值