文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么使用Go语言sync包与锁实现限制线程对变量的访问

2023-07-06 02:29

关注

本篇内容主要讲解“怎么使用Go语言sync包与锁实现限制线程对变量的访问”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么使用Go语言sync包与锁实现限制线程对变量的访问”吧!

Go语言中 sync 包里提供了互斥锁 Mutex 和读写锁 RWMutex 用于处理并发过程中可能出现同时两个或多个协程(或线程)读或写同一个变量的情况。

为什么需要锁

锁是 sync 包中的核心,它主要有两个方法,分别是加锁(Lock)和解锁(Unlock)。

在并发的情况下,多个线程或协程同时其修改一个变量,使用锁能保证在某一时间内,只有一个协程或线程修改这一变量。

不使用锁时,在并发的情况下可能无法得到想要的结果,如下所示:

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

从理论上来说,上面的程序会将 a 的值依次递增输出,然而实际结果却是下面这样子的。

537
995
996
997
538
999
1000

通过运行结果可以看出 a 的值并不是按顺序递增输出的,这是为什么呢?

协程的执行顺序大致如下所示:

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

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

互斥锁 Mutex

上面的示例中出现的问题怎么解决呢?加一个互斥锁 Mutex 就可以了。那什么是互斥锁呢 ?互斥锁中其有两个方法可以调用,如下所示:

func (m *Mutex) Lock()func (m *Mutex) Unlock()

将上面的代码略作修改,如下所示:

package mainimport (    "fmt"    "sync"    "time")func main() {    var a = 0    var lock sync.Mutex    for i := 0; i < 1000; i++ {        go func(idx int) {            lock.Lock()            defer lock.Unlock()            a += 1            fmt.Printf("goroutine %d, a=%d\n", idx, a)        }(i)    }    // 等待 1s 结束主程序    // 确保所有协程执行完    time.Sleep(time.Second)}

运行结果如下:

goroutine 995, a=996
goroutine 996, a=997
goroutine 997, a=998
goroutine 998, a=999
goroutine 999, a=1000

需要注意的是一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁(重新争抢对互斥锁的锁定),示例代码如下:

package mainimport (    "fmt"    "sync"    "time")func main() {    ch := make(chan struct{}, 2)    var l sync.Mutex    go func() {        l.Lock()        defer l.Unlock()        fmt.Println("goroutine1: 我会锁定大概 2s")        time.Sleep(time.Second * 2)        fmt.Println("goroutine1: 我解锁了,你们去抢吧")        ch <- struct{}{}    }()    go func() {        fmt.Println("goroutine2: 等待解锁")        l.Lock()        defer l.Unlock()        fmt.Println("goroutine2: 欧耶,我也解锁了")        ch <- struct{}{}    }()    // 等待 goroutine 执行结束    for i := 0; i < 2; i++ {        <-ch    }}

上面的代码运行结果如下:

goroutine1: 我会锁定大概 2s
goroutine2: 等待解锁
goroutine1: 我解锁了,你们去抢吧
goroutine2: 欧耶,我也解锁了

读写锁

读写锁有如下四个方法:

读写锁的区别在于:

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

我们可以将其总结为如下三条:

示例代码如下所示:

package mainimport (    "fmt"    "math/rand"    "sync")var count intvar rw sync.RWMutexfunc 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{}{}}

其执行结果如下:

goroutine 0 进入读操作...
goroutine 0 读取结束,值为:0
goroutine 3 进入读操作...
goroutine 1 进入读操作...
goroutine 3 读取结束,值为:0
goroutine 1 读取结束,值为:0
goroutine 4 进入写操作...
goroutine 4 写入结束,新值为:81
goroutine 4 进入读操作...
goroutine 4 读取结束,值为:81
goroutine 2 进入读操作...
goroutine 2 读取结束,值为:81
goroutine 0 进入写操作...
goroutine 0 写入结束,新值为:887
goroutine 1 进入写操作...
goroutine 1 写入结束,新值为:847
goroutine 2 进入写操作...
goroutine 2 写入结束,新值为:59
goroutine 3 进入写操作...
goroutine 3 写入结束,新值为:81

下面再来看两个示例。

【示例 1】多个读操作同时读取一个变量时,虽然加了锁,但是读操作是不受影响的。(读和写是互斥的,读和读不互斥)

package mainimport (    "sync"    "time")var m *sync.RWMutexfunc main() {    m = new(sync.RWMutex)    // 多个同时读    go read(1)    go read(2)    time.Sleep(2*time.Second)}func read(i int) {    println(i,"read start")    m.RLock()    println(i,"reading")    time.Sleep(1*time.Second)    m.RUnlock()    println(i,"read over")}

运行结果如下:

1 read start
1 reading
2 read start
2 reading
1 read over
2 read over

【示例 2】由于读写互斥,所以写操作开始的时候,读操作必须要等写操作进行完才能继续,不然读操作只能继续等待。

package mainimport (    "sync"    "time")var m *sync.RWMutexfunc main() {    m = new(sync.RWMutex)    // 写的时候啥也不能干    go write(1)    go read(2)    go write(3)    time.Sleep(2*time.Second)}func read(i int) {    println(i,"read start")    m.RLock()    println(i,"reading")    time.Sleep(1*time.Second)    m.RUnlock()    println(i,"read over")}func write(i int) {    println(i,"write start")    m.Lock()    println(i,"writing")    time.Sleep(1*time.Second)    m.Unlock()    println(i,"write over")}

运行结果如下:

1 write start
3 write start
1 writing
2 read start
1 write over
2 reading

到此,相信大家对“怎么使用Go语言sync包与锁实现限制线程对变量的访问”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯