信号量工具类
信号量是操作系统中一个经典的进程同步机制,它可以通过阻塞和唤醒进程,来实现进程间的同步、并发数量的限制、互斥资源的访问等等。
go语言中的chan也具有阻塞和唤醒协程的功能,所以我就使用chan实现了一个简易的信号量机制。我所实现的主要是信号量的P、V操作:
- P操作是申请并发资源,如果有资源则继续执行,如果没有资源则阻塞。
- V操作是释放并发资源,如果资源池已满则阻塞,否则释放并发资源,并唤醒一个被阻塞的协程。
其实阻塞和唤醒这个功能不需要我来写,chan自带这个功能,我只是基于chan做进一步的封装,组装一些更便于使用的接口罢了。
具体代码如下:
/util/semaphore.go
package util
import "errors"
type semaphore struct {
c chan int8
initCount int
maxCount int
}
// NewSemaphore initCount是信号量的初始值
// P操作请求资源,当信号量的值小于等于0时,再执行P操作会阻塞
// V操作释放资源,当信号量的值等于maxCount时,再执行V操作会阻塞
func NewSemaphore(initCount int, maxCount int) (*semaphore, error) {
if maxCount <= 0 || initCount < 0 || initCount > maxCount {
return nil, errors.New("参数错误")
}
s := &semaphore{
c: make(chan int8, maxCount),
}
for i := 1; i <= initCount; i++ {
s.c <- 1
}
return s, nil
}
// P P操作
func (s *semaphore) P() {
_ = <-s.c
}
// V V操作
func (s *semaphore) V() {
s.c <- 1
}
// NowCount 获得当前时刻信号量的值
func (s *semaphore) NowCount() int {
return len(s.c)
}
// MaxCount 获得信号量的maxCount
func (s *semaphore) MaxCount() int {
return s.maxCount
}
// InitCount 获得信号量的initCount
func (s *semaphore) InitCount() int {
return s.initCount
}
// Close 关闭信号量
func (s *semaphore) Close() {
close(s.c)
}
测试代码
在测试代码中我利用上述功能,模拟了并发数量限制的效果。
具体来说就是有n个协程,每个协程中会在控制台输出一个数字,我使用信号量对并发执行数量进行限制,使最多有nLimit个协程同时执行。
具体代码如下:
/test/01_test.go
package test
import (
"fmt"
"smCrawler/util"
"testing"
"time"
)
// 通过限制输出数字的频率,来模拟n个进程中最多同时执行nLimit个进程的并发限制
func Test001(t *testing.T) {
// 数的总个数和每秒钟最多输出的数的个数
n, nLimit := 30, 5
// 限制数字输出频率的信号量
numS, _ := util.NewSemaphore(nLimit, nLimit)
// 程序运行结束的信号量
overS, _ := util.NewSemaphore(1, 1)
// 计数器,以输出数字的个数
counter := 0
overS.P()
for i := 1; i <= n; i++ {
num := i //会有闭包现象,在go func执行的时候,此作用域的i值已经递增过了,所以要用中间变量读取一下i值
go func() {
numS.P()
defer numS.V()
fmt.Println(num)
counter++
if counter == n {
overS.V()
}
time.Sleep(time.Second)
}()
}
overS.P()
overS.Close()
numS.Close()
fmt.Println("over.......")
}
运行结果如下图所示,当nLimit为5的时候,每次控制台输出5个数字