什么时候需要用到锁?

当程序中就一个线程的时候,是不需要加锁的,但是通常实际的代码不会只是单线程,所以这个时候就需要用到锁了,那么关于锁的使用场景主要涉及到哪些呢?

  • 多个线程在读相同的数据时
  • 多个线程在写相同的数据时
  • 同一个资源,有读又有写

互斥锁(sync.Mutex)

互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问到共享资源(同一个时刻只有一个线程能够拿到锁)

先通过一个并发读写的例子演示一下,当多线程同时访问全局变量时,结果会怎样?

package main
import ("fmt") var count int func main() {
for i := 0; i < 2; i++ {
go func() {
for i := 1000000; i > 0; i-- {
count ++
}
fmt.Println(count)
}()
} fmt.Scanf("\n") //等待子线程全部结束
} 运行结果:
980117
1011352 //最后的结果基本不可能是我们想看到的:200000

修改代码,在累加的地方添加互斥锁,就能保证我们每次得到的结果都是想要的值

package main
import ("fmt"
"sync"
) var (
count int
lock sync.Mutex
) func main() {
for i := 0; i < 2; i++ {
go func() {
for i := 1000000; i > 0; i-- {
lock.Lock()
count ++
lock.Unlock()
}
fmt.Println(count)
}()
} fmt.Scanf("\n") //等待子线程全部结束
} 运行结果:
1952533
2000000 //最后的线程打印输出

读写锁(sync.RWMutex)

在读多写少的环境中,可以优先使用读写互斥锁(sync.RWMutex),它比互斥锁更加高效。sync 包中的 RWMutex 提供了读写互斥锁的封装

读写锁分为:读锁和写锁

  • 如果设置了一个写锁,那么其它读的线程以及写的线程都拿不到锁,这个时候,与互斥锁的功能相同
  • 如果设置了一个读锁,那么其它写的线程是拿不到锁的,但是其它读的线程是可以拿到锁

通过设置写锁,同样可以实现数据的一致性:

package main
import ("fmt"
"sync"
) var (
count int
rwLock sync.RWMutex
) func main() {
for i := 0; i < 2; i++ {
go func() {
for i := 1000000; i > 0; i-- {
rwLock.Lock()
count ++
rwLock.Unlock()
}
fmt.Println(count)
}()
} fmt.Scanf("\n") //等待子线程全部结束
} 运行结果:
1968637
2000000

互斥锁和读写锁的性能对比

demo:制作一个读多写少的例子,分别开启 3 个 goroutine 进行读和写,输出最终的读写次数

1)使用互斥锁:

package main
import (
"fmt"
"sync"
"time"
) var (
count int
//互斥锁
countGuard sync.Mutex
) func read(mapA map[string]string){
for {
countGuard.Lock()
var _ string = mapA["name"]
count += 1
countGuard.Unlock()
}
} func write(mapA map[string]string) {
for {
countGuard.Lock()
mapA["name"] = "johny"
count += 1
time.Sleep(time.Millisecond * 3)
countGuard.Unlock()
}
} func main() {
var num int = 3
var mapA map[string]string = map[string]string{"nema": ""} for i := 0; i < num; i++ {
go read(mapA)
} for i := 0; i < num; i++ {
go write(mapA)
} time.Sleep(time.Second * 3)
fmt.Printf("最终读写次数:%d\n", count)
} 运行结果:
最终读写次数:3766

2)使用读写锁

package main
import (
"fmt"
"sync"
"time"
) var (
count int
//读写锁
countGuard sync.RWMutex
) func read(mapA map[string]string){
for {
countGuard.RLock() //这里定义了一个读锁
var _ string = mapA["name"]
count += 1
countGuard.RUnlock()
}
} func write(mapA map[string]string) {
for {
countGuard.Lock() //这里定义了一个写锁
mapA["name"] = "johny"
count += 1
time.Sleep(time.Millisecond * 3)
countGuard.Unlock()
}
} func main() {
var num int = 3
var mapA map[string]string = map[string]string{"nema": ""} for i := 0; i < num; i++ {
go read(mapA)
} for i := 0; i < num; i++ {
go write(mapA)
} time.Sleep(time.Second * 3)
fmt.Printf("最终读写次数:%d\n", count)
} 运行结果:
最终读写次数:8165

结果差距大概在 2 倍左右,读锁的效率要快很多!

关于互斥锁的补充

互斥锁需要注意的问题:

  1. 不要重复锁定互斥锁
  2. 不要忘记解锁互斥锁,必要时使用 defer 语句
  3. 不要在多个函数之间直接传递互斥锁

死锁: 当前程序中的主 goroutine 以及我们启用的那些 goroutine 都已经被阻塞,这些 goroutine 可以被称为用户级的 goroutine 这就相当于整个程序已经停滞不前了,并且这个时候 go 程序会抛出如下的 panic:

fatal error: all goroutines are asleep - deadlock!

并且go语言运行时系统抛出自行抛出的panic都属于致命性错误,都是无法被恢复的,调用recover函数对他们起不到任何作用

Go语言中的互斥锁是开箱即用的,也就是我们声明一个sync.Mutex 类型的变量,就可以直接使用它了,需要注意:该类型是一个结构体类型,属于值类型的一种,将它当做参数传给一个函数,将它从函数中返回,把它赋值给其他变量,让它进入某个管道,都会导致他的副本的产生。并且原值和副本以及多个副本之间是完全独立的,他们都是不同的互斥锁,所以不应该将锁通过函数的参数进行传递

关于读写锁的补充

1、在写锁已被锁定的情况下再次试图锁定写锁,会阻塞当前的goroutine

2、在写锁已被锁定的情况下再次试图锁定读锁,也会阻塞当前的goroutine

3、在读锁已被锁定的情况下试图锁定写锁,同样会阻塞当前的goroutine

4、在读锁已被锁定的情况下再试图锁定读锁,并不会阻塞当前的goroutine

对于某个受到读写锁保护的共享资源,多个写操作不能同时进行,写操作和读操作也不能同时进行,但多个读操作却可以同时进行

对写锁进行解锁,会唤醒“所有因试图锁定读锁,而被阻塞的goroutine”, 并且这个通常会使他们都成功完成对读锁的锁定(这个还不理解)

对读锁进行解锁,只会在没有其他读锁锁定的前提下,唤醒“因试图锁定写锁,而被阻塞的 goroutine” 并且只会有一个被唤醒的 goroutine 能够成功完成对写锁的锁定,其他的 goroutine 还要在原处继续等待,至于哪一个goroutine,那么就要看谁等待的事件最长

解锁读写锁中未被锁定的写锁, 会立即引发panic ,对其中的读锁也是如此,并且同样是不可恢复的

参考链接:https://www.cnblogs.com/zhaof/p/8636384.html

ending ~

Go 互斥锁(sync.Mutex)和 读写锁(sync.RWMutex)的更多相关文章

  1. 显式锁(三)读写锁ReadWriteLock

    前言:   上一篇文章,已经很详细地介绍了 显式锁Lock 以及 其常用的实现方式- - ReetrantLock(重入锁),本文将介绍另一种显式锁 - - 读写锁ReadWriteLock.    ...

  2. Go基础系列:互斥锁Mutex和读写锁RWMutex用法详述

    sync.Mutex Go中使用sync.Mutex类型实现mutex(排他锁.互斥锁).在源代码的sync/mutex.go文件中,有如下定义: // A Mutex is a mutual exc ...

  3. Go语言协程并发---读写锁sync.RWMutex

    package main import ( "fmt" "sync" "time" ) /* 读写锁 多路只读 一路只写 读写互斥 */ / ...

  4. 【多线程】C++ 互斥锁(mutex)的简单原理分析

    多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序.一般情况下,分为两种类型的多任务处理:基于进程和基于线程. 1)基于进程的多任务处理是程序的并发执行. 2)基于线程 ...

  5. 互斥锁(Mutex)

    互斥锁(Mutex)互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它.互斥锁可适用于一个共享资源每次只能被一个线程访问的情况 函数://创建一个处于未获取状态的互斥锁Public ...

  6. Linux的线程同步对象:互斥量Mutex,读写锁,条件变量

        进程是Linux资源分配的对象,Linux会为进程分配虚拟内存(4G)和文件句柄等 资源,是一个静态的概念.线程是CPU调度的对象,是一个动态的概念.一个进程之中至少包含有一个或者多个线程.这 ...

  7. c++并发编程之互斥锁(mutex)的使用方法

    1. 多个线程访问同一资源时,为了保证数据的一致性,最简单的方式就是使用 mutex(互斥锁). 引用 cppreference 的介绍: The mutex class is a synchroni ...

  8. 深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)

      这一节主要是谈谈读写锁的实现. 上一节中提到,ReadWriteLock看起来有两个锁:readLock/writeLock.如果真的是两个锁的话,它们之间又是如何相互影响的呢? 事实上在Reen ...

  9. 深入浅出 Java Concurrency (13): 锁机制 part 8 读写锁 (ReentrantReadWriteLock) (1)

      从这一节开始介绍锁里面的最后一个工具:读写锁(ReadWriteLock). ReentrantLock 实现了标准的互斥操作,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念.前面的章节中一 ...

  10. 深入浅出 Java Concurrency (14): 锁机制 part 9 读写锁 (ReentrantReadWriteLock) (2)[转]

    这一节主要是谈谈读写锁的实现. 上一节中提到,ReadWriteLock看起来有两个锁:readLock/writeLock.如果真的是两个锁的话,它们之间又是如何相互影响的呢? 事实上在Reentr ...

随机推荐

  1. 原理分析dubbo分布式应用中使用zipkin做链路追踪(转)

    作者:@nele本文为作者原创,转载请注明出处:https://www.cnblogs.com/nele/p/10171794.html 目录 zipkin是什么为什么使用Zipkinzipkin架构 ...

  2. PHP中Cookie与 Session

    在非常多时候,我们需要跟踪浏览者在整个网站的活动,对他们身份进行自动或半自动的识别(也就是平时常说的网站登陆之类的功能),这时候,我们常采用  Cookie与 Session 来跟踪和判断. Sess ...

  3. pip安装各种模块

    date: 2019-08-20   19:27:09 pip install requests pip install xpinyin pip install redis

  4. Python3基础 九九乘法表

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  5. faster_rcnn mAP

  6. 泡泡一分钟:Project AutoVision - Localization and 3D Scene Perception for an Autonomous Vehicle with a Multi-Camera System

    Project AutoVision - Localization and 3D Scene Perception for an Autonomous Vehicle with a Multi-Cam ...

  7. 深入理解ElasticSearch(PDF版 内含目录)

    深入理解ElasticSearch 介绍: 本书涵盖了Elasticsearch的许多中高级功能,并介绍了缓存.ApacheLucene库以及监控等模块的内部运作机制.其中,还涉及一些实用案例,比如配 ...

  8. FreeMarker的应用场景

      FreeMarker是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页.电子邮件.配置文件.源代码等)的通用工具. 它不是面向最终用户的,而是一个Java类库,是一 ...

  9. [转]Office 安装卸载太麻烦?用这个工具帮你解决:Office Tool Plus

    原文链接:https://sspai.com/post/43839 Office Tool官方网站:https://otp.landian.vip/zh-cn/ 真的很好用,发一个安装的截图:

  10. TS - 问题解决力 - 上篇

    本文是已读书籍的内容摘要,少部分有轻微改动,但不影响原文表达. <麦肯锡工作法 - 个人竞争力提升50%的7堂课> ISBN: 9787508644691 https://book.dou ...