一、使用sync.Mutex或sync.RWMutex进行并发安全访问
当多个协程并发访问共享数据时,需要确保数据访问的安全性。sync.Mutex和sync.RWMutex提供了互斥锁和读写锁,用于在访问共享资源之前进行锁定,以避免数据竞争。
sync.Mutex
package mainimport ("fmt""sync"
)func main() {var wg sync.WaitGroupvar mutex sync.Mutexvar data intvar num = 20000wg.Add(2)go func() {defer wg.Done()for i := 0; i < num; i++ {// 写操作,使用互斥锁保护数据mutex.Lock() //上了锁是还可以读的data++mutex.Unlock()}}()go func() {defer wg.Done()for i := 0; i < num; i++ {// 写操作,使用互斥锁保护数据mutex.Lock() //上了锁是还可以读的data++mutex.Unlock()}}()wg.Wait()fmt.Println(data)fmt.Println("在并发的情况下,当num数大到一定程度,如果不加锁就会出现最终运算结果不正确的情况")
}
ync.RWMutex
sync.RWMutex是Go语言提供的一个基础同步原语,它是Reader/Writer Mutual Exclusion Lock的缩写,通常被称为"读写锁"。读写锁允许多个读锁同时拥有者,但在任何时间点只允许一个写锁拥有者,或者没有锁拥有者。
sync.RWMutex提供了以下方法:
type RWMutex
// 获取写锁,有读锁或者写锁被其他goroutine使用则阻塞等待
func (rw *RWMutex) Lock()
// 尝试获取写锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryLock() bool
// 释放写锁
func (rw *RWMutex) Unlock()
// 获取读锁,
func (rw *RWMutex) RLock()
// 尝试获取读锁,获取到则返回true,没有获取到则为false
func (rw *RWMutex) TryRLock() bool
// 释放读锁
func (rw *RWMutex) RUnlock()// 返回Locker
func (rw *RWMutex) RLocker() Locker
注意:
使用RWMutex的时候,一旦调用了Lock方法,就不能再把该锁复制到其他地方使用,否则可能会出现各种问题。这是由于锁的状态(被哪个协程持有,是否已经被锁定等)是存储在RWMutex的结构体中,如果复制了RWMutex,那么复制后的RWMutex就会有一个全新的状态,锁的行为就会变得不可预测。
RWMutex和Mutex一样,一旦有了Lock调用就不能到处copy了,否则出现各种问题。
sync.RWMutex
包含两个主要的方法:
-
RLock()
:用于读锁,多个goroutine可以同时调用此方法获取读锁。 -
RUnlock()
:用于释放读锁。 -
Lock()
:用于写锁,当有goroutine调用此方法时,其他goroutine的读锁和写锁请求都会被阻塞,直到写锁被释放。 -
Unlock()
:用于释放写锁。
package mainimport ("fmt""strconv""sync""time"
)func main() {fmt.Println("通过使用 sync.RWMutex,我们可以确保即使在高并发的情况下,对值的读写也是安全的")fmt.Println("Lock与RLock配合,使用使得写入的时候阻塞不给读取,等写完了再读")var (mu sync.RWMutex // 创建一个RWMutex实例count int)var wg sync.WaitGroupwg.Add(3)go func() {defer wg.Done()// 启动一些goroutine来增加count的值for j := 0; j < 100; j++ {mu.Lock() // 获取写锁fmt.Println("获取写锁Lock--其他goroutine的读锁和写锁请求都会被阻塞,直到写锁被释放")time.Sleep(3000 * time.Millisecond)count++fmt.Println()fmt.Println("---------------写锁被释放----------")mu.Unlock() // 释放写锁}}()time.Sleep(time.Millisecond)go func() {defer wg.Done()// 启动一些goroutine来读取count的值for j := 0; j < 100; j++ {mu.RLock() // 获取读锁fmt.Println("获取读锁RLock--多个goroutine可以同时调用此方法获取读锁count=" + strconv.Itoa(count))mu.RUnlock() // 释放读锁}}()go func() {defer wg.Done()// 启动一些goroutine来读取count的值for j := 0; j < 100; j++ {mu.RLock() // 获取读锁fmt.Println("获取读锁RLock--多个goroutine可以同时调用此方法获取读锁count=" + strconv.Itoa(count))mu.RUnlock() // 释放读锁}}()wg.Wait()fmt.Println("Final count:", count)
}
更详细可参考:逐步学习Go-sync.RWMutex(读写锁)-深入理解与实战-CSDN博客