Sakurairo

加载中...

首页文章专题归档关于友链

主题

返回文章列表
GO

Go 并发安全和锁

有时候,我们在go代码编写中会出现多个goroutine同时操作一个临界区(也就是一个资源),这种情况会发生竞态问题。 会出现竞态问题的代码如下: 上面我们使用了开启了两个协程调用add对x进行累加,这两个协程在修改和访问x变量时会出现数据...

2024-06-252 分钟 356 阅读

发布于 2024-06-25 16:26

·

作者:八云澈

有时候,我们在go代码编写中会出现多个goroutine同时操作一个临界区(也就是一个资源),这种情况会发生竞态问题。

会出现竞态问题的代码如下:

Go
1var x int642 3var sg sync.WaitGroup4 5func add () {6 7    for i:=0;i++ {8    x= x+19    } 10    wg.Done()11  12}13 14func main (){15wg.Add(2)16go add()17go add()18wg.Wait()19fmt.Println(x)20}

上面我们使用了开启了两个协程调用add对x进行累加,这两个协程在修改和访问x变量时会出现数据竞争的问题,导致最后结果和预期是不符的

这种情况下我们可以使用加锁的方式控制go协程对共享资源进行操作以保证不会出现竞态问题。

互斥锁

go提供了sync包中的Mutex类型对互斥锁进行实现。他能保证同时只有一个goroutine可以访问共享资源。互斥锁能保证在同一时间,对资源的写入和读取操作有且只有一个,并且多个协程在等待同一个锁的时候,唤醒协程的策略是随机的。

ps:互斥锁的性能是比较低的,go提供的互斥锁的类型是经过优化的,在绝大多数情况下是比自己写锁是要好的。

现在使用互斥锁来解决上面的问题

Go
1var x int642 3var sg sync.WaitGroup4 5var lock sync.Mutex6 7func add () {8 9    for i:=0;i++ {10    lock.Lock()11    x= x+112    lock.Unlock13    } 14    wg.Done()15  16}17 18func main (){19wg.Add(2)20go add()21go add()22wg.Wait()23fmt.Println(x)24}

读写互斥锁

互斥锁是完全互斥的,这个就导致在实际场景下(读多写少),我们去并发的读取一个资源,加互斥锁的话会导致性能的急剧下降,这种情况下我们可以使用读写锁。sync包中也提供了一个读写锁的实现,就是sync包中的RWMutex类型。

读写锁分为两种锁,一种是读锁,一种是写锁。在协程获取到资源的读锁后,其他协程如果获取读锁,将会继续获得读锁,而获取写锁的话将会等待所有读锁释放后才能获取到写锁。同理,当一个协程获取到写锁时,所有获取读锁和写锁的协程都将等待写锁释放后才能获取对应的写锁和读锁。

读写锁示例:

Go
1var (2 3    x      int644 5    wg     sync.WaitGroup6 7    lock   sync.Mutex8 9    rwlock sync.RWMutex10 11)12 13  14 15func write() {16 17    // lock.Lock()   // 加互斥锁18 19    rwlock.Lock() // 加写锁20 21    x = x + 122 23    time.Sleep(10 * time.Millisecond) // 假设读操作耗时10毫秒24 25    rwlock.Unlock()                   // 解写锁26 27    // lock.Unlock()                     // 解互斥锁28 29    wg.Done()30 31}32 33  34 35func read() {36 37    // lock.Lock()                  // 加互斥锁38 39    rwlock.RLock()               // 加读锁40 41    time.Sleep(time.Millisecond) // 假设读操作耗时1毫秒42 43    rwlock.RUnlock()             // 解读锁44 45    // lock.Unlock()                // 解互斥锁46 47    wg.Done()48 49}50 51  52 53func main() {54 55    start := time.Now()56 57    for i := 0; i < 10; i++ {58 59        wg.Add(1)60 61        go write()62 63    }64 65  66 67    for i := 0; i < 1000; i++ {68 69        wg.Add(1)70 71        go read()72 73    }74 75  76 77    wg.Wait()78 79    end := time.Now()80 81    fmt.Println(end.Sub(start))82 83}
八云澈

Writer · Recorder

八云澈(Bayunche)

开发者 / 写作者 / 普通生活记录者 · 中国 · 深圳

喜欢把工作里的技术问题、读书时的触动、生活中的琐碎观察慢慢写下来。既想把复杂问题讲清楚,也想留住那些很快会被忘掉的日常。

Java 后端Go 工程化读书札记生活记录日常随笔

Continue Reading

相关文章

更多文章

上一篇

Go语言基础学习笔记 (方法与结构)

2024-05-29

下一篇

前端性能优化

2024-07-02

评论区

读完这篇文章后,如果你也有类似经验或不同看法,欢迎继续交流。

💬 评论