Go面试题:锁的实现原理sync-mutex篇

这篇具有很好参考价值的文章主要介绍了Go面试题:锁的实现原理sync-mutex篇。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Go中,主要实现了两种锁:sync.Mutex(互斥锁) 以及 sync.RWMutex(读写锁)。

本篇主要给大家介绍sync.Mutex的使用和实现原理。

为什么需要锁

在高并发下或多goroutine同时执行下,可能会同时读写同一块内存,比如如下场景:

var count int
var mu sync.Mutex

func func1() {
	for i := 0; i < 1000; i++ {
		go func() {
			count = count + 1
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(count)
}

输出的值预期是1000,实际是 948,965等,多次运行结果不一致。
之所以出现这样的现象,是因为对于count=count+1来讲,每个goroutine执行步骤为:

  • 读取当前count值
  • count+1
  • 修改count值

当多个goroutine同时执行修改数值时,后面执行的goroutine会把前面goroutine对count的修改覆盖。

在Go中对于并发程序进行公共资源的访问的限制最常用的就是互斥锁(sync.mutex)的方式

sync.mutex的常用方法有两个:

  • Mutex.lock()用来获取锁
  • Mutex.Unlock()用于释放锁
    在 Lock 和 Unlock 方法之间的代码段称为资源的临界区,这一区间的代码是严格被锁保护的,是线程安全的,任何一个时间点最多只能有一个goroutine在执行。

基于此,上面的示例可以采用sync.mutex来改进:

var count int
var mutex sync.Mutex

func func2() {
	for i := 0; i < 1000; i++ {
		go func() {
			mutex.Lock()
			count = count + 1
			mutex.Unlock()
		}()
	}
	time.Sleep(time.Second)
	fmt.Println(count)
}

输出结果为1000。

当某一goroutine执行了mutex.lock()方法后,如果有其他的goroutine来执行上锁操作,会被阻塞,直到当前的goroutine执行mutex.unlock()方法释放锁后其他的goroutine才会继续抢锁执行。

实现原理

sync.Mutex的数据结构
Go中的sync.Mutex的结构体为:

type Mutex struct {
	state int32
	sema  uint32
}

Sync.Mutex由两个字段构成,state用来表示当前互斥锁处于的状态,sema用于控制锁状态的信号量。相信各位道友读完这两个字段的描述后,好像懂了,又好像没懂。下面我们详细理解下这两个字段到底都作了哪些事。
互斥锁state主要记录了如下四种状态:
Go面试题:锁的实现原理sync-mutex篇,golang,java,面试

waiter_num: 记录了当前等待抢这个锁的goroutine数量
starving: 当前锁是否处于饥饿状态 (后文会详解锁的饥饿状态) 0: 正常状态 1: 饥饿状态
woken: 当前锁是否有goroutine已被唤醒。 0:没有goroutine被唤醒; 1: 有goroutine正在加锁过程
locked: 当前锁是否被goroutine持有。 0: 未被持有 1: 已被持有
sema信号量的作用
当持有锁的gorouine释放锁后,会释放sema信号量,这个信号量会唤醒之前抢锁阻塞的gorouine来获取锁。

锁的两种模式

互斥锁在设计上主要有两种模式: 正常模式和饥饿模式。

之所以引入了饥饿模式,是为了保证goroutine获取互斥锁的公平性。所谓公平性,其实就是多个goroutine在获取锁时,goroutine获取锁的顺序,和请求锁的顺序一致,则为公平。
正常模式下,所有阻塞在等待队列中的goroutine会按顺序进行锁获取,当唤醒一个等待队列中的goroutine时,此goroutine并不会直接获取到锁,而是会和新请求锁的goroutine竞争。 通常新请求锁的goroutine更容易获取锁,这是因为新请求锁的goroutine正在占用cpu片执行,大概率可以直接执行到获取到锁的逻辑。

饥饿模式下, 新请求锁的goroutine不会进行锁获取,而是加入到队列尾部阻塞等待获取锁。
饥饿模式的触发条件

  • 当一个goroutine等待锁的时间超过1ms时,互斥锁会切换到饥饿模式

饥饿模式的取消条件:文章来源地址https://www.toymoban.com/news/detail-716493.html

  • 当获取到锁的这个goroutine是等待锁队列中的最后一个goroutine,互斥锁会切换到正常模式
  • 当获取到锁的这个goroutine的等待时间在1ms之内,互斥锁会切换到正常模式

注意事项

  1. 在一个goroutine中执行Lock()加锁成功后,不要再重复进行加锁,否则会panic。
  2. 在Lock() 之前 执行Unlock()释放锁 会panic
  3. 对于同一把锁,可以在一个goroutine中执行Lock加锁成功后,可以在另外一个gorouine中执行Unlock释放锁。

到了这里,关于Go面试题:锁的实现原理sync-mutex篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深入Golang之Mutex

    可以限制临界区只能同时由一个线程持有。 直接在流程结构中使用 lock 、 unlock 嵌入到结构中,然后通过结构体的 mutex 属性 调用 lock 、 unlock 嵌入到结构体中,但是是直接在需要锁定的资源方法中使用,让外界无需关注资源锁定 在进行资源锁定的过程中,很容易出现 data r

    2024年02月11日
    浏览(12)
  • Go实现在线词典翻译(三种翻译接口,结合sync)

    首先介绍用火山翻译英译汉。 然后运用goroutine,结合sync包,同时实现彩云翻译和百度翻译。可对输入的内容自动检测,完成英译汉,汉译英。 1.json格式数据转golang结构体 JSON转Golang Struct - 在线工具 - OKTools 2.curl转爬虫代码 Convert curl commands to code (curlconverter.com)

    2024年02月13日
    浏览(13)
  • 【从零单排Golang】第十五话:用sync.Once实现懒加载的用法和坑点

    在使用Golang做后端开发的工程中,我们通常需要声明一些一些配置类或服务单例等在业务逻辑层面较为底层的实例。为了节省内存或是冷启动开销,我们通常采用lazy-load懒加载的方式去初始化这些实例。初始化单例这个行为是一个非常经典的并发处理的案例,比如在java当中,

    2024年02月10日
    浏览(14)
  • Java乐观锁的实现原理和典型案例

    什么是乐观锁? 在并发编程中,多个线程同时对同一资源进行操作时,需要使用锁来保证数据的一致性。 乐观锁与悲观锁是两种不同的锁机制。 悲观锁会在整个操作期间占用资源的独占性,以保证数据的一致性,而乐观锁则是基于版本号或时间戳的机制,在操作前做一个乐

    2024年02月12日
    浏览(26)
  • 编程小窍门: 一个简单的go mutex的小例子

    本期小窍门用到了两个组件 mutex 这个类似其他语言的互斥锁 waitGroup 这个类似其他语言的信号量或者java的栅栏锁 示例如下

    2024年02月13日
    浏览(13)
  • golang--sync.map(安全字典)

    引言:在Go语言中,多个goroutine之间安全地共享数据是一项挑战。为了解决这个问题,Go语言提供了sync包,并在其中引入了sync.Map类型。sync.Map是一种并发安全的映射数据结构,它提供了高效的并发访问方式,避免了显式的锁操作。本文将深入探讨sync.Map的使用方法和底层实现

    2024年02月13日
    浏览(19)
  • 从原理到实践,分析 Redisson 分布式锁的实现方案(二)

            上篇讲解了如何用 Redis 实现分布式锁的方案,它提供了简单的原语来实现基于Redis的分布式锁。然而,Redis作为分布式锁的实现方式也存在一些缺点。本文将引入Redisson来实现分布式锁。         Redisson是一个基于Redis的分布式Java框架。它提供了丰富的功能和工

    2024年02月15日
    浏览(14)
  • 关于golang锁的一点东西

    本文基于go 1.19.3 最近打算再稍微深入地看下golang的源码,先从简单的部分入手。正巧前段时间读了操作系统同步机制的一点东西,那么golang这里就从锁开始好了。 在这部分内容中,可能不会涉及到太多的细节的讲解。更多的内容会聚焦在我感兴趣的一些点,以及整体的设计

    2024年02月14日
    浏览(17)
  • Golang 线程安全与 sync.Map

    前言 线程安全通常是指在并发环境下,共享资源的访问被适当地管理,以防止竞争条件(race conditions)导致的数据不一致 Go语言中的线程安全可以通过多种方式实现 实现方式 互斥锁(Mutexes) Go的sync包提供了Mutex和RWMutex类型来确保在一个时间点只有一个协程可以访问某个资

    2024年01月24日
    浏览(13)
  • Java LongAdder类介绍、代码示例、底层实现原理及与分段锁的区别

    LongAdder是Java并发包(java.util.concurrent)中的一个类,用于高效地实现多线程环境下的加法操作。 在多线程环境中,如果多个线程同时对同一个变量进行加法操作,会存在竞争条件(race condition)。传统的加法操作使用synchronized或者锁来保证线程安全,但是在高并发情况下

    2024年02月12日
    浏览(11)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包