Go互斥锁浅谈

355 阅读4分钟

「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

生命不息,学习不止

题外话

清晨第一缕阳光打在我的脸上,我从我席梦思床垫上爬起,拿起我的iPhone56,定了一份加了三斤可食用金粉的麻辣烫,哎,又是乏味的一个早上…… 人生就是这么简简单单 话说今天的风是真的大,是真冷啊

在这里插入图片描述 废话不多说,上货 在这里插入图片描述

互斥锁

在并发的情况下,多个线程或协程同时其修改一个变量,使用锁能保证在某一时间内,只有一个协程或线程修改这一变量; Go语言中 sync 包里提供了互斥锁 Mutex 和读写锁 RWMutex 用于处理并发过程中可能出现同时两个或多个协程(或线程)读或写同一个变量的情况,我们先来说说Mutex ; 举个例子 在这里插入图片描述

package main
import (
    "fmt"
    "time"
)
func main() {
    var count = 0
    for i := 0; i < 10; i++ {
        go func(idx int) {
            count += 1
            fmt.Println(count)
        }(i)
    }
    time.Sleep(time.Second)
}

我们使用 go 函数名( 参数列表 )方式开启一个新的运行期线程,goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。 运行结果: 在这里插入图片描述 可以发现count并没有稳定的+1

协程的执行顺序大致如下所示: 从寄存器读取 count 的值; 然后做加法运算; 最后写到寄存器。

按照上面的顺序,假如有一个协程取得 count 的值为 3,然后执行加法运算,此时又有一个协程对 count 进行取值,得到的值同样是 count ,最终两个协程的返回结果是相同的。

而锁的概念就是,当一个协程正在处理 count 时将 count 锁定,其它协程需要等待该协程处理完成并将 count 解锁后才能再进行操作,也就是说同时处理 count 的协程只能有一个,从而避免上面示例中的情况出现。

互斥锁 Mutex

package main
import (
    "fmt"
    "sync"
    "time"
)
func main() {
    //声明一个互斥锁
    var lock sync.Mutex
    var count = 0
    for i := 0; i < 10; i++ {
        go func(idx int) {
            //在赋值前上锁
            lock.Lock()
            count += 1
            //赋值后解锁
            lock.Unlock()
            fmt.Println(count)
        }(i)
    }
     // 确保所有协程执行完
    time.Sleep(time.Second)
}

运行结果 在这里插入图片描述 需要注意的是一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁

读写锁 RWMutex

读写锁有如下四个方法: **写操作的锁定和解锁分别是func (*RWMutex) Lock和func (*RWMutex) Unlock; 读操作的锁定和解锁分别是func (RWMutex) Rlock和func (RWMutex) RUnlock。

读写锁的区别在于: 当有一个 goroutine 获得写锁定,其它无论是读锁定还是写锁定都将阻塞直到写解锁; 当有一个 goroutine 获得读锁定,其它读锁定仍然可以继续; 当有一个或任意多个读锁定,写锁定将等待所有读锁定解锁之后才能够进行写锁定。

所以说这里的读锁定(RLock)目的其实是告诉写锁定,有很多协程或者进程正在读取数据,写操作需要等它们读(读解锁)完才能进行写(写锁定)。

我们可以将其总结为如下三条: 同时只能有一个 goroutine 能够获得写锁定; 同时可以有任意多个 gorouinte 获得读锁定; 同时只能存在写锁定或读锁定(读和写互斥)。

// 调用事件
package main
import (
    "fmt"
    "math/rand"
    "sync"
)
var count int
var rw sync.RWMutex
func main() {
    ch := make(chan struct{}, 10)
    for i := 0; i < 5; i++ {
        go read(i, ch)
    }
    for i := 0; i < 5; i++ {
        go write(i, ch)
    }
    for i := 0; i < 10; i++ {
        <-ch
    }
}
func read(n int, ch chan struct{}) {
    rw.RLock()
    fmt.Printf("goroutine %d 进入读操作...\n", n)
    v := count
    fmt.Printf("goroutine %d 读取结束,值为:%d\n", n, v)
    rw.RUnlock()
    ch <- struct{}{}
}
func write(n int, ch chan struct{}) {
    rw.Lock()
    fmt.Printf("goroutine %d 进入写操作...\n", n)
    v := rand.Intn(1000)
    count = v
    fmt.Printf("goroutine %d 写入结束,新值为:%d\n", n, v)
    rw.Unlock()
    ch <- struct{}{}
}

在这里插入图片描述

你以为结束了

锁并不只有这些,底层原理也没有细说,锁的升级等等,等我再开一篇详细说说 在这里插入图片描述 大家看完发现有什么错误,写在下面吧!跟我黑虎阿福比划比划! 在这里插入图片描述