互斥锁与读写锁使用

概述

sync.Mutex和sync.RWMutex是Go语言底层基础对象,用于构建多个goroutine间的同步逻辑,当多个协程需要对共享数据读写时用到。具体实现极为简洁,性能也有保证。

使用场景

举例:1.多个协程操作同一个文件 2.生产者消费者模型 
    具体实例我就以最简单的打印方式说明

代码

互斥锁
func print(t *testing.T, i int, wg *sync.WaitGroup, mutex *sync.Mutex) {
    // mutex.Lock()
    t.Logf("routine i=%d start!", i)
    time.Sleep(time.Millisecond * 10)
    t.Logf("routine i=%d end!", i)
    wg.Done()
    // mutex.Unlock()
}

func TestSync(t *testing.T) {
    runtime.GOMAXPROCS(runtime.NumCPU())
    var wg = new(sync.WaitGroup)
    var mutex = &sync.Mutex{}

    for i := 0; i < 2; i++ {
        wg.Add(1)
        go print(t, i, wg, mutex)
    }
    wg.Wait()
}

这是go的测试用例,并没有用main函数来执行,go的源码里面很多都是以这种形式写的测试用例。
这里需要说明一下,go的协程需要达到真正的并发,需要加上runtime.GOMAXPROCS(runtime.NumCPU()),

print函数里面mutex.Lock()注释了,打印的结果是
        sync_test.go:12: routine i=0 start!
        sync_test.go:12: routine i=1 start!
        sync_test.go:14: routine i=1 end!
        sync_test.go:14: routine i=0 end!
协程0(暂且这么称呼)先执行print函数,但并没有先结束,我们看到协程1是先结束的,这个程序就有并发安全性的问题。如果需要解决这个问题,达到谁先进入公共代码区域,谁就先结束,只需要把print函数里面互斥锁Lock()和Unlock()打开即可,会看到如下打印信息。
        sync_test.go:12: routine i=0 start!
        sync_test.go:14: routine i=0 end!
        sync_test.go:12: routine i=1 start!
        sync_test.go:14: routine i=1 end!
读写锁
验证结论:如果一个协程在读,其他协程不可以写,其他协程可以读。如果一个协程在写,任何协程都不可以读和写
首先验证多个读
func TestReadLock(t *testing.T) {
    lock := new(sync.RWMutex)
    go Read(lock, 1) //多个协程随便读,并不锁住
    go Read(lock, 2)
    time.Sleep(time.Second * 4)
}
func Read(lock *sync.RWMutex, i int) {
    println(i, "read start")
    lock.RLock()//读锁定
    defer lock.RUnlock()//读解锁

    println(i, "reading")
    if i == 2 {
        time.Sleep(3 * time.Second)
    } else {
        time.Sleep(1 * time.Second)
    }

    println(i, "read end")
}
打印信息如下:
1 read start
2 read start
1 reading
2 reading
1 read end
2 read end
我们可以看出,协程1在没有read完之前,协程2还是可以读的,即验证了可以有多个读

这次来验证第二个结论,有一个协程在读,另个协程能不能写呢?
func TestWriteLock(t *testing.T) {
    lock := new(sync.RWMutex)
    go Read(lock, 2)
    go Read(lock, 4)
    // time.Sleep(1 * time.Second)
    go Write(lock, 1) //如果在读,不可以写,可以读,如果在写,不可以写,不可以读

    go Write(lock, 3)
    time.Sleep(10 * time.Second)
}

func Read(lock *sync.RWMutex, i int) {
    println(i, "read start")
    lock.RLock()
    defer lock.RUnlock()

    println(i, "reading")
    if i == 2 {
        time.Sleep(3 * time.Second)
    } else {
        time.Sleep(1 * time.Second)
    }

    println(i, "read end")
}

func Write(lock *sync.RWMutex, i int) {
    println(i, "write start")
    lock.Lock()//写锁定
    defer lock.Unlock()//写解锁
    println(i, "writing")
    time.Sleep(1 * time.Second)

    println(i, "write end")
}
打印如下:
2 read start
2 reading
3 write start
1 write start
4 read start
2 read end
3 writing
3 write end
4 reading
4 read end
1 writing
1 write end
我们看到,协程2号线进入读,协程3号写开始,但并没有writing,而是等到协程2read end之后才开始writing,协程2号在读的时候,协程4号开始读,但由于协程3号是在协程2号之后进入write start,所以协程2read end后是协程3号writing,直到结束,协程4号开始reading

接下来验证最后一个结论
我们把TestWriteLock函数里面的Write协程放到前面
func TestWriteLock(t *testing.T) {
    lock := new(sync.RWMutex)
    go Write(lock, 1) //如果在读,不可以写,可以读,如果在写,不可以写,不可以读
    go Write(lock, 3)
    go Read(lock, 2)
    go Read(lock, 4)
    time.Sleep(10 * time.Second)
}

打印信息如下:
1 write start
1 writing
4 read start
2 read start
3 write start
1 write end
2 reading
4 reading
4 read end
2 read end
3 writing
3 write end

好了,我们已经验证了我们最开始的结论,是不是很简单。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
互斥锁读写锁都是多线程编程中用于控制并发访问的工具,但它们的使用场景和实现方式有所不同。 互斥锁是一种排他锁,它在同一时刻只允许一个线程访问共享资源,其他线程必须等待当前线程释放锁后才能访问。互斥锁通常用于保护对数据结构的访问,以保证数据的一致性和正确性。例如,在多线程环境下,如果多个线程需要修改同一个变量,就需要使用互斥锁来保证同一时刻只有一个线程能够修改该变量。 读写锁是一种特殊的锁,它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。当有多个线程需要读取共享资源时,读写锁可以提高并发性能,因为读操作不会修改共享资源,多个线程之间不会产生竞争。但是,如果有一个线程需要修改共享资源,就必须独占锁,其他线程都必须等待该线程释放锁后才能进行读或写操作。读写锁通常用于读多写少的场景,例如,在一个多线程的服务器程序中,有多个客户端同时访问同一个数据库,但是写操作比较少,大多数操作都是读取数据。 举个例子,假设有一个银行账户,多个线程需要对该账户进行读取或修改操作。如果使用互斥锁,每次只能有一个线程访问账户,其他线程必须等待,这会导致并发性能较差。如果使用读写锁,多个线程可以同时读取账户余额,但只能有一个线程进行修改操作,这样就可以提高并发性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值