Go并发编程--通过channel来实现信号量原语

概述

信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。对于一个信号量来说,基本的操作有三个:

  • 创建一个信号量
    创建一个信号量会给该信号量一个初始值,对于二元信号量来说它是1。
  • 等待(wait)一个信号量
    该操作会测试这个信号量的值,如果其值小于或等于0,就等待(阻塞),一旦其值变为大于0就将它减1。
    该操作也被称为P操作,或递减(down),或上锁(lock)。该操作必须是原子的。

  • 发送(signal)一个信号量
    该操作将信号量加1。若有一些进程阻塞着等待该信号量的值变为大于0,其中一个进程/线程被唤醒。同样,该操作必须是原子的。
    该操作也被称为V操作,或递增(up),或解锁(unlock)发送信号(singal)。

通过channel来实现信号量原语

信号量是非常常用的同步机制,可以用来实现互斥锁,多个资源的互斥访问,解决reader-writer的问题。在Go中没有实现信号量,但可以通过channel来实现。通过channel来实现,基于以下事实:

  • buffered channel的容量大小是我们希望同步的资源数
  • channel目前的长度,是目前的可用资源数
  • channel的容量减去channel目前的长度,是释放的资源数

我们并不关心channel中的值,仅关心channel的长度。因此,我们可以先定义一个长度为0的空channel。

type Empty interface {}
type semaphore chan Empty

现在我们创建一个有N个元素的channel:

sem = make(semaphore, N)

同步原语操作设计如下:

// acquire n resources
func (s semaphore) P(n int) {
    e := new(Empty)
    for i := 0; i < n; i++ {
        s <- e
    }
}

// release n resources
func (s semaphore) V(n int) {
    for i := 0; i < n; i++ {
        <-s
    }
}

实现P操作时,其实是不断的往channel中放入一个数据,当channel满时,其他协程就不能再往channel放数据了,而只能阻塞,直到有协程释放一个资源,也就是执行V操作。
而V操作其实就是从channel中取出资源。

总结

基于channel的特性,我们实现了信号量的基本原语,通过该原语我们可以设计协程间同步的各种机制,比如:互斥锁,信号等等。
如何通过信号量原语来实现这些同步机制,请看下回分解。

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81049724