go-study/semaphore/semaphore.go
fantasticbin 5b48ea1a62 使用codex优化代码,具体如下:
主要优化

  - err_group:去掉多余 goroutine,避免潜在泄漏;并把并发 append 改为按下标写
    入,消除数据竞争。
  - err_group 测试稳定性增强:放宽超时并增加结果长度断言。
  - semaphore:修复等待队列元素类型断言错误(*waiter);补充非法参数校验(负数
    acquire/release)。
  - SemaChan:修复 Lock/Unlock 逻辑(初始化令牌桶),避免永久阻塞。
  - observer:修复“每次 Notify 都启动新 fanout 协程”的问题:改为 sync.Once 只启动一次
    fanOut。
  - observer:修复并发读写观察者列表问题:给 Attach/Detach/fanOut 增加读写锁保护。
  - observer:去掉 fanout 内部额外再起 goroutine和自动关闭所有 observer 的行为,避
    免重复关闭/竞态风险(仍保留 Detach 时关闭单个 observer)。
  - lock_free:修复可取消延迟队列的计数错误与 timers map 并发访问问题。
  - lock_free:checkAckStatus 改为非阻塞读取,避免入队路径被卡住。
  - routine:提供默认空任务并忽略 nil taskFn,防止空指针调用。
  - ticker:发送改为非阻塞,Stop 幂等化,降低阻塞和重复关闭风险。
  - query_builder:WaitAndGo 增加 goroutine 内 panic 转 error;测试里
    的 GORM filter 链式写法修正。

  新增测试

  - 新增 semaphore 测试,覆盖 Acquire/Release/TryAcquire 与 SemaChan 并发上限。
2026-03-05 21:53:11 +08:00

162 lines
3.1 KiB
Go

package semaphore
import (
"container/list"
"context"
"errors"
"sync"
)
type waiter struct {
n int64
ready chan<- struct{} // 唤醒信号
}
type Semaphore struct {
size int64 // 资源数量
cur int64 // 当前已使用的资源数量
mu sync.Mutex
waiters list.List // 等待队列
}
func NewSemaphore(n int64) *Semaphore {
return &Semaphore{
size: n,
}
}
func (s *Semaphore) Acquire(ctx context.Context, n int64) error {
if n <= 0 {
if n == 0 {
return nil
}
return errors.New("semaphore: negative acquire")
}
done := ctx.Done()
s.mu.Lock()
// 保证 ctx.Done() happened before Semaphore.Acquire()
select {
case <-done:
s.mu.Unlock()
return ctx.Err()
default:
}
// 快速路径:如果当前可用资源足够,直接分配
if s.size-s.cur >= n && s.waiters.Len() == 0 {
s.cur += n
s.mu.Unlock()
return nil
}
// 慢路径:需要等待释放资源
return s.acquireSlow(ctx, n)
}
func (s *Semaphore) acquireSlow(ctx context.Context, n int64) error {
done := ctx.Done()
// 如果请求的资源数量超过了所能提供的资源数量,则只能依靠 ctx.Done() 来退出
if n > s.size {
s.mu.Unlock()
<-done
return ctx.Err()
}
// 资源不足,将调用者加入等待队列
// 同时创建一个信号通道,用于唤醒等待的调用者
ready := make(chan struct{})
w := &waiter{n: n, ready: ready}
elem := s.waiters.PushBack(w)
s.mu.Unlock()
select {
case <-done:
s.mu.Lock()
select {
case <-ready:
// 如果已经被唤醒,假装已经成功获取资源
s.cur -= n
s.notifyWaiters()
default:
// 如果还没有被唤醒,从等待队列中移除调用者自己
isFront := s.waiters.Front() == elem
s.waiters.Remove(elem)
// 如果当前调用者是队列的第一个且有多余资源,唤醒下一个等待者
if isFront && s.size > s.cur {
s.notifyWaiters()
}
}
s.mu.Unlock()
return ctx.Err()
case <-ready:
// 成功获取资源,唤醒信号已发送
select {
case <-done:
s.Release(n)
return ctx.Err()
default:
}
return nil
}
}
func (s *Semaphore) notifyWaiters() {
for {
next := s.waiters.Front()
if next == nil {
break // 没有等待者
}
w := next.Value.(*waiter)
if s.size-s.cur < w.n {
// 没有足够的资源满足下一个等待者
// 没有必要继续唤醒后续等待者,防止有等待者处于饥饿状态
break
}
s.cur += w.n
s.waiters.Remove(next)
close(w.ready) // 唤醒等待者
}
}
func (s *Semaphore) Release(n int64) {
if n <= 0 {
if n == 0 {
return
}
panic("semaphore: negative release")
}
s.mu.Lock()
s.cur -= n // 释放 n 个资源
if s.cur < 0 {
s.mu.Unlock()
panic("semaphore: released more than held")
}
s.notifyWaiters() // 唤醒等待者
s.mu.Unlock()
}
func (s *Semaphore) TryAcquire(n int64) bool {
if n <= 0 {
return n == 0
}
s.mu.Lock()
// 检查当前可用资源是否足够,并且还没有等待者
success := s.size-s.cur >= n && s.waiters.Len() == 0
if success {
s.cur += n // 分配资源
}
s.mu.Unlock()
return success
}