点击上方蓝色“Golang来啦”关注我哟
加个“星标”,天天 15 分钟,掌握 Go 语言
via:
https://mipsmonsta.medium.com/golang-using-buffered-channel-like-a-mutex-9c7c80ec5c27
作者:Mipsmonsta
大家好,我是四哥。
原文如下:
作为 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!
推荐阅读:
使用context、WaitGroup优雅处理goroutine
2022 必读 Go 图书推荐
对了,看完文章,记得点击下方的卡片。关注我哦~ 👇👇👇
这是持续翻译的第 27/100 篇优质文章。
如果你有想交流的话题,欢迎留言。
如果您的朋友也在学习 Go 语言,相信这篇文章对 TA 有帮助,欢迎转发分享给 TA,非常感谢!