悠悠楠杉
网站页面
正文:
在Go语言的并发编程中,信号量(Semaphore)是一种经典的资源访问控制机制,用于限制同时访问共享资源的协程数量。然而,实际场景中我们往往需要为信号量添加超时功能,避免协程因资源长期不可用而阻塞。本文将介绍如何基于Go的channel和context实现一个带超时机制的信号量。
信号量的核心思想是通过计数器控制资源访问。当协程获取资源时,计数器减1;释放资源时,计数器加1。若计数器为0,则后续协程需等待。在Go中,通常用channel的缓冲区大小模拟计数器,通过select实现超时控制。
以下是一个简单的信号量实现,使用带缓冲的channel:
type Semaphore struct {
sem chan struct{}
}
func NewSemaphore(max int) *Semaphore {
return &Semaphore{
sem: make(chan struct{}, max),
}
}
func (s *Semaphore) Acquire() {
s.sem <- struct{}{}
}
func (s *Semaphore) Release() {
<-s.sem
}
这种实现虽然简单,但缺乏超时机制,可能导致协程永久阻塞。
通过结合context.Context和select语句,我们可以为信号量增加超时控制。以下是改进后的实现:
func (s *Semaphore) AcquireWithTimeout(ctx context.Context) error {
select {
case s.sem <- struct{}{}:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
调用方可以通过设置context.WithTimeout指定超时时间:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := sem.AcquireWithTimeout(ctx); err != nil {
log.Println("Acquire failed:", err)
return
}
以下是一个完整的带超时信号量实现,包含资源释放和错误处理:
package main
import (
"context"
"log"
"time"
)
type Semaphore struct {
sem chan struct{}
}
func NewSemaphore(max int) *Semaphore {
return &Semaphore{
sem: make(chan struct{}, max),
}
}
func (s *Semaphore) AcquireWithTimeout(ctx context.Context) error {
select {
case s.sem <- struct{}{}:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (s *Semaphore) Release() {
<-s.sem
}
func main() {
sem := NewSemaphore(3) // 允许3个并发
for i := 0; i < 5; i++ {
go func(id int) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
if err := sem.AcquireWithTimeout(ctx); err != nil {
log.Printf("Goroutine %d failed: %v", id, err)
return
}
defer sem.Release()
log.Printf("Goroutine %d acquired resource", id)
time.Sleep(2 * time.Second) // 模拟耗时操作
}(i)
}
time.Sleep(5 * time.Second) // 等待所有协程完成
}
Release:使用defer确保资源释放,防止泄漏。len(s.sem)实时查看当前占用数,辅助调试。通过这种方式,开发者可以轻松实现高可靠的并发控制,兼顾效率与安全性。