![](https://img-blog.csdnimg.cn/20201014180756757.png?x-oss-process=image/resize,m_fixed,h_64,w_64)
Golang底层
文章平均质量分 90
~庞贝
Go后端开发
展开
-
GoLang之使用sync.pool和sync.cond
Golang的sync包中的Cond实现了一种条件变量,可以使用在多个Reader等待共享资源ready的场景(如果只有一读一写,一个锁或者channel就搞定了)。Cond的汇合点:多个goroutines等待、1个goroutine通知事件发生。每个Cond都会关联一个Lock(*sync.Mutex or *sync.RWMutex),当修改条件或者调用Wait方法时,必须加锁,保护condition。原创 2022-10-20 16:49:58 · 973 阅读 · 0 评论 -
Golang之自旋锁
自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。自旋锁本身无法保证公平性,同时也无法保证可重入性。基于自旋锁,可以实现具备公平性和可重入性质的锁。原创 2022-10-04 11:35:47 · 1844 阅读 · 1 评论 -
Golang之CAS算法(compare and swap)
无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。具有三个参数,第一个是变量的地址,第二个是变量当前值,第三个是要修改变量为多少,该函数如果发现传递的old值等于当前变量的值,则使用第三个变量替换变量的值并返回true,否则返回false。当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。原创 2022-10-04 09:53:40 · 2381 阅读 · 0 评论 -
GoLang之垃圾回收的认识
GC,全称,即垃圾回收,是一种自动内存管理的机制。当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收并供其他代码进行内存申请时候复用,或者将其归还给操作系统,这种针对内存级别资源的自动回收过程,即为垃圾回收。而负责垃圾回收的程序组件,即为垃圾回收器。垃圾回收其实一个完美的 “Simplicity is Complicated” 的例子。一方面,程序员受益于 GC,无需操心、也不再需要对内存进行手动的申请和释放操作,GC 在程序运行时自动释放残留的内存。原创 2022-09-27 11:04:10 · 823 阅读 · 0 评论 -
Golang内存管理之垃圾收集器
如上图所示,其中黑色的部分是上一次垃圾收集后标记的堆大小,绿色部分是上次垃圾收集结束后新分配的内存,因为我们使用并发垃圾收集,所以黄色的部分就是在垃圾收集期间分配的内存,最后的红色部分是垃圾收集结束时与目标的差值,我们希望尽可能减少红色部分内存,降低垃圾收集带来的额外开销以及程序的暂停时间。需要注意的是,增量式的垃圾收集需要与三色标记法一起使用,为了保证垃圾收集的正确性,我们需要在垃圾收集开始前打开写屏障,这样用户程序修改内存都会先经过写屏障的处理,保证了堆内存中对象关系的强三色不变性或者弱三色不变性。原创 2022-09-26 18:19:00 · 845 阅读 · 0 评论 -
Golang内存管理之内存分配器
程序中的数据和变量都会被分配到程序所在的虚拟内存中,内存空间包含两个重要区域:栈区(Stack)和堆区(Heap)。函数调用的参数、返回值以及局部变量大都会被分配到栈上,这部分内存会由编译器进行管理;不同编程语言使用不同的方法管理堆区的内存,C++ 等编程语言会由工程师主动申请和释放内存,Go 以及 Java 等编程语言会由工程师和编译器共同管理,堆中的对象由内存分配器分配并由垃圾收集器回收。内存管理一般包含三个不同的组件,分别是用户程序(Mutator)、分配器(Allocator)和收集器(Collec原创 2022-09-24 10:00:56 · 731 阅读 · 0 评论 -
GoLang之详解Go中的Channel源码
go tool compile - N - l - S hello . go - N表示禁用优化 - l禁用内联 - S打印结果。原创 2022-09-17 08:59:23 · 439 阅读 · 1 评论 -
GoLang之channel数据结构及阻塞、非阻塞操作、多路select
需要分别记录读,写 下标的位置,当读和写不能立即完成时,需要能够让当前协程在channel上等待,待到条件满足时,要能够立即唤醒等待的协程,所以要有两个等待队列,分别针对读和写。同样的,常规recv操作,会被编译器转换为对runtime.chanrecv1()的调用,而它内部只是调用了runtime.chanrecv(),comma ok风格的写法会被编译器转换为对runtime.chanrecv2()的调用,它的内部也是调用chanrecv() 只不过比chanrecv1()多了一个返回值。原创 2022-09-16 09:27:03 · 1703 阅读 · 0 评论 -
GoLang之图解channel之读、写、关闭
非阻塞式的读操作,会被编译器转换为对runtime.selectnbrecv或selectnbrecv2的调用,而它们也仅仅是调用了runtime.chanrecv,只不过在需要等待时会返回false。那么selectnbsend函数就会返回false,对应的协程也就不会进到sendq里等待了。runtime.selectnbsend也仅仅是调用了runtime.chansend,但是会标记上非阻塞。所以,如果缓冲区指望不上,又没有谁在等着recv,又或者channel为nil…sendq里等待的协程,原创 2022-09-15 11:02:50 · 203 阅读 · 0 评论 -
Golang之Defer必掌握的7知识点
触发panic(“panic”)后defer顺序出栈执行,第一个被执行的defer中 会有panic(“defer panic”)异常语句,这个异常将会覆盖掉main中的异常panic(“panic”),最后这个异常被第二个执行的defer捕获到。原创 2022-09-13 19:48:17 · 164 阅读 · 0 评论 -
GoLang之unsafe分析
如果接下来需要对b进行修改,那么这样转换就没什么问题,但是如果只是因为类型不合适,并不需要对转换后的变量做任何修改,那这样转换就显得不划算了。我们知道,[]byte和string的内存布局如下图所示:可以看到它们都有一个底层数组来存储变量数据,而类型本身只记录这个数组的起始地址。如果采用强制类型转换的方式把a转换为b,那么就会重新分配b使用的底层数组。然后把a的底层数组内容拷贝到b的底层数组。如果字符串内容很多,多占用这许多字节的内存不说,还要耗费时间做拷贝,所以就显得很不合适了。原创 2022-09-13 11:06:22 · 758 阅读 · 1 评论 -
GoLang之深度剖析channel的底层实现
Go语言有个很出名的话是“以通信的手段来共享内存”,channel就是其最佳的体现,channel提供一种机制,可以同步两个并发执行的函数,还可以让两个函数通过互相传递特定类型的值来通信make(chan int) // 无缓存 chan make(chan int , 10) // 有缓存 chan十分简洁的做到了不同协程的交互。综上分析,在使用channel有这么几点要注意1.确保所有数据发送完后再关闭channel,由发送方来关闭2.不要重复关闭channel。原创 2022-09-12 19:40:21 · 177 阅读 · 0 评论 -
GoLang之切片并发安全问题
关于切片的,Go语言中的切片原生支持并发吗?原创 2022-09-12 17:53:32 · 511 阅读 · 0 评论 -
GoLang之并发机制以及它所使用的CSP模型
Golang中channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的,在实现原理上其实类似一个阻塞的消息队列。当正在运行的G0阻塞的时候(可以需要IO),会再创建一个线程(M1),P转到新的线程中去运行。原创 2022-09-12 14:44:28 · 469 阅读 · 0 评论 -
GoLang之GMP调度器原理
假定当前除了M3和M4为自旋线程,还有M5和M6为空闲的线程(没有得到P的绑定,注意我们这里最多就只能够存在4个P,所以P的数量应该永远是M>=P, 大部分都是M在抢占需要运行的P),G8创建了G9,G8进行了阻塞的系统调用,M2和P2立即解绑,P2会执行以下判断:如果P2本地队列有G、全局队列有G或有空闲的M,P2都会立马唤醒1个M和它绑定,否则P2则会加入到空闲P列表,等待M来获取可用的p。最关键的是,程序员看不到这些底层的细节,这就降低了编程的难度,提供了更容易的并发。实现了线程M1的复用。原创 2022-09-12 11:34:56 · 151 阅读 · 0 评论 -
Golang底层原理剖析之内存逃逸
静态分配到栈上,性能一定比动态分配到堆上好。底层分配到堆,还是栈。实际上对你来说是透明的,不需要过度关心。每个 Go 版本的逃逸分析都会有所不同(会改变,会优化)。直接通过 go build -gcflags ‘-m -l’ 就可以看到逃逸分析的过程和结果。到处都用指针传递并不一定是最好的,要用对。栈上分配的内存不需要GC处理堆上分配的内存使用完毕会交给GC处理逃逸分析目的是决定内分配地址是栈还是堆逃逸分析在编译阶段完成。原创 2022-09-11 16:49:47 · 245 阅读 · 0 评论 -
Golang底层原理剖析之闭包
第一:必须要有在函数外部定义,但在函数内部引用的“自由变量”第二:脱离了形成闭包的上下文,闭包也能照常使用这些自由变量函数create的返回值是一个函数,但这个函数内部使用了外部定义的变量c,即使create执行结束,通过f1和f2依然能正常调用这个闭包函数。并使用定义在create函数内部的局部变量c,所以这里符合闭包的定义。通常称这个变量c为捕获变量。闭包函数的指令自然也是在编译阶段生成,但因为每个闭包对象都要保存自己的捕获变量。所以要到执行阶段才创建对应的闭包对象。原创 2022-09-11 16:16:31 · 251 阅读 · 0 评论 -
Golang底层原理剖析之defer
defer信息会注册到一个链表,而当前执行的goroutine持有这个链表的头指针,每个goroutine在运行时都有一个对应的结构体g,其中有一个字段指向defer链表头,defer链表,链起来的是一个个_defer结构体,新注册的defer会添加到链表头,执行时也是从头开始,所以defer才会表现为倒序执行。deferproc函数执行时,需要堆分配一段空间,用于存放_defer结构体,以及后面siz大小的参数与返回值,然后这个结构体就被添加到defer链表头,deferproc注册结束。原创 2022-09-10 17:32:36 · 335 阅读 · 0 评论 -
GoLang之sync.Map底层
Go语言的sync包中提供了一个开箱即用的并发安全版 map——sync.Map。开箱即用表示其不用像内置的 map 一样使用 make 函数初始化就能直接使用。同时sync.Map内置了诸如Store、Load、LoadOrStore、Delete、Range等操作方法。原创 2022-09-10 10:01:38 · 162 阅读 · 0 评论 -
GoLang之bytes.Builder底层
bytes.buffer是一个缓冲byte类型的缓冲器bytes.Buffer 是 Golang 标准库中的缓冲区,具有读写方法和可变大小的字节存储功能。缓冲区的零值是一个待使用的空缓冲区。注:从 bytes.Buffer 读取数据后,被成功读取的数据仍保留在原缓冲区,只是无法被使用,因为缓冲区的可见数据从偏移 off 开始,即buf[off : len(buf)]。原创 2022-09-10 09:39:19 · 219 阅读 · 0 评论 -
GoLang之strings.Builder底层
与 bytes.Buffer 类似,strings.Builder 也支持 4 类方法将数据写入 builder 中。有了它们,用户可以根据输入数据的不同类型(byte 数组,byte, rune 或者 string),选择对应的写入方法。原创 2022-09-09 10:36:20 · 328 阅读 · 0 评论 -
GoLang之切片7连问
因为切片发生了扩容,函数外的切片指向了一个新的底层数组,所以函数内外不会相互影响,因此可以得出一个结论,当参数直接传递切片时,如果指向底层数组的指针被覆盖或者修改(重分配、append触发扩容),此时函数内部对数据的修改将不再影响到外部的切片,代表长度的len和容量cap也均不会被修改。切片是对数组的抽象,因为数组的长度是不可变的,在某些场景下使用起来就不是很方便,所以Go语言提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素。原创 2022-09-08 21:33:26 · 129 阅读 · 0 评论