作为 Go 官方包的一部分,sync 包有下面这段声明:
sync 包提供了基本的同步原语,例如互斥锁。除了 Once 和 WaitGroup 类型之外,大多数其他类型都是为底层函数库准备的。通过 channel 和通信更好地完成更高级别的同步.
在你能找到的关于允许并发访问的绝大多数例子中,很多都是使用互斥锁来解决问题。然而,几乎很少有示例给我们展示如何使用 channel 提供同步机制。所以,这篇文章我们就来讨论下。
为了使互斥锁起作用,访问共享变量时需要加锁,操作完成之后需要解锁。相同的互斥锁不允许多次加锁,以免出现竞态条件。
如果没有接收方,发送者将会阻塞;相同地,如果没有发送方,接收者将会阻塞。基于这种特性,所以我们不能将无缓冲的 channel 作为锁来使用。
我们来看看缓冲 channel 是否可以当做互斥锁来使用。
缓冲大小为 1 的 channel 具有如下的特性:如果缓冲满了,发送时将会阻塞;如果缓存腾空,发送时就会解除阻塞。
显然,这种 channel 的阻塞特性是可取的,与互斥锁的特性做个对比:
缓冲满时 71fb34173e4ee87dab1f85dc1c283a44 上锁
缓冲腾空 71fb34173e4ee87dab1f85dc1c283a44 解锁
我们一起通过代码演示下这种特性。
我们假设有一列名字需要写入到文件中,每个名字需要连续写 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!
以上是学到了!将缓冲 channel 当做 Mutex 来使用的详细内容。更多信息请关注PHP中文网其他相关文章!