信号量(Semaphore)是进程间通信的方式之一,通过P操作,信号量减1;V操作信号量加1。如果信号量为零,阻塞操作的进程。关于这部分不展开讨论了。

在并发编程上,很多语言都支持信号量。例如Java的java.util.concurrent.Semaphore,Python的threading.Semaphore,但Go就没有信号量,在标准库中有一类似的对象sync.WaitGroup,但它并不是信号量,顾名它通常用来等待a group of goroutine。那么如果在Go中实现Semaphore呢?

根据Go内置的channel特性可以实现Semaphore。利用channel拥有阻塞功能和缓冲功能,完成Semaphore的acquire功能和release功能。具体实现如下:

信号量的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package semaphore

type Semaphore chan struct{}

func (s Semaphore) acquire() {
s <- struct{}{}
}

func (s Semaphore) release() {
<-s
}

func NewSemaphore(value uint) Semaphore {
// 原则上,value值不能为负数
return make(Semaphore, value)
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
"time"
)

func main() {
s := NewSemaphore(2)
go func() {
s.acquire()
defer s.release()
time.Sleep(time.Second)
fmt.Println("goroutine 1")
}()

go func() {
s.acquire()
defer s.release()
time.Sleep(2 * time.Second)
fmt.Println("goroutine 2")
}()

fmt.Println("wait here")
s.acquire()
fmt.Println("acquire semaphore success")
time.Sleep(time.Second)
}

上述实现的原理是:创建一个有缓冲的channel,acquire就相当于往channel添加信息,信号量减1(P操作)。release就相当于从channel取出信息,相当于信号量加1(V操作)。使用struct{}{}是因为它指向runtime.Zerobase不占据空间。

另外,还有一种思路,sync.Mutex是信号量的特例,我们可以通过多个sync.Mutex还原信号量。

来自官方的扩展包

Go语言的官方扩展包也包含信号量,在实际开发最好使用它。

安装:

1
$go get golang.org/x/sync/semaphore

见官方文档semaphore