go语言循环队列的实现
队列的概念在 顺序队列 中,而使用循环队列的目的主要是规避假溢出造成的空间浪费,在使用循环队列处理假溢出时,主要有三种解决方案
本文提供后两种解决方案。
顺序队和循环队列是一种特殊的线性表,与顺序栈类似,都是使用一组地址连续的存储单元依次存放自队头到队尾的数据元素,同时附设队头(front)和队尾(rear)两个指针,但我们要明白一点,这个指针并不是指针变量,而是用来表示数组当中元素下标的位置。
本文使用切片来完成的循环队列,由于一开始使用三个参数的make关键字创建切片,在输出的结果中不包含nil值(看起来很舒服),而且在验证的过程中发现使用append()函数时切片内置的cap会发生变化,在消除了种种障碍后得到了一个四不像的循环队列,即设置的指针是顺序队列的指针,但实际上进行的操作是顺序队列的操作。最后是对make()函数和append()函数的一些使用体验和小结,队列的应用放在链队好了。
官方描述(片段)
即切片是一个抽象层,底层是对数组的引用。
当我们使用
构建出来的切片的每个位置的值都被赋为interface类型的初始值nil,但是nil值也是有大小的。
而使用
来进行初始化时,虽然生成的切片中不包含nil值,但是无法通过设置的指针变量来完成入队和出队的操作,只能使用append()函数来进行操作
在go语言中,切片是一片连续的内存空间加上长度与容量的标识,比数组更为常用。使用 append 关键字向切片中追加元素也是常见的切片操作
正是基于此,在使用go语言完成循环队列时,首先想到的就是使用make(type, len, cap)关键字方式完成切片初始化,然后使用append()函数来操作该切片,但这一方式出现了很多问题。在使用append()函数时,切片的cap可能会发生变化,用不好就会发生扩容或收缩。最终造成的结果是一个四不像的结果,入队和出队操作变得与指针变量无关,失去了作为循环队列的意义,用在顺序队列还算合适。
参考博客:
Go语言中的Nil
Golang之nil
Go 语言设计与实现
Go语言设计与实现(上)
基本设计思路:
类型转换、类型断言、动态派发。iface,eface。
反射对象具有的方法:
编译优化:
内部实现:
实现 Context 接口有以下几个类型(空实现就忽略了):
互斥锁的控制逻辑:
设计思路:
(以上为写被读阻塞,下面是读被写阻塞)
总结,读写锁的设计还是非常巧妙的:
设计思路:
WaitGroup 有三个暴露的函数:
部件:
设计思路:
结构:
Once 只暴露了一个方法:
实现:
三个关键点:
细节:
让多协程任务的开始执行时间可控(按顺序或归一)。(Context 是控制结束时间)
设计思路: 通过一个锁和内置的 notifyList 队列实现,Wait() 会生成票据,并将等待协程信息加入链表中,等待控制协程中发送信号通知一个(Signal())或所有(Boardcast())等待者(内部实现是通过票据通知的)来控制协程解除阻塞。
暴露四个函数:
实现细节:
部件:
包: golang.org/x/sync/errgroup
作用:开启 func() error 函数签名的协程,在同 Group 下协程并发执行过程并收集首次 err 错误。通过 Context 的传入,还可以控制在首次 err 出现时就终止组内各协程。
设计思路:
结构:
暴露的方法:
实现细节:
注意问题:
包: "golang.org/x/sync/semaphore"
作用:排队借资源(如钱,有借有还)的一种场景。此包相当于对底层信号量的一种暴露。
设计思路:有一定数量的资源 Weight,每一个 waiter 携带一个 channel 和要借的数量 n。通过队列排队执行借贷。
结构:
暴露方法:
细节:
部件:
细节:
包: "golang.org/x/sync/singleflight"
作用:防击穿。瞬时的相同请求只调用一次,response 被所有相同请求共享。
设计思路:按请求的 key 分组(一个 *call 是一个组,用 map 映射存储组),每个组只进行一次访问,组内每个协程会获得对应结果的一个拷贝。
结构:
逻辑:
细节:
部件:
如有错误,请批评指正。
Go 语言三色标记扫描对象是 DFS 还是 BFS?
最近在看左神新书 《Go 语言设计与实现》的垃圾收集器时产生一个疑惑,花了点时间搞清楚了记录一下。
Go 语言垃圾回收的实现使用了标记清除算法,将对象的状态抽象成黑色(活跃对象)、灰色(活跃对象中间状态)、白色(潜在垃圾对象也是所有对象的默认状态)三种,注意没有具体的字段标记颜色。
整个标记过程就是把白色对象标黑的过程:
1.首先将 ROOT 根对象(包括全局变量、goroutine 栈上的对象等)放入到灰色集合
2.选一个灰色对象,标成黑色,将所有可达的子对象放入到灰色集合
3.重复2的步骤,直到灰色集合中为空
下图是书上的插图,看上去是一个典型的深度优先搜索的算法。
下图是刘丹冰写的《Golang 修养之路》的插图,看上去是一个典型的广度优先搜索的算法。
我疑惑的点在于这个标记过程是深度优先算法还是广度优先算法,因为很多文章博客对此都没有很清楚的说明,作为学习者这种细节其实也不影响对整个 GC 流程的理解,但是这种细节我非常喜欢扣:)
对着书和源码摸索着大致找到了一个结果是深度优先。下面看下大致的过程,源码基于1.15.2版本:
gcStart 是 Go 语言三种条件触发 GC 的共同入口
启动后台标记任务
为每个处理器创建用于执行后台标记任务的 Goroutine
上面休眠的 G 会在调度循环中检查并唤醒执行
执行标记
gcw 是每个 P 独有的所以不用担心并发的问题 和 GMP、mcache 一样设计,减少锁竞争
尝试在全局列表中获取一个不为空的 buf
这是官方实现的无锁队列:)涨见识了,for 循环加原子操作实现栈的 pop
到这里从灰色集合中获取待扫描的对象逻辑说完了。找到对象了接着就是 scanobject(b, gcw) 了,里面有两段逻辑要注意
根据索引位置找到对象进行标色
尝试存入 gcwork 的缓存中,或全局队列中
无锁队列,for 循环加原子操作实现栈的 push
到这里把灰色对象标黑就完成了,又放回灰色集合接着扫下一个指针。
Go 语言设计与实现 垃圾收集器
Golang三色标记+混合写屏障GC模式全分析
go语言是什么
Go语言是一种开源的编程语言,被广泛应用于网络编程、云计算、分布式系统等领域。
go语言的三位作者
Go语言的设计目标是成为一种语法简洁、执行效率高、并发性能强大的编程语言。它由Google公司研发,于2009年首次发布,并于2012年成为了开源项目。Go语言具有C语言的表达能力和Python的开发效率,同时还拥有自己独特的语法和特性,如协程、垃圾回收机制等。因此,它被广泛应用于网络编程、云计算、分布式系统等领域,并且越来越受到开发者的青睐。
Go语言的出现,填补了许多编程语言在并发编程方面的空缺。它提供了一种轻量级线程模型,通过协程(goroutine)的方式,实现了高效的并发编程。同时,Go语言还支持内置的网络编程和字节序列编解码库,使得网络编程变得更加容易和高效。在云计算、分布式系统等领域,Go语言也得到了广泛的应用。例如,Docker和Kubernetes等开源项目就是用Go语言开发的。此外,Go语言还具有代码可读性高、编译速度快、编译后的可执行文件体积小等优点,使得它成为了开发高性能、高并发应用的理想语言之一。