卓越飞翔博客卓越飞翔博客

卓越飞翔 - 您值得收藏的技术分享站
技术文章16333本站已运行3317

学到了!将缓冲 channel 当做 Mutex 来使用


作为 Go 官方包的一部分,sync 包有下面这段声明:

sync 包提供了基本的同步原语,例如互斥锁。除了 Once 和 WaitGroup 类型之外,大多数其他类型都是为底层函数库准备的。通过 channel 和通信更好地完成更高级别的同步.

在你能找到的关于允许并发访问的绝大多数例子中,很多都是使用互斥锁来解决问题。然而,几乎很少有示例给我们展示如何使用 channel 提供同步机制。所以,这篇文章我们就来讨论下。

互斥锁的特性

为了使互斥锁起作用,访问共享变量时需要加锁,操作完成之后需要解锁。相同的互斥锁不允许多次加锁,以免出现竞态条件。

无缓冲 channel 及其不足之处

如果没有接收方,发送者将会阻塞;相同地,如果没有发送方,接收者将会阻塞。基于这种特性,所以我们不能将无缓冲的 channel 作为锁来使用。

我们来看看缓冲 channel 是否可以当做互斥锁来使用。

缓冲为 1 的 channel 的特性及其可取之处

缓冲大小为 1 的 channel 具有如下的特性:如果缓冲满了,发送时将会阻塞;如果缓存腾空,发送时就会解除阻塞。

显然,这种 channel 的阻塞特性是可取的,与互斥锁的特性做个对比:

缓冲满时 <--> 上锁

缓冲腾空 <--> 解锁

我们一起通过代码演示下这种特性。

演示:如何将缓冲 channel 作为 “锁” 来使用

我们假设有一列名字需要写入到文件中,每个名字需要连续写 1000 次,且不允许不同名字出现交叉情况。

package main
import (
 "errors"
 "fmt"
 "os"
 "sync"
)
func main() {
 file, err := os.Create("record.txt")

 defer func() {
  if err := recover(); err != nil {
   fmt.Printf("Error encounter: %w", err)
  }
  file.Close()
 }()
 if err != nil {
  panic(errors.New("Cannot create/open file"))
 }
 ss := []string{ //string slice literals
  "James",
  "Avery",
  "Peter",
  "John",
  "Beau",
 }
 chanLock := make(chan int, 1) //1
 var wg sync.WaitGroup
 for _, str := range ss { //2
  wg.Add(1) //amended thanks to response from Wang
  //Sheng
  go func(aString string) {

   chanLock <- 1 //3
   for i := 0; i < 1000; i++ {
    file.WriteString(aString + "n")
   }
   <-chanLock //4
   wg.Done() //5
  }(str) //pass by value
 }
 wg.Wait()
}

上面的代码中,//1 我们创建了缓冲为 1 的 channel。//2 我们创建了个数与名字数量相同的 goroutine。//3 相当于加锁,//4 相当于解锁,这样就实现了多 goroutine 之间同步地将名字写入到 record.txt 文件,但每次只会有一个 goroutine 操作该文件。

需要注意的是,我们通过 WaitGroup 来保证子 goroutine 完成任务之前,主协程不会退出。

希望这篇文章对你有帮助,enjoy coding!

卓越飞翔博客
上一篇: PHP 错误处理器:如何编写可靠的错误处理代码
下一篇: PHP表单处理:表单数据统计与分析
留言与评论(共有 0 条评论)
   
验证码:
隐藏边栏