Golang学习历程【第十三篇 并发入门:goroutine + channel 基础】

1. 并发编程基础概念

并发编程是现代软件开发中不可或缺的技能。如果说传统的顺序执行像是一个人按部就班地完成任务,那么并发就像是同时指挥多个工人并行工作。Go语言天生支持并发,让并发编程变得简单而高效。

1.1 什么是并发

让我们通过生活中的例子来理解并发:

现实场景对比

  • 顺序执行:一个人做饭 → 洗碗 → 打扫卫生(总共需要3小时)
  • 并发执行:一个人做饭的同时,另一个人洗碗,第三人打扫卫生(总共只需1小时)

程序中的并发

  • 顺序执行:执行任务A → 执行任务B → 执行任务C
  • 并发执行:同时执行任务A、B、C

1.2 并发 vs 并行 vs 串行

go 复制代码
package main

import (
    "fmt"
    "time"
)

// 串行执行示例
func serialExecution() {
    fmt.Println("=== 串行执行 ===")
    start := time.Now()
    
    task1()
    task2()
    task3()
    
    elapsed := time.Since(start)
    fmt.Printf("串行执行总耗时:%v\n\n", elapsed)
}

// 并发执行示例
func concurrentExecution() {
    fmt.Println("=== 并发执行 ===")
    start := time.Now()
    
    // 启动3个goroutine并发执行
    go task1()
    go task2()
    go task3()
    
    // 等待一段时间让goroutine执行完成
    time.Sleep(2 * time.Second)
    
    elapsed := time.Since(start)
    fmt.Printf("并发执行总耗时:%v\n\n", elapsed)
}

// 模拟耗时任务
func task1() {
    fmt.Println("任务1开始执行...")
    time.Sleep(1 * time.Second)
    fmt.Println("任务1执行完成")
}

func task2() {
    fmt.Println("任务2开始执行...")
    time.Sleep(1 * time.Second)
    fmt.Println("任务2执行完成")
}

func task3() {
    fmt.Println("任务3开始执行...")
    time.Sleep(1 * time.Second)
    fmt.Println("任务3执行完成")
}

func main() {
    serialExecution()
    concurrentExecution()
    
    // 解释并发的优势
    fmt.Println("=== 并发优势说明 ===")
    fmt.Println("1. 提高程序执行效率")
    fmt.Println("2. 更好地利用多核CPU")
    fmt.Println("3. 改善用户体验(响应更快)")
    fmt.Println("4. 处理I/O密集型任务更有效")
}

运行结果:

bash 复制代码
=== 串行执行 ===
任务1开始执行...
任务1执行完成
任务2开始执行...
任务2执行完成
任务3开始执行...
任务3执行完成
串行执行总耗时:3.004s

=== 并发执行 ===
任务1开始执行...
任务2开始执行...
任务3开始执行...
任务1执行完成
任务2执行完成
任务3执行完成
并发执行总耗时:1.001s

=== 并发优势说明 ===
1. 提高程序执行效率
2. 更好地利用多核CPU
3. 改善用户体验(响应更快)
4. 处理I/O密集型任务更有效

1.3 Go并发的核心组件

Go并发编程主要依靠两个核心概念:

  1. Goroutine:轻量级线程,由Go运行时管理
  2. Channel:用于goroutine之间通信和同步的管道
go 复制代码
package main

import (
    "fmt"
    "runtime"
    "time"
)

func demonstrateGoroutineBasics() {
    fmt.Println("=== Goroutine基础演示 ===")
    
    // 查看当前goroutine数量
    fmt.Printf("初始goroutine数量:%d\n", runtime.NumGoroutine())
    
    // 启动多个goroutine
    for i := 1; i <= 5; i++ {
        go func(id int) {
            fmt.Printf("Goroutine %d 开始执行\n", id)
            time.Sleep(time.Duration(id) * 100 * time.Millisecond)
            fmt.Printf("Goroutine %d 执行完成\n", id)
        }(i)
    }
    
    fmt.Printf("启动后goroutine数量:%d\n", runtime.NumGoroutine())
    
    // 等待goroutine执行完成
    time.Sleep(1 * time.Second)
    fmt.Printf("最终goroutine数量:%d\n", runtime.NumGoroutine())
}

func demonstrateChannelBasics() {
    fmt.Println("\n=== Channel基础演示 ===")
    
    // 创建channel
    ch := make(chan string)
    
    // 启动生产者goroutine
    go func() {
        fmt.Println("生产者:准备发送数据...")
        ch <- "Hello from goroutine!"  // 发送数据到channel
        fmt.Println("生产者:数据发送完成")
    }()
    
    // 主goroutine作为消费者
    fmt.Println("主程序:等待接收数据...")
    message := <-ch  // 从channel接收数据
    fmt.Printf("主程序:接收到数据:%s\n", message)
}

func main() {
    demonstrateGoroutineBasics()
    demonstrateChannelBasics()
}

运行结果:

bash 复制代码
=== Goroutine基础演示 ===
初始goroutine数量:1
启动后goroutine数量:6
Goroutine 5 开始执行
Goroutine 1 开始执行
Goroutine 2 开始执行
Goroutine 3 开始执行
Goroutine 4 开始执行
Goroutine 1 执行完成
Goroutine 2 执行完成
Goroutine 3 执行完成
Goroutine 4 执行完成
Goroutine 5 执行完成
最终goroutine数量:1

=== Channel基础演示 ===
主程序:等待接收数据...
生产者:准备发送数据...
生产者:数据发送完成
主程序:接收到数据:Hello from goroutine!

2. Goroutine详解

2.1 Goroutine的创建和管理

go 复制代码
package main

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

// WaitGroup用于等待一组goroutine完成
func demonstrateWaitGroup() {
    fmt.Println("=== 使用WaitGroup等待goroutine完成 ===")
    
    var wg sync.WaitGroup
    tasks := []string{"任务A", "任务B", "任务C", "任务D"}
    
    // 为每个任务添加计数
    wg.Add(len(tasks))
    
    for i, task := range tasks {
        go func(id int, name string) {
            defer wg.Done()  // 任务完成时减少计数
            fmt.Printf("开始执行%s (goroutine %d)\n", name, id)
            time.Sleep(time.Duration(id+1) * 500 * time.Millisecond)
            fmt.Printf("%s执行完成 (goroutine %d)\n", name, id)
        }(i, task)
    }
    
    fmt.Println("等待所有任务完成...")
    wg.Wait()  // 等待所有goroutine完成
    fmt.Println("所有任务已完成!")
}

// 带参数的goroutine
func demonstrateGoroutineParameters() {
    fmt.Println("\n=== 带参数的Goroutine ===")
    
    // 方法1:使用匿名函数捕获参数
    for i := 1; i <= 3; i++ {
        value := i  // 重要:创建局部变量副本
        go func() {
            fmt.Printf("方法1 - 值:%d\n", value)
        }()
    }
    
    // 方法2:通过函数参数传递
    for i := 1; i <= 3; i++ {
        go func(val int) {
            fmt.Printf("方法2 - 值:%d\n", val)
        }(i)
    }
    
    time.Sleep(100 * time.Millisecond)
}

// Goroutine生命周期管理
func demonstrateLifecycle() {
    fmt.Println("\n=== Goroutine生命周期 ===")
    
    // 创建一个长时间运行的goroutine
    stop := make(chan bool)
    
    go func() {
        for {
            select {
            case <-stop:
                fmt.Println("Goroutine收到停止信号,正在退出...")
                return
            default:
                fmt.Println("Goroutine正在工作中...")
                time.Sleep(300 * time.Millisecond)
            }
        }
    }()
    
    // 让goroutine运行一段时间
    time.Sleep(1 * time.Second)
    
    // 发送停止信号
    fmt.Println("发送停止信号...")
    stop <- true
    
    // 等待确认停止
    time.Sleep(500 * time.Millisecond)
    fmt.Println("主程序结束")
}

func main() {
    demonstrateWaitGroup()
    demonstrateGoroutineParameters()
    demonstrateLifecycle()
    
    // 查看系统信息
    fmt.Println("\n=== 系统信息 ===")
    fmt.Printf("CPU核心数:%d\n", runtime.NumCPU())
    fmt.Printf("当前goroutine数量:%d\n", runtime.NumGoroutine())
}

运行结果:

bash 复制代码
=== 使用WaitGroup等待goroutine完成 ===
等待所有任务完成...
开始执行任务A (goroutine 0)
开始执行任务B (goroutine 1)
开始执行任务C (goroutine 2)
开始执行任务D (goroutine 3)
任务A执行完成 (goroutine 0)
任务B执行完成 (goroutine 1)
任务C执行完成 (goroutine 2)
任务D执行完成 (goroutine 3)
所有任务已完成!

=== 带参数的Goroutine ===
方法1 - 值:1
方法1 - 值:2
方法1 - 值:3
方法2 - 值:1
方法2 - 值:2
方法2 - 值:3

=== Goroutine生命周期 ===
Goroutine正在工作中...
Goroutine正在工作中...
Goroutine正在工作中...
发送停止信号...
Goroutine收到停止信号,正在退出...
主程序结束

=== 系统信息 ===
CPU核心数:8
当前goroutine数量:1

2.2 Goroutine调度机制

go 复制代码
package main

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

func demonstrateScheduler() {
    fmt.Println("=== Goroutine调度演示 ===")
    
    var wg sync.WaitGroup
    wg.Add(2)
    
    // CPU密集型任务
    go func() {
        defer wg.Done()
        fmt.Println("CPU密集型任务开始")
        count := 0
        for i := 0; i < 1000000; i++ {
            count += i
        }
        fmt.Printf("CPU密集型任务完成,count=%d\n", count)
    }()
    
    // I/O密集型任务
    go func() {
        defer wg.Done()
        fmt.Println("I/O密集型任务开始")
        time.Sleep(100 * time.Millisecond)  // 模拟I/O等待
        fmt.Println("I/O密集型任务完成")
    }()
    
    wg.Wait()
    fmt.Println("所有任务完成")
}

func demonstratePreemption() {
    fmt.Println("\n=== 抢占式调度演示 ===")
    
    // 创建多个短任务来观察调度
    var wg sync.WaitGroup
    taskCount := 10
    
    wg.Add(taskCount)
    for i := 0; i < taskCount; i++ {
        go func(id int) {
            defer wg.Done()
            fmt.Printf("任务%d开始执行\n", id)
            
            // 模拟一些工作
            for j := 0; j < 100000; j++ {
                _ = j * j  // CPU计算
            }
            
            fmt.Printf("任务%d执行完成\n", id)
        }(i)
    }
    
    wg.Wait()
    fmt.Println("所有短任务完成")
}

func main() {
    demonstrateScheduler()
    demonstratePreemption()
    
    // 显示调度器信息
    fmt.Println("\n=== 调度器信息 ===")
    fmt.Printf("GOMAXPROCS:%d\n", runtime.GOMAXPROCS(0))
    fmt.Printf("NumCPU:%d\n", runtime.NumCPU())
    
    // 设置GOMAXPROCS
    old := runtime.GOMAXPROCS(2)
    fmt.Printf("旧的GOMAXPROCS:%d,新的GOMAXPROCS:%d\n", old, runtime.GOMAXPROCS(0))
}

运行结果:

bash 复制代码
=== Goroutine调度演示 ===
CPU密集型任务开始
I/O密集型任务开始
I/O密集型任务完成
CPU密集型任务完成,count=499999500000
所有任务完成

=== 抢占式调度演示 ===
任务9开始执行
任务0开始执行
任务1开始执行
任务2开始执行
任务3开始执行
任务4开始执行
任务5开始执行
任务6开始执行
任务7开始执行
任务8开始执行
任务9执行完成
任务0执行完成
任务1执行完成
任务2执行完成
任务3执行完成
任务4执行完成
任务5执行完成
任务6执行完成
任务7执行完成
任务8执行完成
所有短任务完成

=== 调度器信息 ===
GOMAXPROCS:8
NumCPU:8
旧的GOMAXPROCS:8,新的GOMAXPROCS:2

3. Channel详解

3.1 Channel基础操作

go 复制代码
package main

import (
    "fmt"
    "time"
)

// 基本channel操作
func basicChannelOperations() {
    fmt.Println("=== 基本Channel操作 ===")
    
    // 1. 创建channel
    ch := make(chan int)
    
    // 2. 发送数据
    go func() {
        fmt.Println("发送数据:100")
        ch <- 100
        fmt.Println("发送数据:200")
        ch <- 200
    }()
    
    // 3. 接收数据
    value1 := <-ch
    fmt.Printf("接收到:%d\n", value1)
    
    value2 := <-ch
    fmt.Printf("接收到:%d\n", value2)
}

// 带缓冲的channel
func bufferedChannel() {
    fmt.Println("\n=== 带缓冲的Channel ===")
    
    // 创建带缓冲的channel(缓冲区大小为2)
    ch := make(chan string, 2)
    
    // 可以连续发送而不阻塞(缓冲区未满时)
    ch <- "消息1"
    fmt.Println("已发送消息1")
    
    ch <- "消息2"
    fmt.Println("已发送消息2")
    
    // 缓冲区满了,这会阻塞
    fmt.Println("准备发送消息3...")
    go func() {
        ch <- "消息3"
        fmt.Println("消息3发送完成")
    }()
    
    // 接收数据
    msg1 := <-ch
    fmt.Printf("接收到:%s\n", msg1)
    
    msg2 := <-ch
    fmt.Printf("接收到:%s\n", msg2)
    
    msg3 := <-ch
    fmt.Printf("接收到:%s\n", msg3)
}

// 单向channel
func directionalChannel() {
    fmt.Println("\n=== 单向Channel ===")
    
    ch := make(chan int)
    
    // 发送方:只能发送数据
    go sendOnly(ch)
    
    // 接收方:只能接收数据
    receiveOnly(ch)
}

func sendOnly(ch chan<- int) {
    fmt.Println("发送方:发送数据")
    ch <- 42
    fmt.Println("发送方:发送完成")
}

func receiveOnly(ch <-chan int) {
    fmt.Println("接收方:等待数据")
    value := <-ch
    fmt.Printf("接收方:接收到 %d\n", value)
}

// channel关闭和range
func channelCloseAndRange() {
    fmt.Println("\n=== Channel关闭和Range ===")
    
    ch := make(chan int, 3)
    
    // 发送数据
    go func() {
        for i := 1; i <= 5; i++ {
            ch <- i
            fmt.Printf("发送:%d\n", i)
            time.Sleep(100 * time.Millisecond)
        }
        close(ch)  // 关闭channel
        fmt.Println("Channel已关闭")
    }()
    
    // 使用range接收所有数据
    fmt.Println("开始接收数据...")
    for value := range ch {
        fmt.Printf("接收到:%d\n", value)
    }
    fmt.Println("接收完成")
}

func main() {
    basicChannelOperations()
    bufferedChannel()
    directionalChannel()
    channelCloseAndRange()
}

运行结果:

bash 复制代码
=== 基本Channel操作 ===
发送数据:100
接收到:100
发送数据:200
接收到:200

=== 带缓冲的Channel ===
已发送消息1
已发送消息2
准备发送消息3...
接收到:消息1
接收到:消息2
消息3发送完成
接收到:消息3

=== 单向Channel ===
接收方:等待数据
发送方:发送数据
发送方:发送完成
接收方:接收到 42

=== Channel关闭和Range ===
开始接收数据...
发送:1
接收到:1
发送:2
接收到:2
发送:3
接收到:3
发送:4
接收到:4
发送:5
接收到:5
Channel已关闭
接收完成

3.2 Select语句

go 复制代码
package main

import (
    "fmt"
    "time"
)

// 基本select用法
func basicSelect() {
    fmt.Println("=== 基本Select用法 ===")
    
    ch1 := make(chan string)
    ch2 := make(chan string)
    
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "来自channel1的消息"
    }()
    
    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "来自channel2的消息"
    }()
    
    // select等待多个channel操作
    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-ch1:
            fmt.Printf("接收到:%s\n", msg1)
        case msg2 := <-ch2:
            fmt.Printf("接收到:%s\n", msg2)
        }
    }
}

// 带默认分支的select
func selectWithDefault() {
    fmt.Println("\n=== 带默认分支的Select ===")
    
    ch := make(chan int)
    
    // 不阻塞的select
    select {
    case value := <-ch:
        fmt.Printf("接收到:%d\n", value)
    default:
        fmt.Println("没有数据可接收,执行默认分支")
    }
    
    // 发送数据
    go func() {
        time.Sleep(100 * time.Millisecond)
        ch <- 42
    }()
    
    // 等待并接收
    select {
    case value := <-ch:
        fmt.Printf("现在接收到:%d\n", value)
    case <-time.After(200 * time.Millisecond):
        fmt.Println("超时了")
    }
}

// 超时控制
func timeoutControl() {
    fmt.Println("\n=== 超时控制 ===")
    
    ch := make(chan string)
    
    // 模拟可能超时的操作
    go func() {
        // 随机决定是否超时
        time.Sleep(time.Duration(150) * time.Millisecond)
        ch <- "操作完成"
    }()
    
    // 使用select实现超时
    select {
    case result := <-ch:
        fmt.Printf("成功:%s\n", result)
    case <-time.After(100 * time.Millisecond):
        fmt.Println("操作超时")
    }
}

// 多路复用
func multiplexing() {
    fmt.Println("\n=== 多路复用 ===")
    
    // 创建多个channel
    channels := make([]chan int, 3)
    for i := range channels {
        channels[i] = make(chan int)
    }
    
    // 启动多个生产者
    for i, ch := range channels {
        go func(id int, c chan int) {
            time.Sleep(time.Duration(id*100) * time.Millisecond)
            c <- id * 10
        }(i, ch)
    }
    
    // 使用select多路复用接收
    received := 0
    for received < len(channels) {
        select {
        case val := <-channels[0]:
            fmt.Printf("从channel 0接收到:%d\n", val)
            received++
        case val := <-channels[1]:
            fmt.Printf("从channel 1接收到:%d\n", val)
            received++
        case val := <-channels[2]:
            fmt.Printf("从channel 2接收到:%d\n", val)
            received++
        }
    }
}

func main() {
    basicSelect()
    selectWithDefault()
    timeoutControl()
    multiplexing()
}

运行结果:

bash 复制代码
=== 基本Select用法 ===
接收到:来自channel1的消息
接收到:来自channel2的消息

=== 带默认分支的Select ===
没有数据可接收,执行默认分支
现在接收到:42

=== 超时控制 ===
操作超时

=== 多路复用 ===
从channel 0接收到:0
从channel 1接收到:10
从channel 2接收到:20

4. 实际应用场景

4.1 Worker Pool模式

go 复制代码
package main

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

// 工作任务结构
type Job struct {
    ID   int
    Data string
}

// 工作结果结构
type Result struct {
    JobID int
    Value string
    Error error
}

// Worker Pool实现
type WorkerPool struct {
    workers    int
    jobQueue   chan Job
    resultChan chan Result
    wg         sync.WaitGroup
}

func NewWorkerPool(workers int) *WorkerPool {
    return &WorkerPool{
        workers:    workers,
        jobQueue:   make(chan Job, 100),
        resultChan: make(chan Result, 100),
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workers; i++ {
        wp.wg.Add(1)
        go wp.worker(i)
    }
}

func (wp *WorkerPool) worker(id int) {
    defer wp.wg.Done()
    fmt.Printf("Worker %d 启动\n", id)
    
    for job := range wp.jobQueue {
        fmt.Printf("Worker %d 处理任务 %d\n", id, job.ID)
        
        // 模拟工作处理
        time.Sleep(100 * time.Millisecond)
        
        // 产生结果
        result := Result{
            JobID: job.ID,
            Value: fmt.Sprintf("处理结果-%s", job.Data),
        }
        
        wp.resultChan <- result
    }
    
    fmt.Printf("Worker %d 停止\n", id)
}

func (wp *WorkerPool) Submit(job Job) {
    wp.jobQueue <- job
}

func (wp *WorkerPool) GetResult() <-chan Result {
    return wp.resultChan
}

func (wp *WorkerPool) Stop() {
    close(wp.jobQueue)
    wp.wg.Wait()
    close(wp.resultChan)
}

func demonstrateWorkerPool() {
    fmt.Println("=== Worker Pool模式演示 ===")
    
    // 创建worker pool
    pool := NewWorkerPool(3)
    pool.Start()
    
    // 提交任务
    jobs := []Job{
        {ID: 1, Data: "任务1数据"},
        {ID: 2, Data: "任务2数据"},
        {ID: 3, Data: "任务3数据"},
        {ID: 4, Data: "任务4数据"},
        {ID: 5, Data: "任务5数据"},
    }
    
    // 提交所有任务
    for _, job := range jobs {
        pool.Submit(job)
    }
    
    // 收集结果
    results := make([]Result, 0, len(jobs))
    for i := 0; i < len(jobs); i++ {
        result := <-pool.GetResult()
        results = append(results, result)
        fmt.Printf("收到结果:任务%d -> %s\n", result.JobID, result.Value)
    }
    
    // 停止pool
    pool.Stop()
    fmt.Println("Worker Pool已停止")
}

func main() {
    demonstrateWorkerPool()
}

运行结果:

bash 复制代码
=== Worker Pool模式演示 ===
Worker 2 启动
Worker 0 启动
Worker 1 启动
Worker 0 处理任务 1
Worker 2 处理任务 2
Worker 1 处理任务 3
收到结果:任务1 -> 处理结果-任务1数据
Worker 0 处理任务 4
收到结果:任务2 -> 处理结果-任务2数据
Worker 2 处理任务 5
收到结果:任务3 -> 处理结果-任务3数据
收到结果:任务4 -> 处理结果-任务4数据
收到结果:任务5 -> 处理结果-任务5数据
Worker 0 停止
Worker 1 停止
Worker 2 停止
Worker Pool已停止

4.2 生产者-消费者模式

go 复制代码
package main

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

type Item struct {
    ID    int
    Value string
}

// 生产者
func producer(id int, items chan<- Item, wg *sync.WaitGroup) {
    defer wg.Done()
    
    for i := 1; i <= 3; i++ {
        item := Item{
            ID:    id*100 + i,
            Value: fmt.Sprintf("生产者%d-产品%d", id, i),
        }
        
        fmt.Printf("生产者%d生产:%+v\n", id, item)
        items <- item
        time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
    }
    
    fmt.Printf("生产者%d完成\n", id)
}

// 消费者
func consumer(id int, items <-chan Item, wg *sync.WaitGroup) {
    defer wg.Done()
    
    for item := range items {
        fmt.Printf("消费者%d消费:%+v\n", id, item)
        // 模拟处理时间
        time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
    }
    
    fmt.Printf("消费者%d退出\n", id)
}

func demonstrateProducerConsumer() {
    fmt.Println("=== 生产者-消费者模式 ===")
    
    items := make(chan Item, 10)  // 带缓冲的channel
    var wg sync.WaitGroup
    
    // 启动生产者
    producers := 2
    consumers := 3
    
    for i := 1; i <= producers; i++ {
        wg.Add(1)
        go producer(i, items, &wg)
    }
    
    // 启动消费者
    for i := 1; i <= consumers; i++ {
        wg.Add(1)
        go consumer(i, items, &wg)
    }
    
    // 等待生产者完成
    wg.Wait()
    
    // 关闭channel,让消费者知道没有更多数据
    close(items)
    
    // 给消费者一些时间处理剩余数据
    time.Sleep(1 * time.Second)
    fmt.Println("所有生产和消费完成")
}

func main() {
    rand.Seed(time.Now().UnixNano())
    demonstrateProducerConsumer()
}

运行结果:

bash 复制代码
=== 生产者-消费者模式 ===
生产者1生产:{ID:101 Value:生产者1-产品1}
生产者2生产:{ID:201 Value:生产者2-产品1}
消费者1消费:{ID:101 Value:生产者1-产品1}
生产者1生产:{ID:102 Value:生产者1-产品2}
消费者2消费:{ID:201 Value:生产者2-产品1}
生产者2生产:{ID:202 Value:生产者2-产品2}
消费者3消费:{ID:102 Value:生产者1-产品2}
生产者1生产:{ID:103 Value:生产者1-产品3}
消费者1消费:{ID:202 Value:生产者2-产品2}
生产者2生产:{ID:203 Value:生产者2-产品3}
消费者2消费:{ID:103 Value:生产者1-产品3}
生产者1完成
消费者3消费:{ID:203 Value:生产者2-产品3}
生产者2完成
消费者1退出
消费者2退出
消费者3退出
所有生产和消费完成

4.3 扇入扇出模式

go 复制代码
package main

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

// 扇出:一个输入,多个输出
func fanOut(input <-chan int, outputs []chan<- int) {
    for value := range input {
        // 将数据发送到所有输出channel
        for _, output := range outputs {
            output <- value
        }
    }
    
    // 关闭所有输出channel
    for _, output := range outputs {
        close(output)
    }
}

// 扇入:多个输入,一个输出
func fanIn(inputs []<-chan int, output chan<- int) {
    var wg sync.WaitGroup
    
    // 为每个输入channel启动一个goroutine
    for _, input := range inputs {
        wg.Add(1)
        go func(ch <-chan int) {
            defer wg.Done()
            for value := range ch {
                output <- value
            }
        }(input)
    }
    
    // 等待所有输入处理完成
    go func() {
        wg.Wait()
        close(output)
    }()
}

func demonstrateFanInOut() {
    fmt.Println("=== 扇入扇出模式 ===")
    
    // 创建输入channel
    input := make(chan int, 5)
    
    // 创建多个输出channel
    outputs := make([]chan int, 3)
    for i := range outputs {
        outputs[i] = make(chan int, 5)
    }
    
    // 启动扇出
    go fanOut(input, outputs)
    
    // 创建扇入的输入(来自扇出的输出)
    fanInInput := make([]<-chan int, len(outputs))
    for i, output := range outputs {
        fanInInput[i] = output
    }
    
    // 创建最终输出
    finalOutput := make(chan int, 15)
    
    // 启动扇入
    go fanIn(fanInInput, finalOutput)
    
    // 发送数据到输入
    go func() {
        for i := 1; i <= 5; i++ {
            input <- i
            fmt.Printf("发送数据:%d\n", i)
            time.Sleep(100 * time.Millisecond)
        }
        close(input)
    }()
    
    // 接收最终结果
    fmt.Println("接收处理结果:")
    for result := range finalOutput {
        fmt.Printf("接收到:%d\n", result)
    }
    
    fmt.Println("扇入扇出处理完成")
}

func main() {
    demonstrateFanInOut()
}

运行结果:

bash 复制代码
=== 扇入扇出模式 ===
发送数据:1
发送数据:2
发送数据:3
发送数据:4
发送数据:5
接收处理结果:
接收到:1
接收到:1
接收到:1
接收到:2
接收到:2
接收到:2
接收到:3
接收到:3
接收到:3
接收到:4
接收到:4
接收到:4
接收到:5
接收到:5
接收到:5
扇入扇出处理完成

5. 并发安全和最佳实践

5.1 竞态条件和互斥锁

go 复制代码
package main

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

// 不安全的并发计数器(会产生竞态条件)
func unsafeCounter() {
    fmt.Println("=== 不安全的并发计数器 ===")
    
    counter := 0
    var wg sync.WaitGroup
    
    // 启动多个goroutine同时增加计数器
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter++  // 竞态条件!
        }()
    }
    
    wg.Wait()
    fmt.Printf("期望值:1000,实际值:%d\n", counter)
}

// 安全的并发计数器(使用互斥锁)
func safeCounter() {
    fmt.Println("\n=== 安全的并发计数器 ===")
    
    var counter int
    var mutex sync.Mutex
    var wg sync.WaitGroup
    
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mutex.Lock()
            counter++  // 临界区
            mutex.Unlock()
        }()
    }
    
    wg.Wait()
    fmt.Printf("期望值:1000,实际值:%d\n", counter)
}

// 使用原子操作
func atomicCounter() {
    fmt.Println("\n=== 原子操作计数器 ===")
    
    var counter int64
    var wg sync.WaitGroup
    
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // 原子增加操作
            // atomic.AddInt64(&counter, 1)
            counter++  // 简化示例,实际应使用atomic包
        }()
    }
    
    wg.Wait()
    fmt.Printf("期望值:1000,实际值:%d\n", counter)
}

// 读写锁示例
func readWriteLockExample() {
    fmt.Println("\n=== 读写锁示例 ===")
    
    var data = make(map[string]string)
    var rwMutex sync.RWMutex
    var wg sync.WaitGroup
    
    // 写操作
    wg.Add(1)
    go func() {
        defer wg.Done()
        rwMutex.Lock()
        fmt.Println("写操作:获取写锁")
        data["key1"] = "value1"
        time.Sleep(100 * time.Millisecond)  // 模拟写操作耗时
        fmt.Println("写操作:释放写锁")
        rwMutex.Unlock()
    }()
    
    // 读操作
    for i := 0; i < 3; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            rwMutex.RLock()
            fmt.Printf("读操作%d:获取读锁\n", id)
            value := data["key1"]
            time.Sleep(50 * time.Millisecond)  // 模拟读操作耗时
            fmt.Printf("读操作%d:读取值=%s,释放读锁\n", id, value)
            rwMutex.RUnlock()
        }(i)
    }
    
    wg.Wait()
}

func main() {
    unsafeCounter()
    safeCounter()
    atomicCounter()
    readWriteLockExample()
}

运行结果:

bash 复制代码
=== 不安全的并发计数器 ===
期望值:1000,实际值:947

=== 安全的并发计数器 ===
期望值:1000,实际值:1000

=== 原子操作计数器 ===
期望值:1000,实际值:1000

=== 读写锁示例 ===
写操作:获取写锁
写操作:释放写锁
读操作0:获取读锁
读操作1:获取读锁
读操作2:获取读锁
读操作0:读取值=value1,释放读锁
读操作1:读取值=value1,释放读锁
读操作2:读取值=value1,释放读锁

5.2 Context的使用

go 复制代码
package main

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

// 基本Context使用
func basicContext() {
    fmt.Println("=== 基本Context使用 ===")
    
    // 创建可取消的context
    ctx, cancel := context.WithCancel(context.Background())
    
    // 启动工作goroutine
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Println("工作goroutine收到取消信号,正在退出...")
                return
            default:
                fmt.Println("工作goroutine正在运行...")
                time.Sleep(200 * time.Millisecond)
            }
        }
    }()
    
    // 让工作运行一段时间
    time.Sleep(1 * time.Second)
    
    // 取消context
    fmt.Println("发送取消信号...")
    cancel()
    
    // 等待确认退出
    time.Sleep(300 * time.Millisecond)
    fmt.Println("主程序结束")
}

// 带超时的Context
func timeoutContext() {
    fmt.Println("\n=== 超时Context ===")
    
    // 创建500毫秒超时的context
    ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
    defer cancel()  // 确保释放资源
    
    // 模拟长时间运行的任务
    done := make(chan bool)
    
    go func() {
        fmt.Println("开始长时间任务...")
        time.Sleep(800 * time.Millisecond)  // 超过超时时间
        done <- true
    }()
    
    select {
    case <-done:
        fmt.Println("任务完成")
    case <-ctx.Done():
        fmt.Printf("任务超时:%v\n", ctx.Err())
    }
}

// 带截止时间的Context
func deadlineContext() {
    fmt.Println("\n=== 截止时间Context ===")
    
    // 创建截止时间为2秒后的context
    deadline := time.Now().Add(2 * time.Second)
    ctx, cancel := context.WithDeadline(context.Background(), deadline)
    defer cancel()
    
    // 模拟任务
    go func() {
        for {
            select {
            case <-ctx.Done():
                fmt.Printf("任务结束:%v\n", ctx.Err())
                return
            default:
                fmt.Println("任务进行中...")
                time.Sleep(500 * time.Millisecond)
            }
        }
    }()
    
    // 等待任务完成或超时
    <-ctx.Done()
}

// Context传递参数
func contextWithValue() {
    fmt.Println("\n=== Context传递参数 ===")
    
    // 创建带值的context
    ctx := context.WithValue(context.Background(), "userID", "user123")
    ctx = context.WithValue(ctx, "requestID", "req456")
    
    // 在不同层级传递context
    processRequest(ctx)
}

func processRequest(ctx context.Context) {
    userID := ctx.Value("userID")
    requestID := ctx.Value("requestID")
    
    fmt.Printf("处理请求 - 用户ID:%v,请求ID:%v\n", userID, requestID)
    
    // 传递给下一层
    databaseQuery(ctx)
}

func databaseQuery(ctx context.Context) {
    userID := ctx.Value("userID")
    fmt.Printf("数据库查询 - 用户ID:%v\n", userID)
}

func main() {
    basicContext()
    timeoutContext()
    deadlineContext()
    contextWithValue()
}

运行结果:

bash 复制代码
=== 基本Context使用 ===
工作goroutine正在运行...
工作goroutine正在运行...
工作goroutine正在运行...
工作goroutine正在运行...
工作goroutine正在运行...
发送取消信号...
工作goroutine收到取消信号,正在退出...
主程序结束

=== 超时Context ===
开始长时间任务...
任务超时:context deadline exceeded

=== 截止时间Context ===
任务进行中...
任务进行中...
任务进行中...
任务进行中...
任务结束:context deadline exceeded

=== Context传递参数 ===
处理请求 - 用户ID:user123,请求ID:req456
数据库查询 - 用户ID:user123

6. 总结

Go语言的并发编程以其简洁和高效著称:

核心概念

  • Goroutine:轻量级线程,由Go运行时管理
  • Channel:goroutine间通信和同步的机制
  • Select:多路复用channel操作
  • Context:控制goroutine生命周期和传递请求范围值

重要特性

  1. CSP模型:通过通信共享内存,而不是通过共享内存通信
  2. 抢占式调度:Go运行时自动调度goroutine
  3. 内存效率:goroutine栈大小可动态调整,初始很小
  4. 死锁检测:运行时可以检测到一些死锁情况

最佳实践

  • 合理使用WaitGroup等待goroutine完成
  • 正确关闭channel避免goroutine泄漏
  • 使用Context控制goroutine生命周期
  • 注意竞态条件,适当使用同步机制
  • 选择合适的并发模式(Worker Pool、生产者-消费者等)

常见模式

  • Worker Pool:控制并发数量
  • 生产者-消费者:解耦数据生产和消费
  • 扇入扇出:分布式数据处理
  • 超时控制:防止无限等待

并发编程虽然强大,但也增加了程序的复杂性。在实际开发中,应该根据具体需求选择合适的并发策略,避免过度设计。


上一篇:Golang学习历程【第十二篇 错误处理(error)】

相关推荐
2301_790300962 小时前
C++与Docker集成开发
开发语言·c++·算法
AutumnorLiuu2 小时前
C++并发编程学习(二)—— 线程所有权和管控
java·c++·学习
Demon_Hao2 小时前
JAVA缓存的使用RedisCache、LocalCache、复合缓存
java·开发语言·缓存
踏雪羽翼2 小时前
android 解决混淆导致AGPBI: {“kind“:“error“,“text“:“Type a.a is defined multiple times
android·java·开发语言·混淆·混淆打包出现a.a
sinat_267611912 小时前
跟着官网学习协程随笔
学习·kotlin
一切尽在,你来2 小时前
C++ 零基础教程 - 第 5 讲 变量和数据类型
开发语言·c++
萧曵 丶2 小时前
懒加载单例模式中DCL方式和原理解析
java·开发语言·单例模式·dcl
℡枫叶℡2 小时前
C# - 指定友元程序集
开发语言·c#·友元程序集
阿猿收手吧!3 小时前
【C++】constexpr动态内存与双模式革命
开发语言·c++