Go 使用sync.Mutex或sync.RWMutex进行并发安全访问

一、使用sync.Mutex或sync.RWMutex进行并发安全访问

当多个协程并发访问共享数据时,需要确保数据访问的安全性。sync.Mutex和sync.RWMutex提供了互斥锁和读写锁,用于在访问共享资源之前进行锁定,以避免数据竞争。

sync.Mutex

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	var mutex sync.Mutex
	var data int
	var num = 20000

	wg.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 包含两个主要的方法:

  1. RLock():用于读锁,多个goroutine可以同时调用此方法获取读锁。

  2. RUnlock():用于释放读锁。

  3. Lock():用于写锁,当有goroutine调用此方法时,其他goroutine的读锁和写锁请求都会被阻塞,直到写锁被释放。

  4. Unlock():用于释放写锁。

package main

import (
	"fmt"
	"strconv"
	"sync"
	"time"
)

func main() {
	fmt.Println("通过使用 sync.RWMutex,我们可以确保即使在高并发的情况下,对值的读写也是安全的")
	fmt.Println("Lock与RLock配合,使用使得写入的时候阻塞不给读取,等写完了再读")

	var (
		mu    sync.RWMutex // 创建一个RWMutex实例
		count int
	)

	var wg sync.WaitGroup
	wg.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博客

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值