第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())
// ... 省略其他代码 ...
}()
}
}
技术要点:
- 非阻塞处理:每个消息都在独立的 goroutine 中处理
- 避免阻塞:MQTT 客户端的消息处理函数不应阻塞
- 并发安全: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
}
并发模式解析:
- 互斥锁(Mutex):保护共享数据结构
- 条件变量(Cond):实现线程间协作
- defer Unlock:确保锁一定会被释放
- 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 本章小结
本章我们学习了:
- Goroutine 的基本使用和在 sfsEdgeStore 中的实际应用
- sync 包的互斥锁和条件变量实现线程安全
- atomic 包的无锁操作实现高性能计数器
- sync.Pool 对象池减少内存分配和 GC 压力
- context 包的超时控制和取消机制
- 几个实用的并发模式和实战练习
本书版本:1.0.0
最后更新:2026-03-08
sfsEdgeStore - 让边缘数据存储更简单!🚀
技术栈 - Go语言、sfsDb与EdgeX Foundry。纯golang工业物联网边缘计算技术栈
项目地址:GitHub