元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
Go运行时系统
▶
调度器原理
Goroutine调度机制
GMP模型详解
抢占式调度实现
系统线程管理
调度器源码实现分析
▶
网络轮询器
I/O多路复用实现
Epoll事件循环
异步IO处理
▶
系统监控
Sysmon监控线程
死锁检测机制
资源使用监控
▶
内存管理
▶
内存分配器
TCMalloc变体实现
mcache与mspan
对象分配流程
堆内存管理
▶
栈管理
分段栈实现
连续栈优化
栈扩容机制
▶
并发模型
▶
Channel实现
Channel底层结构
发送与接收流程
select实现原理
同步原语实现
▶
原子操作
CPU指令支持
内存顺序保证
sync/atomic实现
▶
并发原语
sync.Map实现原理
WaitGroup实现机制
Mutex锁实现
RWMutex读写锁
Once单次执行
Cond条件变量
信号量代码详解
信号量实现源码分析
信号量应用示例
▶
垃圾回收机制
▶
GC核心算法
三色标记法
三色标记法示例解析
写屏障技术
混合写屏障实现
▶
GC优化策略
GC触发条件
并发标记优化
内存压缩策略
▶
编译与链接
▶
编译器原理
AST构建过程
SSA生成优化
逃逸分析机制
▶
链接器实现
符号解析处理
重定位实现
ELF文件生成
▶
类型系统
▶
基础类型
类型系统概述
基本类型实现
复合类型结构
▶
切片与Map
切片实现原理
切片扩容机制
Map哈希实现
Map扩容机制详解
Map冲突解决
Map并发安全
▶
反射与接口
▶
类型系统
rtype底层结构
接口内存布局
方法表构建
▶
反射机制
ValueOf实现
反射调用代价
类型断言优化
▶
标准库实现
▶
同步原语
sync.Mutex实现
RWMutex原理
WaitGroup机制
▶
Context实现
上下文传播链
取消信号传递
Value存储优化
▶
time定时器实现
Timer实现原理
Ticker周期触发机制
时间轮算法详解
定时器性能优化
定时器源码分析
▶
执行流程
▶
错误异常
错误处理机制
panic与recover
错误传播最佳实践
错误包装与检查
自定义错误类型
▶
延迟执行
defer源码实现分析
▶
性能优化
▶
执行效率优化
栈内存优化
函数内联策略
边界检查消除
字符串优化
切片预分配
▶
内存优化
对象池实现
内存对齐优化
GC参数调优
内存泄漏分析
堆栈分配优化
▶
并发性能优化
Goroutine池化
并发模式优化
锁竞争优化
原子操作应用
Channel效率优化
▶
网络性能优化
网络轮询优化
连接池管理
网络缓冲优化
超时处理优化
网络协议调优
▶
编译优化
编译器优化选项
代码生成优化
链接优化技术
交叉编译优化
构建缓存优化
▶
性能分析工具
性能基准测试
CPU分析技术
内存分析方法
追踪工具应用
性能监控系统
▶
调试与工具
▶
dlv调试
dlv调试器使用
dlv命令详解
dlv远程调试
▶
调试支持
GDB扩展实现
核心转储分析
调试器接口
▶
分析工具
pprof实现原理
trace工具原理
竞态检测实现
▶
跨平台与兼容性
▶
系统抽象层
syscall封装
OS适配层
字节序处理
▶
cgo机制
CGO调用开销
指针传递机制
内存管理边界
▶
工程管理
▶
包管理
Go模块基础
模块初始化配置
依赖版本管理
go.mod文件详解
私有模块配置
代理服务设置
工作区管理
模块版本选择
依赖替换与撤回
模块缓存管理
第三方包版本形成机制
发布时间:
2025-04-24 23:09
↑
☰
# Go语言信号量实际应用示例 信号量是一种强大的并发控制机制,在Go语言中有着广泛的应用。本文将通过实际示例展示如何在Go程序中使用信号量解决各种并发问题。 ## 使用标准库中的信号量 Go语言标准库中并没有直接提供信号量的实现,但我们可以通过`sync.Mutex`和`sync.Cond`组合实现简单的信号量,或者使用第三方库如`golang.org/x/sync/semaphore`。 ### 使用golang.org/x/sync/semaphore ```go package main import ( "context" "fmt" "log" "runtime" "time" "golang.org/x/sync/semaphore" ) func main() { // 创建一个权重为5的信号量,表示最多允许5个并发操作 weight := int64(5) sem := semaphore.NewWeighted(weight) ctx := context.Background() // 模拟10个并发任务 for i := 0; i < 10; i++ { // 尝试获取信号量 if err := sem.Acquire(ctx, 1); err != nil { log.Printf("Failed to acquire semaphore: %v\n", err) break } // 启动goroutine执行任务 go func(id int) { defer sem.Release(1) // 模拟任务执行 fmt.Printf("Worker %d: 开始执行任务\n", id) time.Sleep(2 * time.Second) fmt.Printf("Worker %d: 任务完成\n", id) }(i) } // 等待所有信号量被释放 if err := sem.Acquire(ctx, weight); err != nil { log.Printf("Failed to acquire semaphore: %v\n", err) } fmt.Println("所有任务已完成") } ``` 这个示例展示了如何使用信号量限制并发任务的数量。无论有多少个任务需要执行,同时运行的任务数量不会超过信号量的权重值。 ## 使用信号量实现资源池 信号量非常适合实现资源池,限制对有限资源的并发访问: ```go package main import ( "context" "fmt" "math/rand" "sync" "time" "golang.org/x/sync/semaphore" ) // 数据库连接池示例 type DBConnection struct { id int } func (db *DBConnection) Query() int { // 模拟查询 time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) return rand.Intn(100) } type ConnectionPool struct { conns []*DBConnection sem *semaphore.Weighted available []bool mu sync.Mutex } func NewConnectionPool(size int) *ConnectionPool { pool := &ConnectionPool{ conns: make([]*DBConnection, size), sem: semaphore.NewWeighted(int64(size)), available: make([]bool, size), } // 初始化连接 for i := 0; i < size; i++ { pool.conns[i] = &DBConnection{id: i} pool.available[i] = true } return pool } func (p *ConnectionPool) Acquire(ctx context.Context) (*DBConnection, error) { // 获取信号量 if err := p.sem.Acquire(ctx, 1); err != nil { return nil, err } // 分配连接 p.mu.Lock() var conn *DBConnection for i, available := range p.available { if available { p.available[i] = false conn = p.conns[i] break } } p.mu.Unlock() return conn, nil } func (p *ConnectionPool) Release(conn *DBConnection) { p.mu.Lock() // 查找连接并标记为可用 for i, c := range p.conns { if c.id == conn.id { p.available[i] = true break } } p.mu.Unlock() // 释放信号量 p.sem.Release(1) } func main() { // 创建一个有5个连接的连接池 pool := NewConnectionPool(5) ctx := context.Background() // 模拟20个并发请求 var wg sync.WaitGroup for i := 0; i < 20; i++ { wg.Add(1) go func(id int) { defer wg.Done() // 获取连接 conn, err := pool.Acquire(ctx) if err != nil { fmt.Printf("请求 %d: 无法获取连接: %v\n", id, err) return } fmt.Printf("请求 %d: 获取连接 %d\n", id, conn.id) // 使用连接 result := conn.Query() fmt.Printf("请求 %d: 查询结果: %d\n", id, result) // 释放连接 pool.Release(conn) fmt.Printf("请求 %d: 释放连接 %d\n", id, conn.id) }(i) } wg.Wait() fmt.Println("所有请求已完成") } ``` ## 使用信号量实现限流器 信号量可以用来实现简单的限流器,控制请求速率: ```go package main import ( "context" "fmt" "sync" "time" "golang.org/x/sync/semaphore" ) // 限流器实现 type RateLimiter struct { sem *semaphore.Weighted interval time.Duration } func NewRateLimiter(rate int, interval time.Duration) *RateLimiter { return &RateLimiter{ sem: semaphore.NewWeighted(int64(rate)), interval: interval, } } func (r *RateLimiter) Allow(ctx context.Context) error { // 尝试获取信号量 err := r.sem.Acquire(ctx, 1) if err != nil { return err } // 启动一个goroutine在指定时间后释放信号量 go func() { time.Sleep(r.interval) r.sem.Release(1) }() return nil } func main() { // 创建一个限流器,每秒允许5个请求 limiter := NewRateLimiter(5, time.Second) ctx := context.Background() // 模拟20个并发请求 var wg sync.WaitGroup for i := 0; i < 20; i++ { wg.Add(1) go func(id int) { defer wg.Done() // 尝试通过限流器 start := time.Now() err := limiter.Allow(ctx) elapsed := time.Since(start) if err != nil { fmt.Printf("请求 %d: 被限流: %v\n", id, err) return } fmt.Printf("请求 %d: 通过限流器,等待时间: %v\n", id, elapsed) // 模拟处理请求 time.Sleep(100 * time.Millisecond) fmt.Printf("请求 %d: 处理完成\n", id) }(i) } wg.Wait() fmt.Println("所有请求已完成") } ``` ## 使用信号量实现工作池 信号量可以用来控制工作池中的并发工作者数量: ```go package main import ( "context" "fmt" "math/rand" "sync" "time" "golang.org/x/sync/semaphore" ) // 任务定义 type Task struct { id int } // 工作池实现 type WorkerPool struct { tasks chan Task sem *semaphore.Weighted wg sync.WaitGroup workers int } func NewWorkerPool(workers int, queueSize int) *WorkerPool { return &WorkerPool{ tasks: make(chan Task, queueSize), sem: semaphore.NewWeighted(int64(workers)), workers: workers, } } func (p *WorkerPool) Start(ctx context.Context) { for i := 0; i < p.workers; i++ { p.wg.Add(1) go func(workerID int) { defer p.wg.Done() for { select { case task, ok := <-p.tasks: if !ok { return // 通道已关闭,退出 } // 获取信号量 err := p.sem.Acquire(ctx, 1) if err != nil { fmt.Printf("工作者 %d: 无法获取信号量: %v\n", workerID, err) return } // 处理任务 fmt.Printf("工作者 %d: 开始处理任务 %d\n", workerID, task.id) time.Sleep(time.Duration(rand.Intn(500)+500) * time.Millisecond) // 模拟处理时间 fmt.Printf("工作者 %d: 完成任务 %d\n", workerID, task.id) // 释放信号量 p.sem.Release(1) case <-ctx.Done(): fmt.Printf("工作者 %d: 收到取消信号,退出\n", workerID) return } } }(i) } } func (p *WorkerPool) Submit(task Task) { p.tasks <- task } func (p *WorkerPool) Stop() { close(p.tasks) p.wg.Wait() } func main() { // 创建一个有5个工作者的工作池,任务队列大小为20 pool := NewWorkerPool(5, 20) ctx := context.Background() // 启动工作池 pool.Start(ctx) // 提交20个任务 for i := 0; i < 20; i++ { pool.Submit(Task{id: i}) } // 等待一段时间让任务处理 time.Sleep(5 * time.Second) // 停止工作池 pool.Stop() fmt.Println("工作池已停止") } ``` ## 使用信号量实现并发控制的最佳实践 1. **选择合适的信号量实现**: - 对于简单场景,可以使用`golang.org/x/sync/semaphore` - 对于复杂场景,可能需要自定义实现或使用其他并发原语组合 2. **合理设置信号量大小**: - 太小会限制并发性能 - 太大会导致资源过度使用 - 可以根据CPU核心数、内存大小或外部资源限制来设置 3. **处理获取信号量超时**: - 使用带超时的context避免无限等待 - 实现优雅的降级策略 4. **确保正确释放信号量**: - 使用defer确保在各种情况下都能释放信号量 - 避免重复释放或忘记释放 ## 总结 信号量是Go语言并发编程中的强大工具,可以用于限制并发数量、实现资源池、控制访问速率等多种场景。通过本文的示例,我们展示了信号量在实际应用中的多种用法,希望能帮助读者更好地理解和使用信号量解决并发问题。 在Go语言中,虽然信号量不是直接暴露的原语,但通过标准库或第三方库提供的实现,我们可以轻松地在程序中使用信号量控制并发,提高程序的性能和稳定性。