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

7种常见网络并发模型介绍 对于网络服务器后端开发,为满足不同并发场景的需要,一般来说,不外乎几种常见的并发模型,除了一些教学场景常用的单线程、多进程(线程)的服务器实现外,生产用的服务器,一般都会考虑使用IO多路复用模型。而常见的IO多路复用场景 ,可以设计得很简单,也可以设计得比较复杂,一般根据业务需要而定。本文总结了一些比较常见的服务器并发模型,基本涵盖了 大部分业务场景。在实际业务开发的技术选型时,可根据场景,选取一款稳定、可靠的网络模型,还是十分关键的。 阅读详情

📚 原创系列: “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.WithTimeout和context.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语言的学习旅程中共同成长!

Go语言学习笔记——通过示例代码和详尽的解释来帮助初学者理解Golang的核心概念 Go 是一种静态类型的、编译型、并发执行的编程语言,由 Google 创建于 2009 年。Go 拥有编译时类型检查、自动内存管理、安全高效运行等特性,能够在一些领域解决一些难题,如实时系统开发、云计算服务构建、分布式系统开发等。Go 是开源的,免费提供给所有人使用。前言里简单介绍了 Golang 以及为什么要学习它。接下来从环境安装到语法基础再到简单的编程实例,带领大家全面掌握 Golang。最后,对 Golang 提供的支持、参考资源做一些回顾。 阅读详情

相关推荐

【ROS学习笔记】8.话题消息(Message)的定义与使用

ROS学习笔记8——话题消息(Message)的定义与使用

takedachia的博客 9547

常见的并发模式

1、生产者消费者模型 并发编程中最常见的例子就是生产者消费者模式,该模式主要通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。简单地说,就是生产者生产一些数据,然后放到成果队列中,同时消费者从成果队列中来取这些数据。这样就让生产消费变成了异步的两个过程。当成果队列中没有数据时,消费者就进入饥饿的等待中;而当成果队列中数据已满时,生产者则面临因产品挤压导致CPU被剥夺的下岗问题。 Go语言实现生产者消费者并发简单案例: // 生产者: 生成 factor 整数倍的序列 func Produc

Haikuotiankong11111的博客 920

深入解析国家企业征信系统瑞数6vmp算法逆向实战

本文深入解析了国家企业征信系统所采用的双层防护机制,重点聚焦于瑞数6代vmp算法的逆向实战。文章详细拆解了从加速乐521状态码的绕过,到瑞数412挑战的核心算法还原过程,提供了通过动态Hook、日志分析进行算法逆向,并用Python实现自动化请求链的完整技术方案,旨在为安全研究与技术学习提供参考。

yellow的专栏 610

Go 语言精进之路——Go 中常见并发模式总结

本文主要总结了 Go 中使用 goroutine 和 channel 以及 select 实现的几种常用并发模式,包括优雅退出,取消超时,管道模式等。 附:封面图片在文章末尾哟~ 😊

奔跑的蜗牛的博客 1095

养龙虾经验:OpenClaw用户必看的3个“省token”救命配置

本文分享了3个优化OpenClaw使用成本的配置方案:1)启用上下文压缩功能,可选择全局或单个代理设置,支持safeguard/adaptive两种压缩模式;2)修改SOUL.md提示词,要求模型回答简洁直接;3)限制对话历史长度。方案一效果最佳,可减少长对话的token消耗,但可能影响连贯性。三种方案可单独或组合使用,需根据实际场景调整参数。文章最后邀请读者分享使用痛点,将针对高频问题撰写解决方案。

wpplvhq的专栏 1416

双目深度相机--10.双目视觉的主要步骤讲解分析

双目视觉技术通过模拟人眼原理,利用两个相机从不同视角采集图像,实现三维坐标计算。其核心流程包括:1)系统标定,消除相机畸变并建立坐标映射关系;2)图像同步采集与预处理;3)立体匹配,寻找左右图像对应点;4)基于视差图的深度计算;5)三维重建生成点云或网格模型;6)结果应用如目标测距、三维建模等。关键技术难点在于精确标定和准确匹配,这两步直接影响最终深度计算的精度。该技术广泛应用于自动驾驶、工业检测、VR等领域,实现了从二维图像到三维空间的精准转换。

AIoT深度探索者 | 嵌入式系统架构师 深耕物联网(IoT)与人工智能(AI)融合技术,聚焦​​边缘计算、图像开发、低功耗通信协议​​,致力于智能硬件,关注我获取最新技术文章信息与海量资源~ 625

并发模式

并发编程 并发编程的目的是让程序“同时”执行多个任务。如果程序是计算密集型的,并发编程并没有优势,反而由于任务的切换是运行效率降低。但如果程序是I/O密集型的,如经常访问读写文件,访问数据库等,情况就不一样了。由于I/O操作的速度远没有CPU的计算速度快,所以让程序阻塞于I/O操作将浪费大量的CPU时间,如果程序有多个执行线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或有操作系统来调度),并将执行权转移到其他线程。如此,CPU就可以用来做其他的任务,而不是等待I/O操作完成,因此CPU的利.

站在树顶端设计的男人 767

AMBA-CHI协议详解(三)

Write Transactions、Atomic transactions、 Stash transactions、 Dataless transactions的等各个事务的事务流。

Crazzy_M的博客 3180

常见Go 语言并发模式学习

Go 语言以其强大的并发特性而闻名。本文将详细介绍 Go 语言中常见的并发模式,帮助你更好地理解和使用 Go 的并发特性。1. 基本的 Goroutine 和 Channel 模式生产者-消费者模式是并发编程中最基础和最常用的模式之一。这种模式通过将数据生产和消费解耦,可以更好地控制数据流和处理速度。1.1 生产者-消费者模式工作流程:生产者1 Channel 生产者2 消费者1 消费者2特点:•...

weixin_46825193的博客 900

ArcGIS Pro符号化教程

本文详细介绍了ArcGIS Pro的软件设置和符号化操作步骤。主要包括:1)设置默认工程目录和关闭地图自动加载;2)创建新工程的基本流程;3)点要素的单一符号化方法,包括样式修改和图层调整;4)图表符号显示过滤器的使用,涉及字段添加、图表类型选择和背景设置。文章还提供了操作截图和数据下载链接,适合GIS初学者学习ArcGIS Pro的基础符号化操作。

WwLK123的博客 548

华为od 面试八股文_C语言_02_含答案

华为od 面试八股文_C语言_02_含答案

MISAYAONE的博客 2230

【Go语言学习系列33】并发编程(六):原子操作与内存模型

深入探讨Go语言的原子操作和内存模型,包括sync/atomic包的应用、happens-before关系、内存重排以及并发陷阱的避免方法,帮助你构建高效、正确的并发程序

GopherTribe的博客 1208

[深入研究4G/5G/6G专题-31]: 5G NR开机流程5.2 - UE Attach流程(Registeration Request/Accept/Complete)

目录前言:前置条件:第1章 5G NR UE attach流程(从UE log的视角看)1.0 NAS消息解析器1.1 消息交互流程1.2 UE Registeration Request消息(AMF->基站->UE)1.3 AMF Registeration Accept消息(AMF->基站->UE)1.4 UE Register complete消息(UE->基站->AMF)第2章 UE Register...............

文火冰糖(王文兵)的博客 7万+

【Go语言学习系列00】Go语言学习系列导航

本文是Go语言学习系列的导航文档,提供了从入门到精通的学习路径,包含各阶段文章链接和内容概述,帮助读者系统化学习Go语言。

GopherTribe的博客 1574

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

【Go语言学习系列47】性能优化(三):并发调优

深入探讨Go语言并发编程的性能调优技术,包括goroutine管理、channel使用优化、sync包高级应用、并发模式选择,以及避免常见的并发陷阱

GopherTribe的博客 1201

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

【Go语言学习系列12】第一阶段项目实战:命令行工具

通过实战开发一个功能完整的文件处理命令行工具,掌握Go工程化实践,包括参数解析、文件操作、并发控制与单元测试等核心技能

GopherTribe的博客 1201

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

【语言学习】Golang前进之路(序)

Go modules 是 Go 语言的依赖解决方案Go 语言长久以来的依赖管理问题。“淘汰”现有的 GOPATH 的使用模式。统一社区中的其它的依赖管理工具(提供迁移功能)。

qq_49353571的博客 7216

【Go语言学习系列23】Go中的面向对象编程

深入解析Go语言的面向对象编程模式,掌握组合优于继承、接口实现与方法集等核心概念

GopherTribe的博客 1024

Go语言学习(二)

真正。

wj617906617的博客 1052

【Go语言学习系列45】性能优化(一):编写高性能Go代码

深入探讨Go语言性能优化技术,包括内存分配优化、减少垃圾回收压力、使用sync.Pool缓存对象以及合理选择数据结构,帮助开发者编写高效Go程序

GopherTribe的博客 755

Go最全搭建开发环境以及STM32固件库移植_stm32g4能用f4固件库吗(4),看懂这份Golang面经大厂真不是问题

不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!也是通过直接操作寄存器来实现,区别在于,可以利用已经做好的第三方库来借助不同的 接口技术来实现对寄存器操作,无需去深度的掌握寄存器的内容。比如:ST公司在研发出STM32系列芯片的同时,开奖该系列所有的单片机寄存器及其操作封装成了一整套的接口技术 =》 固件库。ST意法半导体公司根据ARM公司的Cortex-M系列的芯片研发出来的一款高性能的单片机芯片。

2401_84905102的博客 451

Go语言学习10-指针类型

Go语言指针类型

Huazie 858

2024年最全搭建开发环境以及STM32固件库移植_stm32g4能用f4固件库吗(1),阿里P7Golang社招面试的经历

指基于8051内核衍生出的各种单片机芯片AT89C51/AT89C52、STC89C51/STC89C52、cc2530(ZigBee)ESP8266(WIFI)……8位单片机:指基于ARM公司Cortex-M内核衍生出的各种单片机芯片ST:意法半导体公司STM32F4xx、STM32F1xx 、STM32F7xx ……32位单片机商用的有STM32Gxx位:指芯片同时处理数据量的大小,同时还限定了 寄存器的大小、地址总线的大小8位: 芯片同时可以处理8bits的数据。

2401_84904964的博客 581

Go语言-类型

前言:即将成为一名Golang语言的开发人员,把自己学习的过程记录下来与大家一起分享,希望能给您带来帮助,如果在编写的过程中有什么错误或者我说的不对的地方还请大家不吝赐教,给我留言我好及时做修改防止误导其他阅读人员,在此就先行感谢了! 声明:本问中的内容均出自《Go语言编程》、《Go语言学习笔记》、《Go并发编程实战》、以及视频“跟着无闻学Go”视频连接如下:[http://edu.51cto.c

Paddy90的专栏 1108

Sublime Text 3配置Go语言开发环境

Sublime Text 3搭建Go语言开发环境

Huazie 2万+

Go从入门到精通(16)-读写数据

读写数据。

weixin_43444221的博客 889
上一篇: 【Go语言学习系列30】并发编程(三):select语句
下一篇: 【Go语言学习系列31】并发编程(四):sync包
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

Gopher部落

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

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

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

打赏作者

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

抵扣说明:

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

余额充值