go面试题-并发编程

go并发编程

Mutex几种状态

  • mutexLocked 表示互斥锁的锁定状态
  • mutexWoken 唤醒锁
  • mutexStarving 当前互斥锁进入饥饿状态
  • mutexWaiterShift 统计阻塞在这个互斥锁上的goroutine的数目

互斥锁无冲突是最简单的情况了,有冲突时,首先进行自旋,因为Mutex保护的代码段都很短

经过短暂的自旋就可以获得,如果自旋等待无果,就只好通过信号量让当前goroutine进入Gwaitting状态

Mutex的正常模式和饥饿模式

  • 正常模式(非公平锁)
    1. 正常模式下,所有等待的goroutine按照FIFO(先进先出)的顺序等待
    2. 唤醒的goroutine不会直接拥有锁而是会和新进来的请求goroutine竞争锁
    3. 新请求的goroutine更容易抢占,因为它正在CPU上执行,所以刚刚唤醒的goroutine很大可能会竞争失败
    4. 在这种情况下这个被唤醒的goroutine会被加入到等待队列的前面
  • 饥饿模式(公平锁)
    1. 为了解决等待goroutine队列的长尾问题,饥饿模式下
    2. 直接由unlock把锁交给等待队列中第一个goroutine(队头),同时,饥饿模式下
    3. 新进来的goroutine不会进行抢锁,也不会进入自旋状态,会直接进入等待队列的尾部
    4. 这样很好的解决了老的goroutine一直抢不到锁的情况
  • 饥饿模式的触发条件
    1. 当一个goroutine等待时间超过1毫秒时,或者当前队列剩下一个goroutine的时候,Mutex切换到饥饿模式
  • 总结
    1. 对于两种模式,正常模式下性能是最好的,因为goroutine可以连续多次获取锁
    2. 饥饿模式解决了取锁公平的问题,但是性能会下降,这其实是性能和公平的一个平衡模式

Mutex允许自旋的条件

  1. 锁已被占用,且锁不处于饥饿模式
  2. 积累的自旋次数小于最大自旋次数
  3. CPU核数>1
  4. 有空闲的P
  5. 当前的goroutine所挂载的P下,本地待运行队列为空

RWMutex实现

  1. 通过记录readerCount读锁的数量来进行限制,当有一个写锁的时候,会将读锁的数量设置为负数1<<30
  2. 目的是让新进来的读锁等待之前的写锁释放通知读锁
  3. 同样当有写锁进行抢占时,也会等待之前的读锁都释放完毕,才进行后续的操作
  4. 而等写锁释放完成之后,会将值重新加上1<<30,并通知刚才新进入的读锁(rw.readerSem)两者互相限制

RWMutex注意事项

  1. RWMutex是个单写多读锁,可以加多个读锁或者一个写锁
  2. 读锁占用的情况下会阻止写,不会阻止读,多个goroutine可以同时获取读锁
  3. 写锁会阻止其它goroutine(无论读写锁)进来,整个锁由该goroutine独占
  4. 适用于读多写少的场景
  5. RWMutex的零值是一个未锁定状态的互斥锁
  6. RWMutex在首次使用之后就不能再被拷贝
  7. RWMutex的读锁或写锁在未锁定的状态下进行解锁都会引发panic
  8. RWMutex的一个写锁去锁定临界区的共享资源,如果临界区的资源已被(读锁或写锁)锁定,这个写锁的goroutine会被阻塞直到解锁
  9. RWMutex的读锁不要用于递归调用,容易产生死锁
  10. RWMutex的锁定状态与特定的goroutine没有关联,一个goroutine可以RLock(Lock),另外一个goroutine可以RUnlock(Unlock)
  11. 写锁被解锁后,所有因操作锁定读锁的goroutine会被唤醒,并都可以成功锁定读锁
  12. 读锁被解锁后,在没有其它读锁锁定的情况下,所有因操作锁定写锁而被阻塞的goroutine中,其中等待时间最长的goroutine会被唤醒

cond是什么

  1. Cond实现了一种条件变量,可以使用在多个reader等待共享资源ready的场景,(如果只有一读一写,一个锁或者channel就搞定了)
  2. 每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex), 当修改条件或者调用Wait方法时,必须加锁以保护condition
  3. 案例:
var (
	c = sync.Cond{L: &sync.Mutex{}}  // 条件变量,多个reader等待共享资源ready的场景
	maxNum = 15
)
func main(){
	for i := 0; i < maxNum; i++{
		go func(i int) {
			c.L.Lock()
			defer c.L.Unlock()
			// Wait会释放c.L锁,并挂起调用者的goroutine,之后恢复执行
			c.Wait()  // Wait会在返回时对c.L加锁
			// 除非被Broadcast或Signal唤醒,否则Wait不会返回
			fmt.Println("goroutine:", i)
			time.Sleep(time.Millisecond * 100)
		}(i)
	}
	c.L.Lock()
	maxNum = 16
	c.L.Unlock()
	c.Broadcast()  // 广播,所有等待的goroutine都会执行
	//c.Signal()  // 单播,从所有等待的goroutine中随机找一个去执行
	time.Sleep(time.Second * 2)
}

Broadcast和Signal的区别

  • Broadcast会唤醒所有等待c的goroutine,调用Broadcast的时候,可以加锁也可以不加锁
  • Signal只唤醒一个等待c的goroutine,调用Signal的时候,可以加锁也可以不加锁

Cond中Wait使用

  1. Wait会自动释放c.L锁,并挂起调用者的goroutine,之后恢复执行
  2. Wait会在返回时对c.L加锁
  3. 除非被Broadcast或Signal唤醒,否则Wait不会返回
  4. 由于Wait第一次恢复是,c.L并没有加锁,所以当Wait返回时,调用者通常不能假设条件为真
  5. 简单来说,只要想使用condition就必须加锁

WaitGroup用法

  • 一个WaitGroup对象可以等待一组协程结束,使用方法是
  1. main协程通过调用 wg.add(delta int) 来设置worker协程的个数,然后创建worker协程
  2. worker协程结束以后,都要调用wg.Done()
  3. main协程调用wg.Wait()而被block, 知道所有worker协程全部执行结束后返回
  4. 如果不确定要创建的worker协程数量,就不要一次性wg.add(),而是在每个创建worker协程之前调用一次wg.add(1)
  5. 首次使用后不得复制wg

WaitGroup实现原理

  1. WaitGroup主要维护了两个计数器,一个请求计数器v,一个等待计数器w
  2. 二者组成了一个64bit的值,请求计数器占高32bit,等待计数器占低32bit
  3. 每次Add执行,请求计数器 v+1, 每次Done,等待计数器 w-1
  4. 当v为0时,通过信号量唤醒Wait

什么事sync.Once

  1. Once可以用来执行且仅仅执行一次的动作,常常用于单例对象的初始化场景
  2. Once常常用来初始化单例资源,或者并发访问只需要初始化一次的共享资源
  3. 或者在测试的时候初始化一次测试资源
  4. sync.Once只暴露了一个方法Do,你可以多次调用Do方法
  5. 但是只有第一次调用Do方法时f参数才会执行,这里的f是一个无参数无返回值的函数

什么操作叫原子操作

  1. 原子操作即是进程过程中不能被中断的操作,针对某个值的原子操作在被进行的过程中
  2. CPU绝不会再去进行其它针对值的操作,为了实现这样的严谨性,原子操作仅会由一个独立的CPU指令代表和完成
  3. 原子操作是无锁的,常常直接通过CPU指令直接实现,
  4. 事实上,其它同步技术的实现常常依赖于原子操作

原子操作和锁的区别

  1. 原子操作由底层硬件支持,而锁由操作系统调度器实现
  2. 锁应当用来保护一段逻辑,对于一个变量更新的保护
  3. 原子操作通常执行上会更有效率,并且更能利用计算机多核资源
  4. 如果要更新的是一个复合对象,则应当使用automic.Value封装好的实现

sync.Pool有什么用

  1. 对于很多需要重复分配、回收内存的地方,sync.Pool是一个很好的选择
  2. 频繁的分配回收内存会给GC带来一定负担,严重的时候会引起CPU的毛刺
  3. 而sync.Pool可以将暂时不使用的对象缓存起来,待下次需要的时候直接使用
  4. 不用再次经过内存分配,复用对象的内存,减轻GC的压力,提升系统性能
  5. 案例
func main(){
	var p sync.Pool
	var a = "jdfakljfdalfaskdj阿加加加金灯送福卡萨发几块爱神的箭快疯了金阿奎"
	p.Put(a)
	ret := p.Get()
	fmt.Println(ret)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毕业_设计

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值