951 words
5 minutes
Go语言教程:使用读写锁实现并发安全的计数器
在本教程中,我们将通过一个简单的计数器示例,学习如何使用Go语言中的读写锁(sync.RWMutex)来实现并发安全的操作。我们将详细解释代码的每一部分,并提供一些额外的示例来帮助你更好地理解。
代码结构
首先,让我们看一下完整的代码:
package main
import ( "log" "sync" "time")
// counter 代表计数器。type counter struct { num uint // 计数。 mu sync.RWMutex // 读写锁。}
// number 会返回当前的计数。func (c *counter) number() uint { c.mu.RLock() defer c.mu.RUnlock() return c.num}
// add 会增加计数器的值,并会返回增加后的计数。func (c *counter) add(increment uint) uint { c.mu.Lock() defer c.mu.Unlock() c.num += increment return c.num}
func main() { c := counter{} count(&c) redundantUnlock()}
func count(c *counter) { // sign 用于传递演示完成的信号。 sign := make(chan struct{}, 3) go func() { // 用于增加计数。 defer func() { sign <- struct{}{} }() for i := 1; i <= 10; i++ { time.Sleep(time.Millisecond * 500) c.add(1) } }() go func() { defer func() { sign <- struct{}{} }() for j := 1; j <= 20; j++ { time.Sleep(time.Millisecond * 200) log.Printf("The number in counter: %d [%d-%d]", c.number(), 1, j) } }() go func() { defer func() { sign <- struct{}{} }() for k := 1; k <= 20; k++ { time.Sleep(time.Millisecond * 300) log.Printf("The number in counter: %d [%d-%d]", c.number(), 2, k) } }() <-sign <-sign <-sign}
func redundantUnlock() { var rwMu sync.RWMutex
// 示例1。 //rwMu.Unlock() // 这里会引发panic。
// 示例2。 //rwMu.RUnlock() // 这里会引发panic。
// 示例3。 rwMu.RLock() //rwMu.Unlock() // 这里会引发panic。 rwMu.RUnlock()
// 示例4。 rwMu.Lock() //rwMu.RUnlock() // 这里会引发panic。 rwMu.Unlock()}代码解析
1. 定义计数器结构体
type counter struct { num uint // 计数。 mu sync.RWMutex // 读写锁。}counter结构体包含一个无符号整数num用于存储计数值,以及一个读写锁mu用于保护对num的并发访问。
2. 获取当前计数值的方法
func (c *counter) number() uint { c.mu.RLock() defer c.mu.RUnlock() return c.num}number方法使用读锁(RLock)来保护对num的读取操作,并在读取完成后释放读锁(RUnlock)。
3. 增加计数值的方法
func (c *counter) add(increment uint) uint { c.mu.Lock() defer c.mu.Unlock() c.num += increment return c.num}add方法使用写锁(Lock)来保护对num的写操作,并在写入完成后释放写锁(Unlock)。
4. 主函数
func main() { c := counter{} count(&c) redundantUnlock()}在main函数中,我们创建一个counter实例,并调用count函数和redundantUnlock函数。
5. 并发计数函数
func count(c *counter) { // sign 用于传递演示完成的信号。 sign := make(chan struct{}, 3) go func() { // 用于增加计数。 defer func() { sign <- struct{}{} }() for i := 1; i <= 10; i++ { time.Sleep(time.Millisecond * 500) c.add(1) } }() go func() { defer func() { sign <- struct{}{} }() for j := 1; j <= 20; j++ { time.Sleep(time.Millisecond * 200) log.Printf("The number in counter: %d [%d-%d]", c.number(), 1, j) } }() go func() { defer func() { sign <- struct{}{} }() for k := 1; k <= 20; k++ { time.Sleep(time.Millisecond * 300) log.Printf("The number in counter: %d [%d-%d]", c.number(), 2, k) } }() <-sign <-sign <-sign}count函数启动了三个并发的goroutine:
- 第一个goroutine每500毫秒增加一次计数。
- 第二个和第三个goroutine分别每200毫秒和300毫秒读取并打印当前计数值。
sign通道用于等待所有goroutine完成。
6. 错误解锁示例
func redundantUnlock() { var rwMu sync.RWMutex
// 示例1。 //rwMu.Unlock() // 这里会引发panic。
// 示例2。 //rwMu.RUnlock() // 这里会引发panic。
// 示例3。 rwMu.RLock() //rwMu.Unlock() // 这里会引发panic。 rwMu.RUnlock()
// 示例4。 rwMu.Lock() //rwMu.RUnlock() // 这里会引发panic。 rwMu.Unlock()}redundantUnlock函数展示了几种错误使用读写锁的方法,这些错误会导致程序崩溃(panic)。正确的使用方式是成对调用RLock/RUnlock和Lock/Unlock。
总结
通过这个示例,我们学习了如何使用Go语言中的读写锁来实现并发安全的计数器。我们还展示了几种错误使用锁的方法,以帮助你避免常见的陷阱。
Share
If this article helped you, please share it with others!
Go语言教程:使用读写锁实现并发安全的计数器
https://blog.ithuo.net/posts/concurrent-safe-counter-using-rwmutex-in-golang/ Last updated on 2022-05-25,1325 days ago
Some content may be outdated