第1章:Go语言并发编程基础

6 阅读5分钟

第1章:Go语言并发编程基础

1.1 Goroutine与Channel在sfsEdgeStore中的应用

在 sfsEdgeStore 中,我们大量使用了 Go 语言的并发特性来实现高性能的数据处理。让我们从 main.go 开始分析:

1.1.1 主协程与信号处理

// main.go:166-170
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down adapter...")

技术解析:

  • 使用缓冲 channel 接收系统信号
  • signal.Notify 注册信号处理器
  • <-quit 阻塞直到收到中断信号
  • 这是 Go 语言优雅关闭的标准模式

1.1.2 异步消息处理

在 mqtt/client.go 中,我们使用 goroutine 异步处理消息,避免阻塞 MQTT 客户端:

// mqtt/client.go:247-396
func (c *Client) messageHandler() mqtt.MessageHandler {
    return func(client mqtt.Client, msg mqtt.Message) {
        // 增加MQTT消息接收计数
        if c.monitor != nil {
            c.monitor.IncrementMQTTMessagesReceived()
        }

        log.Printf("Received message on topic: %s", msg.Topic())

        // 使用goroutine异步处理消息,避免阻塞MQTT消息接收
        go func() {
            // 使用edgex包处理消息
            event, err := edgex.ProcessMessage(msg.Payload())
            // ... 省略其他代码 ...
        }()
    }
}

技术要点:

  1. 非阻塞处理:每个消息都在独立的 goroutine 中处理
  2. 避免阻塞:MQTT 客户端的消息处理函数不应阻塞
  3. 并发安全:monitor 的方法需要是并发安全的

1.1.3 队列处理协程

数据队列的处理也是在后台 goroutine 中运行:

// main.go:105-112
dataQueue.ProcessQueue(func(data interface{}) error {
    records, ok := data.([]*map[string]any)
    if !ok {
        return fmt.Errorf("invalid data type in queue")
    }
    // 使用重试机制插入数据
    return database.BatchInsertWithRetry(database.Table, records, 3, 2*time.Second)
})

设计模式:

  • 生产者-消费者模式:MQTT 客户端是生产者,队列处理是消费者
  • 回调函数:通过函数参数传入处理逻辑
  • 重试机制:内置指数退避重试

1.2 sync包的使用:互斥锁与条件变量

让我们查看 queue/queue.go 中的并发控制实现(代码示例):

1.2.1 互斥锁保护共享状态

// 假设的队列实现示例
type Queue struct {
    mu       sync.Mutex
    items    []interface{}
    notEmpty *sync.Cond
}

func NewQueue() *Queue {
    q := &Queue{
        items: make([]interface{}, 0),
    }
    q.notEmpty = sync.NewCond(&q.mu)
    return q
}

func (q *Queue) Enqueue(item interface{}) error {
    q.mu.Lock()
    defer q.mu.Unlock()
    
    q.items = append(q.items, item)
    q.notEmpty.Signal() // 唤醒等待的消费者
    return nil
}

func (q *Queue) Dequeue() (interface{}, error) {
    q.mu.Lock()
    defer q.mu.Unlock()
    
    for len(q.items) == 0 {
        q.notEmpty.Wait() // 等待直到队列不为空
    }
    
    item := q.items[0]
    q.items = q.items[1:]
    return item, nil
}

并发模式解析:

  1. 互斥锁(Mutex):保护共享数据结构
  2. 条件变量(Cond):实现线程间协作
  3. defer Unlock:确保锁一定会被释放
  4. for循环检查:防止虚假唤醒(spurious wakeup)

1.3 原子操作与无锁编程

对于简单的计数器,我们使用 atomic 包实现无锁操作:

// monitor/monitor.go 中的计数器实现示例
import "sync/atomic"

type Monitor struct {
    mqttMessagesReceived  uint64
    mqttMessagesProcessed uint64
    databaseOperations    uint64
    httpRequests          uint64
}

func (m *Monitor) IncrementMQTTMessagesReceived() {
    atomic.AddUint64(&m.mqttMessagesReceived, 1)
}

func (m *Monitor) IncrementMQTTMessagesProcessed() {
    atomic.AddUint64(&m.mqttMessagesProcessed, 1)
}

func (m *Monitor) GetMetrics() map[string]interface{} {
    return map[string]interface{}{
        "mqtt_messages_received":  atomic.LoadUint64(&m.mqttMessagesReceived),
        "mqtt_messages_processed": atomic.LoadUint64(&m.mqttMessagesProcessed),
        "database_operations":     atomic.LoadUint64(&m.databaseOperations),
        "http_requests":           atomic.LoadUint64(&m.httpRequests),
    }
}

性能优势:

  • 无锁操作:原子操作在用户态完成,无需系统调用
  • 高吞吐量:适合频繁更新的计数器
  • 内存序:Go 的 atomic 保证顺序一致性

1.4 内存管理与对象池

在 mqtt/client.go 中,我们使用对象池来减少内存分配:

// mqtt/mapPool.go - 对象池实现
package mqtt

import "sync"

// MapPool map对象池,用于减少内存分配
type MapPool struct {
    pool sync.Pool
}

var objPool *MapPool

func init() {
    objPool = &MapPool{
        pool: sync.Pool{
            New: func() interface{} {
                return make(map[string]interface{})
            },
        },
    }
}

// GetMap 从池中获取一个map
func (p *MapPool) GetMap() map[string]interface{} {
    return p.pool.Get().(map[string]interface{})
}

// PutMap 将map归还到池中
func (p *MapPool) PutMap(m map[string]interface{}) {
    // 清空map
    for k := range m {
        delete(m, k)
    }
    p.pool.Put(m)
}

// GetMap 全局获取map
func GetMap() map[string]interface{} {
    return objPool.GetMap()
}

// PutMap 全局归还map
func PutMap(m map[string]interface{}) {
    objPool.PutMap(m)
}

在 mqtt/client.go 中的使用:

// mqtt/client.go:266-267
// 从对象池获取map,减少内存分配
data := objPool.GetMap()

// ... 使用 data ...

// mqtt/client.go:340-342
// 归还map对象到池中
for _, data := range records {
    objPool.PutMap(*data)
}

技术收益:

  • 减少 GC 压力:复用对象,减少频繁的内存分配和回收
  • 提升性能:对象池命中时无需重新分配
  • 注意事项:使用前必须清空,防止数据污染

1.5 Context包的应用:超时控制与取消

虽然在当前代码中没有大量使用 context,但在 HTTP 服务器中,context 是标准的超时控制机制:

// server/server.go 中可以添加的超时控制示例
func (s *Server) handleQueryReadings(w http.ResponseWriter, r *http.Request) {
    // 创建带超时的 context
    ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
    defer cancel()
    
    // 使用 context 控制数据库查询
    resultChan := make(chan struct {
        readings record.Records
        err      error
    }, 1)
    
    go func() {
        readings, err := database.QueryRecords(database.Table, deviceName, startTime, endTime)
        resultChan <- struct {
            readings record.Records
            err      error
        }{readings, err}
    }()
    
    select {
    case <-ctx.Done():
        w.WriteHeader(http.StatusRequestTimeout)
        json.NewEncoder(w).Encode(map[string]string{"error": "request timeout"})
        return
    case result := <-resultChan:
        if result.err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            json.NewEncoder(w).Encode(map[string]string{"error": result.err.Error()})
            return
        }
        // 处理结果...
    }
}

1.6 并发模式总结

sfsEdgeStore 中使用的核心并发模式:

模式应用场景实现位置
Goroutine Pool消息处理mqtt/client.go
生产者-消费者数据队列queue/queue.go
对象池内存优化mqtt/mapPool.go
原子操作计数器monitor/monitor.go
信号处理优雅关闭main.go
互斥锁共享状态多个模块

1.7 实战练习

练习1:实现一个简单的工作池

package main

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

type WorkerPool struct {
    tasks    chan func()
    wg       sync.WaitGroup
    shutdown chan struct{}
}

func NewWorkerPool(workerCount int) *WorkerPool {
    pool := &WorkerPool{
        tasks:    make(chan func(), 100),
        shutdown: make(chan struct{}),
    }
    
    pool.wg.Add(workerCount)
    for i := 0; i < workerCount; i++ {
        go pool.worker()
    }
    
    return pool
}

func (p *WorkerPool) worker() {
    defer p.wg.Done()
    for {
        select {
        case task := <-p.tasks:
            task()
        case <-p.shutdown:
            return
        }
    }
}

func (p *WorkerPool) Submit(task func()) {
    select {
    case p.tasks <- task:
    default:
        fmt.Println("Task queue full, dropping task")
    }
}

func (p *WorkerPool) Shutdown() {
    close(p.shutdown)
    p.wg.Wait()
}

func main() {
    pool := NewWorkerPool(3)
    defer pool.Shutdown()
    
    for i := 0; i < 10; i++ {
        id := i
        pool.Submit(func() {
            fmt.Printf("Processing task %d\n", id)
            time.Sleep(100 * time.Millisecond)
        })
    }
    
    time.Sleep(1 * time.Second)
}

练习2:实现一个线程安全的缓存

package main

import (
    "sync"
    "time"
)

type Cache struct {
    mu    sync.RWMutex
    items map[string]cacheItem
}

type cacheItem struct {
    value      interface{}
    expiresAt  time.Time
}

func NewCache() *Cache {
    c := &Cache{
        items: make(map[string]cacheItem),
    }
    go c.cleanupExpired()
    return c
}

func (c *Cache) Set(key string, value interface{}, ttl time.Duration) {
    c.mu.Lock()
    defer c.mu.Unlock()
    
    c.items[key] = cacheItem{
        value:     value,
        expiresAt: time.Now().Add(ttl),
    }
}

func (c *Cache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    
    item, exists := c.items[key]
    if !exists {
        return nil, false
    }
    
    if time.Now().After(item.expiresAt) {
        return nil, false
    }
    
    return item.value, true
}

func (c *Cache) cleanupExpired() {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()
    
    for range ticker.C {
        c.mu.Lock()
        now := time.Now()
        for key, item := range c.items {
            if now.After(item.expiresAt) {
                delete(c.items, key)
            }
        }
        c.mu.Unlock()
    }
}

1.8 本章小结

本章我们学习了:

  1. Goroutine 的基本使用和在 sfsEdgeStore 中的实际应用
  2. sync 包的互斥锁和条件变量实现线程安全
  3. atomic 包的无锁操作实现高性能计数器
  4. sync.Pool 对象池减少内存分配和 GC 压力
  5. context 包的超时控制和取消机制
  6. 几个实用的并发模式和实战练习

本书版本:1.0.0
最后更新:2026-03-08
sfsEdgeStore - 让边缘数据存储更简单!🚀
技术栈 - Go语言、sfsDb与EdgeX Foundry。纯golang工业物联网边缘计算技术栈 项目地址GitHub