“Go程序员面试笔试宝典”复习便签

一.逃逸分析

1.1逃逸分析是什么?

逃逸分析,主要是Go编译器用来决定变量分配在堆或者栈的手段。

区分于C/C++手动管理内存分配,Go将这些工作交给了编译器。

1.2逃逸分析有什么作用

解放程序员。程序员不需要手动指定指针分配内存。

灵活的内存管理。编译器机制可以高效管理内存。

1.3逃逸分析是怎么完成的

编译器根据变量是否被外部引用,决定是否发生逃逸。

如果不被外部引用,则发生逃逸;反之,则发生逃逸。

1.4如何确定是否发生逃逸

运行/编译时,可以指定参数进行查看

1.5Go与C/C++中的堆和栈是同一个概念吗

不是。

C/C++中的堆栈,就是操作系统中传统的堆栈概念。

Go语言中则不同。Go语言中,操作系统中的栈,都提供给了Go运行时,用来处理调度器、垃圾回收、系统调用等。对于用户态的Go代码,消耗的都是操作系统中的堆内存,只是构造出了逻辑上不同的“堆”和“栈”。

二.延迟语句

2.1延迟语句是什么?

延迟语句(defer)是Go语言中注册延迟调用的机制,主要用于成对操作时,如文打开文件/关闭文件、打开连接/关闭连接、加锁/释放锁。

2.2延迟语句的执行顺序是什么?

defer语句会将调用函数压入栈中,先进后出的执行。

在defer函数定义时,对外部变量的引用有2种形式:

1.函数参数:defer定义时会把值传递给defer,并被cache起来。 2.闭包引用:真正执行时根据上下文确定参数值。

2.3如何拆解延迟语句

return xxx

相当于:

1.返回值=xxx

2.调用defer函数

3.return

2.4如何确定延迟语句的参数

判断依据主要与2.2的规则一样。

2.5闭包是什么?

匿名函数也被称为闭包,一个闭包继承了函数声明时的作用域,Go语言中所有的匿名函数都是闭包。

2.6延迟语句如何配合恢复语句

recover()函数只在defer的函数中直接调用才有效。

2.7 todo

2.8为什么无法从父goroutine恢复子goroutine的panic

设计使然。goroutine被设为为一个独立的代码执行单元,拥有自己的执行栈,不与其他goroutine共享任何数据。这意味着,无法让goroutine拥有返回值、也无法让goroutine拥有自身的ID编号等。

三、数据容器

3.1数组与切片

3.1.1数组和切片有何异同

数组是定长的,长度是类型的一部分,所以表达能力有限,在Go语言中不常见。

切片则非常灵活,可以动态扩容,且切片的类型和长度无关。

*底层数组可以被多个切片同时指向,因此对一个切片的元素进行操作,有可能会影响到其他切片。

3.1.2切片如何被截取

基于已有slice创建新slice对象,被称为reslice。新老slice共用底层数组,它们对底层数组的更改都会影响到彼此。

如果因为append操作引起了新slice或者老slice底层数组扩容,则不会相互影响。

这其中的关键就是:两者是否共用底层数组。

 

func main (){ slice := []int{0,1,2,3,4,5,6,7,8,9} s1 := slice[2:5] s2 := s1[2:6:7] s2 = append(s2,100) s2 = append(s2,200) s1[2] = 20 }

 

s1:[2 3 20] s2:[4 5 6 7 100 200] slice:[0 1 2 3 4 5 6 7 100 9]

3.1.3 切片的容量是怎样增长的?

下面的说法是不准确的:

1.当原slice容量小于1024的时候,新slice容量变成原来的2倍

2.当原slice容量超过1024,新slice容量变成原来的1.25倍

实际是:

扩容过程对newcap进行了内存对齐,而这个和内存分配策略有关。进行内存对齐后,新s的容量要大于等于老s容量的2倍或者1.25倍。

3.1.4切片作为函数参数会被改变吗?

slice作为函数参数时,就是一个普通的结构体。直接传slice,实参不受影响;传入slice的指针,则会影响。

Go语言中的函数参数传递,只有值传递,没有引用传递。

3.1.5 内建函数make和new的区别是什么

1.make和new都用来分配内存,但适用类型不同。make适用于slice、map、channel等引用类型,new适用于int、数组、结构体等值类型。

2.make返回一个值,new返回一个指针。

3.make返回初始化之后的类型的引用,new会为类型的新值分配已置零的内存空间,并返回指针。

3.2散列表map

  3.2.1map是什么

map最主要的数据结构有两种:哈希查找表(Hash table)、搜索树(search tree)

哈希查找表用一个哈希函数将key分配到不同的bucket桶,开销主要是哈希函数的计算以及数组的常数访问时间。处理碰撞的方法一般有:链表法和开放地址法。

搜索树一般采用平衡搜索树,包括AVL树、红黑树等。

  3.2.2map的底层原理是什么?

  Go语言使用的是哈希查找表,并且使用链表法解决哈希冲突。

  1.map内存模型

  map的结构体是hmap。关键字段有:

  B:buckets数组的长度的对数,即buckets数组的长度为2^B,bucket里面存储了key和value,bucket是一个指针,指向的是一个结构体:bmap。

  bmap就是人们常说的“桶”,桶里最多装8个<key,value>。

  这些key之所以会落入同一个桶,是因为它们的hash结果是“一类”的,并不是完全相等。hash值的高8位,决定落入桶内的槽位。

每个bucket设计成最多只能放8个key-value对,如果有第9个key-value落入当前的bucket,则需要再构建一个bucket,并通过overflow指针连接起来。这就是所谓的“链表法”。

  2.创建map

创建map,就是调用makemap函数,初始化hmap的各个字段。

slice和map分别作为函数参数数时有什么区别?

在函数内部对map的操作会影响map结构体;而对slice操作则不会。

主要原因:前者是指针(*hmap),后者是结构体(slice)

  3.哈希函数

  Go会检测cpu是否支持aes,如果支持使用aes hash,否则使用memhash。

  4.key定位过程

哈希值共64个bit位(针对64位机),计算元素落入到哪个bucket,只会用到最后B个bit位。

当两个不同的key落入同一个桶中,使用链表法解决哈希冲突,即链表法:从前往后查找第一个空位。

查找时:先找到对应的桶,再去遍历桶中的所有key。如果bucket中没有找到,并且overflow不为空,则会继续在overflow bucket中寻找。

寻找某个key的底层函数是mapacess系列函数。

  5.map的赋值过程是怎样的

  向map插入或者修改key,调用的是mapassign函数。赋值操作的核心仍然是一个双层循环:外层遍历bucket和overflow bucket,内层遍历单个bucket的所有槽位。有比较重要的几点:

  1.写标志flags=1时,说明有其他协程在执行“写”操作,程序会panic,说明map不是协程安全的。

  2.map的扩容是渐进式的,定位元素到某个bucket后,需要确保这个bucket对应的老bucket已经完成了迁移过程(老bucket中的key会被分散到2个新bucket)

  3.定位元素放置的位置时,准备两个指针: inserti和insertk分别指向第一个空的tophash、第一个空闲的cell(槽)

  4.如果触发扩容,则查找定位key的过程会重新执行一次。

  5.最后会更新元素的值,如果是插入的话,map的count字段值加1,hasWriting清零。

  6.map的删除过程是怎样的

  底层执行mapdelete函数,主要逻辑:

  1.检测并发写操作

  2.计算元素的hash值,找到落入的bucket

  3.设置写标志位

  4.检测此map是否在扩容中,如果是,则触发一次搬迁。

  5.两层循环,核心是找到key的具体位置。

  6.找到对应位置后,完成清零操作。

  7.将map的count字段减1,对应位置的tophash改为emptyone。

  8.联动判断是否处理同bucket的其他槽位,emptyOne改成emptyRest的过程。

  7.map的扩容过程是怎样的(todo)
  8.map的遍历过程是怎样的(todo)

  3.2.3map中的key为什么是无序的?

  map在扩容时会触发搬迁。一个bucket中的元素会分散到2个。这个过程不能保证元素的顺序。

  3.2.4map是线程安全的吗?

  不是。如果检测到写标志flags=1则直接panic了。

  3.2.5float类型可以作为map的key吗?

  可以,但是会出现精度丢失问题。float64作为key时,会转成uint64类型,再插入key中。

  3.2.6map如何实现两种get操作

  带comma和不带comma。

  3.2.7如何比较两个map是否相等

  1.都为nil

  2.非空、长度相等,指向同一个map实体对象

  3.相同的key指向value“深度”相等。

  3.2.8可以对map的元素取地址吗?

  不能。

  3.2.9可以边遍历边删除吗?

  同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以的。

  如果存在多个读写同时进行的情况,推荐使用线程安全的sync.map。

四.通道

4.1CSP是什么

不用通过共享内存来通信,而要通过通信来实现共享内存。

大多数编程语言的并发编程模型是基于线程和内存同步访问控制。Go的并发编程模型则用goroutine和channel来替代。goroutine和线程类似,channel则和mutex类似。

4.2通道有哪些应用

1.停止信号

2.定时任务(结合time包,一般有2种做法,实现超时控制、定时任务)

3.解耦生产方和消费方

4.控制并发数(使用缓存通道)

4.3通道的底结构#todo

  4.3.1数据结构

  4.3.2创建过程

  4.3.3接收过程

  4.3.4发送过程

  4.3.5收发数据的本质

4.4通道的关闭过程发生了什么#todo

4.5从一个关闭的通道里仍然能读出数据吗

从一个有缓冲的channel里读数据,当channel被关闭,已然能读出有效值,只有当返回的ok值为false时,读出的数据才是无效的。

如果是无缓冲的呢?

4.6如何优雅的关闭通道

所谓的优雅关闭channel,就是不关闭channel,让GC代劳。

4.7关于通道的happens-before有哪些?

定义:假设事件a和事件b存在happened-before关系,那么a/b完成后的结果也一定要体现这种关系。

由于现代编译器、CPU会做各种优化,包括编译器重排、内存重排等,在并发代码里,happened-before限制就非常重要了。

4.8通道在什么情况下会引发资源泄露

goroutine操作channel后,处于发送或者接收阻塞状态,而channel处于满或空的状态,一直得不到改变,垃圾回收器并不会回收此类资源。

如果一个channel,没有任何goroutine引用,GC会对其进行回收操作,不会引起内存泄露。

4.9通道的操作情况总结

操作

nil chan

closed chan

not nil & not closed

close

panic

panic

正常关闭

读<-ch

阻塞

对应类型的0值

1.正常读取。

2.缓冲型channel为空阻塞

无缓存型channel无发送者时阻塞

写ch<-

阻塞

panic

1.正常写入

2.缓冲型channel满时阻塞

非缓冲型channel无接收者时阻塞

五.接口

5.1Go接口与C++接口有何异同

Go采用的是“非侵入式”不需要显式声明,只需要定义接口定义的函数,编译器就会自动识别。

5.2Go语言与“鸭子类型”的关系

Go不要求类型显式的声明实现了某个接口,只要实现了相关地方法即可。

5.3iface和eface的区别是什么

iface和eface都是Go中描述接口的底层结构体,区别在于iface描述的接口包含方法,而eface则是不包含任何方法的空接口。

 

type iface struct { tab *itab //接口的类型以及赋值给这个接口的实体类型《动态类型》 data unsafe.Pointer // 指向接口具体的值《动态值》 } type itab struct { inter *interfacetype // 接口类型 _type *_type // 赋值给这个接口的实体类型 ... } type interfacetype struct { typ _type pkgpath name // 接口的包名 mhdr []imethod // 接口定义的函数列表 }

 

type eface struct { _type *_type // 空接口承载的实体类型 data unsafe.Pointer // 具体的值 }

Go语言中各种数据类型都是在_type字段的基础上,增加一些额外的字段来进行管理。

主要包括:类型大小、类型的hash值、内存对齐相关、类型的编号以及GC相关的字段。

5.4值接收者和指针接收者的区别

函数添加一个接收者,它就变成了方法。接收者可以是值接收者,也可以是指针接收者。

实现了接收者是值类型的方法,相当于是自动实现了接收者是指针类型方法。

实现了接收者是指针类型的方法,不会自动生成接收者是值类型的方法。

使用指针作为方法的接收者的理由如下:

1.方法能够修改接收者指向的值

2.避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。

5.5如何用interface实现多态

多态,可以让一种类型具有多种类型的能力。

5.6接口的动态类型和动态值是什么

iface包含两个字段:tab是接口表指针,指向类型信息;data是数据指针。分别被称为动态类型和动态值。

 

var c coder var g *Gopher c = g fmt.Println(c == nil) // false

5.7接口转换的原理是什么

当判定一种类型是否满足某个接口时,Go将类型的方法集和接口所需的方法集进行匹配。

如果类型的方法集,完全包含接口的方法集,则可认为该类型实现了该接口。

5.8类型转换和断言的区别是什么

类型转换、类型断言本质都是把一个类型转换成另外一个类型。不同之处在于,类型断言是对接口变量进行的操作。

对应类型转换,转换前后的两个类型要相互兼容才行。

因为空接口interface{}没有定义任何函数,因此Go中所有类型都实现了空接口。

当一个函数的形参是interface{},那么在函数中,需要对形参进行断言,从而得到它的真实类型。

Go语言中的switch仅执行第一个匹配成功的分支,不需要break语句;另外case不需要是常量,也不必是整数。

fallthrough关键字,表示需要执行下一个分支。

5.9如何让编译器自动检测类型是否实现了接口

var _ io.Writer = (*myWriter)(nil)

六.unsafe

  6.1如何利用unsafe包修改私有成员

对于一个结构体,通过offset函数可以获取结构体成员的偏移量,进而获取成员的地址,读写该地址的内存,就可以达到改变成员值的目的。

结构体被分配一块连续的内存,结构体的地址,也代表了第一个成员的地址。

  6.2如何利用unsafe获取slice和map的长度

  主要通过unsafe.Pointer和uintptr进行转换。

  6.3如何实现字符串和byte切片的零复制转换

 

type StringHeader stuct { Data uintptr Len int } type SliceHeader stuct { Data uintptr Len int Cap int }

只需要共享底层Data和Len就可以实现zero-copy。

七.context

7.1context是什么

context是goroutine的上下文,在goroutine中传递上下文信息,包含:取消信号、超时时间、截止时间、k-v等

7.2context有什么作用

使用context的几点建议:

1.不要讲context塞到结构体里,而是作为第一参数,一般命名为ctx 2.不用向函数传入一个含nil属性的context,可以使用todo代替

3.不用把业务参数塞到context中

4.context是并发安全的

7.3如何使用context

1.传递共享的数据

2.定时取消

3.防止goroutine泄露

7.4context底层原理是什么

主要包含Context和Canceler两个接口。

1.context定义了4个方法,都是冪等的

2.Deadline()返回context的截止时间,决定是否进行后续操作

3.Done()返回一个channel,表示context被取消的信号

4.Err()返回一个错误,表示channel被关闭的原因。例如被取消还是超时

5.Value()获取之前设置的key对应的value

源码中有2个类型实现了canceler接口:*cancelCtx、*timerCtx,注意是指针类型。

设计原因主要是:

  1.“取消”操作应该是建议性,而非强制性

  2.“取消”操作应该可传递

  八、错误

  8.1接口error是什么

  Go使用error类型表示错误,是一个接口类型。

  最简单的是errors.New(),如果需要具体的上下文信息,可以使用fmt.Errorf()

  8.2接口error有什么问题

  Go代码里error满天飞,显得非常冗长拖沓。

  8.3如何理解关于error的三句谚语

  1.视错误为值

  处理error的方式分为三种。

  Sentinel errors:哨兵。处理流程停止。最大的问题在于定义error和使用error的包之间建立了依赖关系,容易引起循环调用。(不推荐)

  Error Types:自定义Error类型,在error基础上,附带其他字段,外层调用者需要使用类型断言来判断错误。也存在循环调用的问题。(不推荐)

  Opaque errors:黑盒error。能知道错误发生了,但是无法看到它内部到底是什么,不知道它的具体类型。

  一旦出错,直接返回错误;否则继续后面的流程。

  如果调用者需要判断返回的错误类型,可以判断错误是否具有某种行为,或者说实现了某个接口。

  这样做的好处是:不需要import引用定义错误的包,并且不需要知道error的具体类型,只需要判断它的行为。即:面向接口编程。

  2.检查并优雅的处理错误

  Go1.13之前使用github.com/pkg/errors,使用Wrap可以将一个错误,加上一个字符串,“包装”成一个新的错误。Cause则是反向操作,将里层的错误还原。

  3.只处理一次错误

  避免函数内和函数外的调用者都处理错误。

  8.4错误处理的改进

  Go1.13支持了error包裹(wrapping),fmt.Errorf增加了%w的格式,并且在error包增加了三个函数:errors.Unwrap、errors.Is、errors.As。

  fmt.Errorf使用%w来生成一个嵌套的error

  Unwrap将嵌套的error解析出来

  Is判断err和target是同一类型,或者error嵌套的error有没有和target同一类型。

  As从错误链中找到第一个和target相等的值,并且设置target指向的变量为err。

九、计时器

  9.1Timer底层数据结构为什么用四叉堆而非二叉堆

  四叉堆和二叉堆本质上没有区别,它使得整体上层数更低,且时间复杂度从O(log2N)降到O(log4N)

  9.2Timer曾做过哪些重大的改进

  9.3定时器的使用场景有哪些

  1.固定时间间隔触发

  2.固定时间间隔重复触发

  3.在某个具体时刻触发

  9.4Timer/Ticker的计时功能有多准确

  影响时间准确性的元素

  1.对系统时间的依赖程度:只能依靠操作系统或者时间提供方(通常认为精度在毫秒级)

  2.对运行时的依赖程度:由于运行时组件的存在,这个时间管理的准确性也将或多或少受到一定程度的影响,例如调度器的调度延迟、垃圾回收器的干扰、操作系统对应用程序进行中断产生的延迟等。(当系统出现可感知的延迟时,可以着重调试运行时本身对延迟的影响,如:调度器任务的数量、Timer/Ticker的密度和垃圾回收器的压力)

  9.5定时器的实现还有哪些方式

  定时器可以使用链表、堆、红黑树等数据结构,也可以使用时间轮实现。流行的高效定时器有三种:Go使用的堆结构、nginx使用的红黑树、linux kernel使用的时间轮。

  十.反射

  10.1反射是什么

  Go语言提供了一个机制,在允许时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的类型,这就是反射机制。

  10.2什么情况下需要使用反射机制

  使用反射的常用场景有以下两种:

  1.不能明确接口调用哪个函数,需要根据传入的参数在运行时决定。

  2.不能明确传入函数的参数类型,需要在运行时处理任意对象。

  不推荐使用反射的原因:

  1.代码经常难以阅读

  2.编译器能提前发现一些类型错误,但对反射代码无能为力。

  3.性能影响较大

  10.3Go语言如何实现反射

  反射是通过接口的类型信息实现的,建立在类型的基础上。

  反射主要与interface{}有关。

  接口变量,可以存储任何实现了接口定义的所有方法的变量。

  GO语言reflect包里定义了一个接口和一个结构体,即reflect.Type和reflect.Value,它们提供很多函数来获取存储在接口里的类型信息。

  reflect.Type提供关于类型相关的信息,和_type关联比较紧密。

  reflect.Value则结合_type和data两者,因此可以获取并改变类型的值。

  reflect包提供了两个基础的关于反射的函数,来获取上述的接口和结构体:TypeOf、ValueOf

  TypeOf函数用来提取一个接口中值的类型信息。

  ValueOf函数返回一个结构体变量,包含类型信息以及实际值。

  反射三大定律

  1.接口类型变量,可以转化成反射类型对象(反射类型对象,指reflect.Type、reflect.Value)

  2.反射类型对象,可以转化成接口类型变量

  3.如果想要操作原变量,反射变量Value必须要持有原变量的地址才行。

  10.4如何比较两个对象是否完全相同

  Go语言中提供了DeepEqual()函数进行比较。参数是两个interface。

  如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是深度相等。

  10.5如何利用反射实现深度拷贝

  浅拷贝:只复制指向某个对象的指针,而不复制对象本身,新旧对象中指针类型的字段还是共享同一块内存。深拷贝:创造一个内容完全相同的对象,新对象与原对象不共享内存,修改新对象不影响原对象。

  实现深度拷贝可以存在多种形式,最简单、最安全也是最容易的方式是使用json.Marshal/Unmarshal。但是涉及到序列化和反序列化,性能较差。通过反射可以实现更高效的深度复制。

  十一.同步模式

    11.1等待组sync.Waitgroup的原理是什么

      sync.Waitgroup可以达到并发goroutine的执行屏障的效果。

      当需要对一个并行执行的代码块引入等待条件时,便可以使用Add操作来产生同步记录;而当不再需要等待条件时,则可在并发代码块中使用Done操作来促成同步屏障条件的达成。

      Waitgroup的内部结构非常简单,内部由3个uint32来对并发的goroutine进行不同目的的计数,分别是运行计数、等待计数和信号计数。并通过state()函数来消除在高层实现上的差异,返回状态(运行计数和等待计数)和信号(信号计数)。

    11.2缓存池sync.pool

    大量重复的创建很多对象,会引起GC的工作量飚升,这时可以使用sync.Pool来缓存对象,减轻对GC的消耗。

    11.3并发安全散列表sync.Map

    Sync.Map是线程安全的,读取、插入、删除也都保持着常数级的时间复杂度。

    Sync.Map的零值是有效的,并且零值是一个空的map,它在第一次使用后,不允许被复制。

    Sync.Map使用非常简单,和普通map相比,仅遍历的方式略有不同。

      sync.Map数据结构:

      mu Mutext 保护read和dirty字段

      read是atomic.Value类型,可以并发地读。

      dirty是一个非线程安全的原始map。

  十二、调度机制

    12.1goroutine和线程有什么区别

  1.内存消耗:goroutine的栈内存消耗为2kb,创建线程需要1MB栈内存

  2.创建和销毁:线程的创建和销毁消耗巨大,是内核级的,通常使用线程池提高复用;goroutine由go runtime负责管理,创建和消费的消耗非常小,是用户级的。

  3.切换:线程切换需要保存各种寄存器,以便恢复;goroutine切换只需保存三个寄存器:PC、Stack Pointer和BP。

    12.2 Go sheduler是什么

    Go程序的执行有两个层面,Go program和Runtime。

  Go schedule是Go运行时最重要的部分。Runtime维护所有的goroutine,并通过schedule进行调度。

  对操作系统而言,只有线程的概念,并不感知goroutine。

  有3个基础的结构体来实现goroutine的调度:G、P、M。

  G:代表goroutine,表示goroutine栈的一些字段等。

  M:代表内核线程,包含正在运行的goroutine等字段

  P:代表一个虚拟的Processor,维护一个处于Runnable状态的goroutine队列。M需要获得P才能运行G。

  还有一个核心结构体:sched,总览全局,负责整个调度器的运行。

  Runtime起始时,会启动一些G:垃圾回收的G,执行调度的G,运行用户代码的G;并且创建一个M用来开始G的运行。

  Go schedule会启动一个后台线程sysmon,来检测长时间(超过10ms)运行的goroutine,将其“停靠”到global runqueues。

  初始化时,Go程序会有一个G,G在M上得到执行,内核线程是在CPU核心上调度,G则在M上进行调度。

  此外,还有两个比较重要的组件:全局可运行队列(GRQ)和本地可运行队列(LRQ)。

  和线程类似,goroutine的状态也有3种:Waiting(等待状态)、Runnable(就绪状态)、Executing(运行状态)。

  12.3goroutine的调度时机有哪些

  4种情形下,goroutine可能会发生调度,但也并不是一定发生。分别是:使用go关键字、GC、系统调用、内存同步访问。

  12.4 M:N模型是什么

  Go runtime会在程序启动后,“按需”创建N个线程,之后创建M个goroutine会依附在N个线程上执行。

  12.5 工作窃取是什么

  Go schedule的职责就是将所有处于runnable的goroutine均匀调度到在P上运行的M。

  当一个P发现自己的LRQ已经没有G时,会从其他P“偷”一些G来运行,这被称为“工作窃取”。

  Go schedule每一轮调度要做的工作,就是找到处于runnable的goroutine,并执行它。顺序如下:

  1.从LRQ中找

  2.从GRQ中找

  3.从netpoll里找

  4.从其他P偷取

  12.6 GPM底层数据结构是怎样的

  G:取的是goroutine的首字母,主要保持goroutine的一些状态信息,以及CPU的一些寄存器的值。G关联了两个比较重要的结构体,stack表示goroutine运行时的栈,gobuf保存PC、SP等寄存器的值。

  M:取的是machine的首字母,代表一个工作线程,或者说系统线程。G需要调度到M上才能运行,M是真正工作的实体。m结构体保存了M自身需要使用的栈信息、正在M上执行的G信息、与之绑定的P信息等。

  P:取processor的首字母,为M的执行提供“上下文”,保存M执行G时的一些资源,例如本地可运行G队列,memeory cache等。一个M只有绑定P才能执行goroutine,当M被阻塞时,整个P会被传递给其他M。

  12.7schedule的初始化过程是怎样的

  Go scheduler在源码中的结构体为schedt,保存调度器的状态信息、全局的可运行G队列等。

  12.9g0栈和用户栈如何被切换

  g0栈用于执行调度器的代码,它选择一个可运行的goroutine,之后跳转到执行用户代码的地方。如何跳转,这中间涉及栈和寄存器的切换。函数调用和返回主要靠的也是CPU寄存器的切换,goroutine的切换和此类似。

  12.13M如何找工作

  1.从本地队列找、2.定期从全局队列找、最后从别的P偷取。

  12.14系统监控sysmon后台监控线程做了什么

  1.抢占处于系统调用的P,让其他M接管它,以运行其他的goroutine

  2.将运行时间过长的goroutine调度出去,给其他goroutine运行的机会

  十三、内存分配机制

    13.1管理内存的动机是什么,通常涉及哪些组件

    内存管理的动机:性能要求、解放程序员

    内存管理运行时的组件:

    1.页分配器:从操作系统申请内存

    2.对象分配器:为用户程序分配内存

    3.垃圾回收期:回收用户程序所分配的内存

    4.拾荒器:向操作系统归还申请的内存

    从运行时对内存的管理角度来看,内存有4种状态:空状态(None)预留态(Reserved)准备态(Prepared)以及就绪态(Ready)

    13.2Go语言中的堆和栈概念与传统意义上的堆和栈有什么区别

    运行时中的mheap结构存储了整个go堆的管理状态,涉及页分配器和对象分配器。从两个分配器视角可以将内存考虑为两种不同的粒度单位。

    页分配器:堆是按照连续的页进行管理。

    对象分配器:以跨度(span)进行管理。一个跨度以mspan结构进行存储,每个跨度可以存储多个分配的对象,并由多个连续的页组成。

    13.3对象分配器是如何实现的

    分配的基本策略:顺序分配和自由表分配两大策略。

    顺序分配:直接从一段连续空间的一端开始,按需逐次将内存分配给用户程序。(Go堆内存使用)

    自由表分配:使用链表结构来维护未分配的内存,进行串联管理。(运行时对象所在的非托管内存使用)

    对象分配的缓存分为两个:

    1.本地跨度缓存:不需要分配新的跨度 2.中枢跨度缓存:需要分配新的跨度

    _type是Go类型的实现,通过size属性可以获得该类型对应的大小。对象分配的流程分为三种基本情况:

    1.微对象分配:针对小于16B的对象分配请求

    2.小对象分配:针对大小介于16B和32KB之间的分配请求

    3.大对象分配:针对大于32KB的对象分配请求

十四、垃圾回收机制

14.1垃圾回收的认识

垃圾回收,是一种自动内存管理的机制。

当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收,并供其他代码申请内存复用,或者将其归还给操作系统,这个过程称为垃圾回收。

垃圾回收器的执行过程被划分为两个半独立的组件:

1.赋值器:代指用户态的代码,对垃圾回收器而言,用户态的代码只修改对象之间的引用关系。

2.回收器:负责执行垃圾回收的代码。

垃圾回收器在标记过程中,最先检查的对象包括:全局变量、执行栈、寄存器。

GC算法的存在形式,可以归结为:追踪和引用计数这两种形式的混合运用。

追踪式GC:从根对象出发,根据引用关系逐步扫描,确定保留的对象,从而回收所有可回收的对象。

引用计数式GC:每个对象自身包含一个被引用的计数器,计数器归零时自动回收。

三色标记法是什么?

关键是理解三色抽象以及波面推进两个概念。

三色抽象规定了三种不同类型的对象,并以不同颜色相称。

1.白色对象:未被回收器访问,初始颜色,回收结束后,均不可达。

2.灰色对象:已被回收器访问,可能指向白色对象。

3.黑色对象:已被回收器访问,所有字段已被扫描,黑色对象中任何一个指针都不可能指向白色对象。

STW是什么?

stop the world,也可以是start the world。从stop the world到start the world的这段时间间隔。

垃圾回收过程,为了保证实现的正确性,防止无止境的内存增长。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/89082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql 8.0 窗口函数 之 序号函数 与 sql server 序号函数 一样

sql server 序号函数 序号函数 ROW_NUMBER() 顺序排序RANK() 并列排序&#xff0c;会跳过重复的序号&#xff0c;比如序号为1&#xff0c;1&#xff0c;3DENSE_RANK() 并列排序&#xff0c;不会跳过重复的序号&#xff0c;比如 序号为 1&#xff0c;1&#xff0c;2 语法结构…

《Zookeeper》源码分析(二十)之 Follower

目录 Follower创建Follower实例followLeader()findLeader()connectToLeader()registerWithLeader()syncWithLeader() FollowerZooKeeperServer Follower Leader选举结束后&#xff0c;成为Follower的服务器开始进行Follower的工作&#xff0c;过程如下&#xff1a; 与Leader…

汉诺塔问题--夏令营

题目 tips&#xff1a; 1.本题只用多试几次&#xff0c;由数据推导规律即可 2.汉诺塔问题分析 这里的递归函数是&#xff08;n,a,b,c&#xff09;指n个盘子从a移到c&#xff0c;且凭借b 递归边界是n1 原始思想&#xff1a;要想把n个盘子从a移到c,若n1则直接move a到c n>…

【LUBAN】【功能验证】至简投屏功能之Android有线连接方式测试

1、概述 至简投屏功能之Android有线连接方式支持至简自带应用至加的投屏功能和谷歌官方的Android auto功能。 支持的功能范围列举如下&#xff1a; 1、屏幕投屏&#xff08;支持自动旋转屏&#xff09;2、音视频播放&#xff08;抖音、百度地图等&#xff09;3、车机反控手机…

vue中使用echarts三维的项目

需要安装 echarts 同时引入 echarts-gl 我安装的版本&#xff1a; "echarts": "^5.3.2", "echarts-gl": "^2.0.9", 效果 &#xff1a; 安装后main.js引入 import Vue from "vue"; import * as echarts from "echart…

车联网技术介绍

上图是目前车联网架构图&#xff0c;基于“云-管-端”的车联网系统架构以支持车联网应用的实现&#xff0c; “云”是指 V2X 基础平台、高基于精度定位平台等基础能力&#xff0c;可实现车辆动态厘米级定位&#xff0c;这将满足现阶段以及未来车联网应用场景的定位精度需求。 “…

滑动验证组件---设置movable-view组件的x属性在微信小程序端失效的问题

场景 采用uniapp的movable-view组件实现滑动验证组件。 流程 滑块未滑到最右端时&#xff0c;回弹到原点滑块滑到最右端时&#xff0c;则显示滑动结束&#xff0c;不可再滑动 问题 频繁设置uniapp的movable-view组件的x属性&#xff0c;在H5端正常&#xff0c;但在微信小程…

Linux —— 进程间通信(System V)

目录 一&#xff0c;共享内存 申请共享内存 shmget 控制共享内存 shmctl 关联共享内存 shmat / 去联共享内存 shmdt 二&#xff0c;消息队列 创建或打开消息队列 msgget 发送消息 msgsnd / 接收消息 msgrcv 控制消息 msgctl 三&#xff0c;信号量 创建或打开信号量 s…

【esp32】GPIO引脚功能使用集合

本文主要介绍 esp32 这块芯片的GPIO 口功能使用以及软硬件设计注意事项 &#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#x1f4dd; 个人主页&#xff1a;欢迎访问我的 Ethernet_Comm 博客主页…

Mac操作系统Safari 17全新升级:秋季推出全部特性

苹果的内置浏览器可能是Mac上最常用的应用程序&#xff08;是的&#xff0c;甚至比Finder、超级Mac Geeks还要多&#xff09;。因此&#xff0c;苹果总是为其浏览器Safari添加有用的新功能。在今年秋天与macOS Sonoma一起推出的第17版中&#xff0c;Safari可以帮助你提高工作效…

【HCIP】15.MPLS基础

多协议标签交换 MPLS位于TCP/IP协议栈中的数据链路层和网络层之间&#xff0c;可以向所有网络层提供服务。 通过在数据链路层和网络层之间增加额外的MPLS头部&#xff0c;基于MPLS头部实现数据快速转发。 术语 MPLS域&#xff08;MPLS Domain&#xff09;&#xff1a;一系列…

C语言学习笔记(完整版)

文章目录 算法算法的基本概念算法的特征算法的优劣 描述算法三种基本结构流程图N-S流程图伪代码 常量和变量了解数据类型常量整形常量实型常量字符型常量转义字符符号常量 变量整形变量实型变量字符型变量 表达式与运算符赋值运算符和赋值表达式变量赋初值强制类型转换 算术运算…

行式存储与列式存储

1.概述 数据处理大致可分为两大类&#xff0c;联机事务处理OLTP(on-line transaction processing) 和联机分析处理OLAP(on-line analytical processing)。 OLTP是传统关系型数据库的主要应用&#xff0c;用来执行一些基本的、日常的事务处理&#xff0c;比如数据库记录的增、删…

LAMP配置与应用

web资源类型&#xff1a; 静态资源&#xff1a;原始形式与响应内容一致&#xff0c;在客户端浏览器执行 动态资源&#xff1a;原始形式通常为程序文件&#xff0c;需要在服务器端执行之后&#xff0c;将执行结果返回给客户端 LAMP架构组成&#xff1a; L&#xff1a;linux …

翻倍以链表形式表示的数字

题目&#xff1a; 示例&#xff1a; 思路&#xff1a; 有点相似于&#xff1a;链表相加II&#xff0c;这道题我们仍然有进位&#xff0c;但不同的是&#xff0c;链表相加我们选择了开辟新节点&#xff0c;这道题我们选择反转两次链表&#xff0c;开始一次&#xff0c;结束一次…

测试工具coverage的高阶使用

在文章Python之单元测试使用的一点心得中&#xff0c;笔者介绍了自己在使用Python测试工具coverge的一点心得&#xff0c;包括&#xff1a; 使用coverage模块计算代码测试覆盖率使用coverage api计算代码测试覆盖率coverage配置文件的使用coverage badge的生成 本文在此基础上…

CrystalNet .Net VCL for Delphi Crack

CrystalNet .Net VCL for Delphi Crack VCL或更为人所知的可视化组件库是基于一个面向对象的框架&#xff0c;什么是用户对开发人员和事件的Microsoft Windows应用程序的接口。可视化组件库是用对象Pascal编写的。它主要是为使用Borland而开发的&#xff0c;它具有与Delphi以及…

Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)

默认Excel/PowerPoint折线图是这个样子的&#xff1a; 左右两侧都留了大块空白&#xff0c;很难看 解决方案 点击横坐标&#xff0c;双击&#xff0c;然后按下图顺序点击 效果

自动设置服务器全教程

亲爱的爬虫探险家&#xff01;在网络爬虫的世界里&#xff0c;自动设置代理服务器是一个非常有用的技巧。今天&#xff0c;作为一家代理服务器供应商&#xff0c;我将为你呈上一份轻松实用的教程&#xff0c;帮助你轻松搞定爬虫自动设置代理服务器。 一、为什么需要自动设置代…