目录
1、示例
有时候在Go代码中可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态)。类比现实生活中的例子有十字路口被各个方向的的汽车竞争;还有火车上的卫生间被车厢里的人竞争。
package main
import (
"fmt"
"sync"
)
var x int64
var wg sync.WaitGroup
func add() {
for i := 0; i < 400000; i++ {
x = x + 1
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
上面的代码中我们开启了两个goroutine去累加变量x的值,这两个goroutine在访问和修改x变量的时候就会存在数据竞争,导致最后的结果与期待的不符。
2、独占锁/互斥锁(Mutex Lock)
在并发编程中,"并发安全"是指程序在多个并发执行的线程或协程中,仍然能够正确地执行并产生正确结果的特性。并发安全是一个重要的概念,因为并发程序可能会面临多个线程同时访问共享资源的情况,这可能导致数据竞争和不确定的行为。为了确保并发程序的正确性,我们需要采取适当的措施来处理并发安全问题。
其中一个常用的处理并发安全问题的方法是使用锁机制。锁(Lock)是一种同步工具,用于保护共享资源,以便在同一时间只有一个线程可以访问该资源。锁可分为独占锁和共享锁。
扫描二维码关注公众号,回复:
16060784 查看本文章
- 独占锁/互斥锁(Mutex Lock):独占锁是最常用的锁类型,也称为互斥锁(Mutex Lock),在同一时间只允许一个线程获取锁,其他线程必须等待锁的释放。通过互斥锁,可以确保同一时间只有一个线程访问被保护的代码块或共享资源。
package main
import (
"fmt"
"sync"
)
var x int64
var wg sync.WaitGroup
var mutex sync.Mutex
func add() {
for i := 0; i < 400000; i++ {
mutex.Lock()
x = x + 1
mutex.Unlock()
}
wg.Done()
}
func main() {
wg.Add(2)
go add()
go add()
wg.Wait()
fmt.Println(x)
}
在上述示例中,sync.Mutex
是Go语言内置的互斥锁类型。通过调用Lock
方法获取锁,在锁保护的代码块中进行共享资源的访问或修改,最后调用Unlock
方法释放锁。
3、读写锁(RWMutex)
- 读写锁(RWMutex):读写锁是一种特殊的锁,它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。读写锁在读多写少的场景中可以提供更高的并发性能。
package main
import (
"fmt"
"sync"
"time"
)
var (
x int64
wg sync.WaitGroup
rwlock sync.RWMutex
)
func write() {
// lock.Lock() // 加互斥锁
rwlock.Lock() // 加写锁
x = x + 1
time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒
rwlock.Unlock() // 解写锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func read() {
// lock.Lock() // 加互斥锁
rwlock.RLock() // 加读锁
time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒
rwlock.RUnlock() // 解读锁
// lock.Unlock() // 解互斥锁
wg.Done()
}
func main() {
start := time.Now()
for i := 0; i < 10; i++ {
wg.Add(1)
go write()
}
for i := 0; i < 1000; i++ {
wg.Add(1)
go read()
}
wg.Wait()
end := time.Now()
fmt.Println(end.Sub(start))
}
使用锁机制可以有效地解决并发安全问题,确保共享资源的正确访问和修改。然而,过度使用锁也可能导致性能下降和死锁等问题,因此在使用锁时需要谨慎,并根据具体应用场景选择适当的锁类型和策略。