golang面试 - channel全解析

channel 什么情况下会出现panic 1 2 3 1. 试图往已close的channel继续发送 2. 试图关闭已关闭的channel 3. 试图关闭一个nil的channel 向为nil的channel发送数据会怎样 1 2 3 给定一个 nil channel c: <-c 从 c 接收将永远阻塞 c <- v 发送值到 c 会永远阻塞 channel close后读的问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 关闭后继续读数据,已在缓冲区内的可以读取到数据,而后得到的是零值(对于int,就是0)。 z 我们可以通过for循环遍历channel,来获取到已经写入的值 for _, v := range channel{ } 对于可能获取到零值,我们可以使用断言判断,ok为false代表channel已关闭后读取的 value, ok := <- channel 也可以在close channel之后有意将channel 置为 nil, 如此再使用,并不会读取到零值 对于不带缓冲的ch,和带缓冲的一样,channel close掉之后并不影响读,只影响写入 channel的底层结构 底层结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type hchan struct { qcount uint // total data in the queue 代表 chan 中已经接收但还没被取走的元素的个数。内建函数 len 可以返回这个字段的值。 dataqsiz uint // size of the circular queue 循环队列的大小 buf unsafe....

十一月 4, 2022 · nobject

golang面试 - string与[]byte的区别,如何互相转化

string是一个8位字节的集合,通常但不一定代表UTF-8编码的文本。string可以为空,但是不能为nil。string的值是不能改变的。string类型本质也是一个结构体,底层本质就是一个byte类型的数组。 byte就是uint8的别名,它是用来区分字节值和8位无符号整数值 string与[]byte的区别 对于[]byte与string而言,两者之间最大的区别就是string的值不能改变。string在底层都是结构体stringStruct{str: str_point, len: str_len},string结构体的str指针指向的是一个字符常量的地址, 这个地址里面的内容是不可以被改变的,因为它是只读的,但是这个指针可以指向不同的地址。 string的好处:以在不加锁的控制下,多次使用同一字符串,在保证高效共享的情况下而不用担心安全问题 对于[]byte来说,以下操作是可行的: 1 2 b := []byte("hello world") b[1] = 'i' string修改操作是被禁止的: 1 2 s := "hello world" s[1] = 'i' string支持这样的操作: 1 2 s := "hello world" s = "iello world" string与[]byte的互相转化 string的底层数据结构类型: 1 2 3 4 type stringStruct struct { str unsafe.Pointer len int } []byte对应的底层数据结构(本质上是个slice): 1 2 3 4 5 type slice struct { array unsafe.Pointer len int cap int } 两者对应的结构非常相似。...

十一月 4, 2022 · nobject

golang面试 - 米哈游第三方编制一面复盘

代码题 以n个协程,去输出[]string{“a”,“b”,“c”,“d”,“e”, “f”, “g”}。 代码题真得很简单,自己手生的很,竟然没写出来。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func main() { var wg sync.WaitGroup sem := make(chan struct{}, 5) // 创建一个大小为5的信号量 letters := []string{"a", "b", "c", "d", "e", "f", "g"} for _, letter := range letters { sem <- struct{}{} // 尝试写入信号量,如果已满,则阻塞 wg.Add(1) go func(l string) { defer wg.Done() fmt.Println(l) time.Sleep(5 * time.Second) <-sem // 认领信号量 }(letter) } wg....

十一月 4, 2022 · nobject

golang面试 - 深拷贝与浅拷贝

深拷贝是指两个变量,如果其内部引用其他的地址,那么深拷贝会将其内部的指针所指向的值重新分配地址用于存储这个值,因此他们内部的一些引用指针会变化。 浅拷贝是指两个变量,实际上其内部引用部分的地址并没有变化。 常见的浅拷贝,像slice的赋值,map的赋值 1 2 s := []int{1,2,3} s1 = s 如上所示,s1实际上就是对s的浅拷贝 对于深拷贝,我们在代码里应该这么使用 1 2 3 s := []int{1,2,3} var s1 []int copy(s, &s1)

十一月 4, 2022 · nobject

golang 面试 - gin

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 协程和线程的区别 goroutine初始栈大小,上下文如何切换,存在哪里 - 初始栈:2kb GMP说一下,P有多少个,G有多少个,M有多少个。系统调用的时候M会如何,网络IO的时候M会怎样。 - P的个数默认等于cpu个数,可以通过GOMAXPROCS环境变量更改 - M的默认值10000,go程序启动时,会设置M的最大数量,默认10000....

十一月 4, 2022 · nobject

golang 面试 - 读写锁

golang中读写锁的原理 1 2 3 4 5 6 7 8 9 10 11 Go 标准库中的 RWMutex 设计是 Write-preferring(写优先) 方案。 如果已经有一个 writer 在等待请求锁的话,它会阻止新来的请求锁的 reader获取到锁,所以优先保障 writer。 当然,如果有一些 reader 已经请求了锁的话,新请求的 writer 也会等待已经存在的 reader 都释放锁之后才能获取。 所以,写优先级设计中的优先权是针对新来的请求而言的。这种设计主要避免了 writer 的饥饿问题。 Go RwMutex使用了readerCount记录目前读请求的总数量 将readerCount进行取反操作 这也是此字段除了标记reader数量的第二个功能,进行写锁标记 此时将取反的r值交给readerWait代表仍需要等待释放锁的reader的数量 如果该数量为0 那么代表不需要等待则直接获取写锁即可,否则就将writer挂起阻塞直至readerWait中的所有读请求全部释放掉,然后RUlock唤醒该写请求 写锁释放时,会将readerCount再加回来,所以负的代表有写请求在,正的代表只有读请求 如果你遇到可以明确区分 reader 和 writer goroutine 的场景,且有大量的并发读、少量的并发写,并且有强烈的性能需求,你就可以考虑使用读写锁 RWMutex 替换 Mutex。 Read-preferring:读优先的设计可以提供很高的并发性,但是,在竞争激烈的情况下可能会导致写饥饿。这是因为,如果有大量的读,这种设计会导致只有所有的读都释放了锁之后,写才可能获取到锁。 Write-preferring:写优先的设计意味着,如果已经有一个 writer 在等待请求锁的话,它会阻止新来的请求锁的 reader获取到锁,所以优先保障 writer。当然,如果有一些 reader 已经请求了锁的话,新请求的 writer 也会等待已经存在的 reader 都释放 锁之后才能获取。所以,写优先级设计中的优先权是针对新来的请求而言的。这种设计主要避免了 writer 的饥饿问题。 不指定优先级:这种设计比较简单,不区分 reader 和 writer 优先级,某些场景下这种不指定优先级的设计反而更有效,因为第一类优先级会导致写饥饿,第二类优先级可能...

十一月 4, 2022 · nobject

golang 面试 - 互斥锁, 互斥锁的前世今生

坑:Unlock 方法可以被任意的 goroutine 调用释放锁,即使是没持有这个互斥锁的 goroutine,也可以进行这个操作。这是因为,Mutex 本身并没有包含持有这把锁的 goroutine 的信息,所以,Unlock 也不会对此进行检查。Mutex 的这个设计一直保持至 今。 互斥锁是怎么实现的 1 2 3 4 5 如果锁是空闲状态且没等待的goroutine,直接获取锁 正常模式: 当前的`goroutine`会与被唤醒的`goroutine`进行抢锁,如果锁未抢到,则会进入自旋状态,自旋多次后,还未竞争到锁,如果是第1次未获取到锁,则加入到等待队列的尾部,如果超过阈值1毫秒,那么,将这个Mutex设置为饥饿模式。 饥饿模式:饥饿模式下,`mutex`将锁直接交给等待队列的最前面的`goroutine`,新来的`goroutine`不会尝试获取锁,即使锁没有被持有,也不会去抢,也不会`spin`,会加入到等待队列的尾部. 如果当前等待的`goroutine`是最后一个`waiter`,没有其他等待的`goroutine` 或者此`goroutine`等待的时间小于1ms,退出饥饿模式。 初版(最普通的做法) 数据结构 1 2 3 4 type Mutex struct { key int32 // 锁是否被持有的标识 sema int32 // 信号量专用,用以阻塞/唤醒goroutine } 具体实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 // 保证成功在val上增加delta的值 func xadd(val *int32, delta int32) (new int32) { for { v := *val if cas(val, v, v+delta) { return v + delta } } panic("unreached") } // 请求锁 func (m *Mutex) Lock() { if xadd(&m....

十一月 4, 2022 · nobject

golang 面试 - 内存模型

通俗定义 1 2 内存模型描述的是并发环境中多goroutine 读相同变量的时候,变量的可见性条件,具体点说,就是指,在什么条件下, goroutine 在读取一个变量的值的时候,能够看到其它 goroutine 对这个变量进行的写的结果。 Go内存模型限定了一些条件 满足这些条件 才能让变量 安全地在不同的goroutine之间读写。换句话说就是如何保证在一个 goroutine中看到在另一个goroutine修改的变量的值,如果程序中修改数据时有其他 goroutine 同时读取,那么必须将读取串行化。为了串行化访问,请使用 channel 或其他同步原语,例如 sync 和 sync/atomic 来保护数据。 happens before 谁前谁后 多个go routine的可见性问题,如果存在多个go routine访问数据的时候,必须序列化访问 单个go routine当中编写 的代码 总是会按照我们的编写代码的顺序来执行 当然这是符合我们的习惯的 当然这并不表示编辑器在编译的时候不会对我们的程序进行指令重排 而是说只会在不影响语言规范的情况下对go routine的行为定义的时候,编辑器和CPU才会对读取与写入的顺序进行重排 happens before(先行发生)的定义 1 2 3 4 5 6 7 当下面条件满足时,对变量 v 的读操作 r 是被允许看到对 v 的写操作 w 的: 1. r 不先行发生于 w 2. 在 w 后 r 前没有对 v 的其他写操作 为了保证对变量 v 的读操作 r 看到对 v 的写操作 w,要确保 w 是 r 允许看到的唯一写操作。即当下面条件满足时,r 被保证看到 w: 1....

十一月 4, 2022 · nobject

golang面试 - gc

相关文章 https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/ https://www.cnblogs.com/luozhiyun/p/14564903.html https://www.kancloud.cn/aceld/golang/1958308#Go_V13mark_and_sweep_21 https://www.bilibili.com/video/BV1wz4y1y7Kd?p=14&spm_id_from=pageDriver https://cloud.tencent.com/developer/article/1756163 https://blog.haohtml.com/archives/26358 https://mp.weixin.qq.com/s/5xjH-LJ53XiNm2sMNQZiGQ GCMark 标记准备阶段,为并发标记做准备工作,启动写屏障 STW GCMark 扫描标记阶段,与赋值器并发执行,写屏障开启 并发 GCMarkTermination 标记终止阶段,保证一个周期内标记任务完成,停止写屏障 STW GCoff 内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭 并发 GCoff 内存归还阶段,将过多的内存归还给操作系统,写屏障关闭 并发 Go 语言中对 GC 的触发时机存在两种形式: 主动触发,通过调用 runtime.GC 来触发 GC,此调用阻塞式地等待当前 GC 运行完毕。 被动触发,分为两种方式: 使用系统监控,当超过两分钟没有产生任何 GC 时,强制触发 GC。 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。

十一月 4, 2022 · nobject

golang面试 - gmp

相关文章 https://mp.weixin.qq.com/s/jIWe3nMP6yiuXeBQgmePDg https://tonybai.com/2017/06/23/an-intro-about-goroutine-scheduler/ https://tonybai.com/2020/03/21/illustrated-tales-of-go-runtime-scheduler/ https://draveness.me/golang/docs/part3-runtime/ch06-concurrency/golang-goroutine/ https://www.kancloud.cn/aceld/golang/1958305#GoroutineGMP_164 https://segmentfault.com/a/1190000023869478 https://www.luozhiyun.com/archives/448 https://zboya.github.io/post/go_scheduler/ https://www.bilibili.com/video/BV19r4y1w7Nx?p=2&spm_id_from=pageDriver https://mp.weixin.qq.com/s/XiqVIR3U5ZmRD7xwJZKipA 进程,线程,协程的区别 进程: 进程是程序的一次执行过程,是程序在执行过程中的分配和管理资源的基本单位,每个进程都有自己的地址空间,进程是系统进行资源分配和调度的一个独立单位。 每个进程都有自己的独立内存空间,由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。 线程:在同一个进程中的不同线程共享相同的地址空间(虚拟内存,代表切换不需要切换页表),每个线程有独立的栈区,程序计数器(执行指令的信息),栈指针以及函数运行的寄存器。 初始栈大小8M,ulimit - s可以查看。调度由系统调度。从用户态切换到内核态,上下文切换消耗相对大 协程:用户态线程,不由内核管理,由go的底层调度器控制,协程初始栈大小2K。由用户调度 goroutine初始栈大小,上下文如何切换,存在哪里 协程初始栈大小2K,上下文由g0去调度找到对应可以运行的goroutine,存在gorountine的gobuf中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 组织下面试的语言 gmp是go为了提高并发能力实现的协程调度模型 g代表我们常说的协程,代码里的体现就是go关键字 m代表操作系统的线程,系统最大值默认为10000 p代表逻辑处理器,默认与cpu的核数想同,当然也可以通过runtime.GOMAXPROCS()来设置p的数量,m一般需要与p关联才能执行g。 调度策略: p会切换到g0栈上,执行调度函数,查找到可执行的goroutine,然后调用gogo函数,切换到goroutine的执行流程上。 1. 每隔61次调度轮回从全局队列找,避免全局队列中的g被饿死。 2. 从p.runnext获取g,从p的本地队列中获取。 3. 调用 findrunnable 找g,找不到的话就将m休眠,等待唤醒。 findrunnable 查找G的过程: 1. 调用 runqget ,尝试从P本地队列中获取G,获取到返回 2. 调用 globrunqget ,尝试从全局队列中获取G,获取到返回 3....

十一月 4, 2022 · nobject

golang 基础面试题

Go语言中是如何实现继承的? 1 2 go语言没有像java,c++等传统的语言一样,有extends关键字用于继承 go语言实现继承可使用组合模式 1 2 3 4 5 6 type User struct { Name string } type Man struct { User } go struct 能不能比较 1 2 3 4 5 6 7 8 9 10 11 12 13 需要具体情况具体分析,如果struct中含有不能被比较的字段类型,就不能被比较,如果struct中所有的字段类型都支持比较,那么就可以被比较。 不可被比较的类型: ① slice,因为slice是引用类型,除非是和nil比较 ② map,和slice同理,如果要比较两个map只能通过循环遍历实现 ③ 函数类型 其他的类型都可以比较。 还有两点值得注意: 结构体之间只能比较它们是否相等,而不能比较它们的大小。 只有所有属性都相等而属性顺序都一致的结构体才能进行比较。 深拷贝和浅拷贝 1 2 3 4 5 6 7 8 深拷贝: 拷贝的是数据本身,创造一个新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。 实现深拷贝的方式: copy(slice2, slice1); 遍历slice进行append赋值 浅拷贝∶拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。 实现浅拷贝的方式:引用类型的变量,默认赋值操作就是浅拷贝 如slice2 := slice1 make 与 new 的区别 1 2 3 4 5 6 7 8 9 new()对类型进行内存分配,入参为类型,返回为类型的指针,指向分配类型的内存地址 make()也是用于内存分配的,但是和new不同,它只用于channel、map以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。 注意,因为这三种类型是引用类型,所以必须得初始化,但是不是置为零值,这个和new是不一样的。 通过make创建对象 make只能创建slice 、channel、 map。 new和make对比: 1)make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据; 2)new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type; 3)new 分配的空间被清零。make 分配空间后,会进行初始化; 4)make 函数只用于 map,slice 和 channel,并且不返回指针 array 与 slice的区别 go语言的引用类型有什么? 1 切片(slice)类型, map类型 ,管道(channel)类型 , 接口(interface)类型 获取不到锁会一直等待吗? 1 2 3 4 5 如果锁未被持有,直接获取锁 正常模式: 当前的goroutine会与被唤醒的goroutine进行抢锁,如果锁未抢到,则会进入自旋状态,自旋多次后,还未竞争到锁,如果是第1次未获取到锁,则加入到等待队列的尾部,如果超过阈值1毫秒,那么,将这个Mutex设置为饥饿模式。 饥饿模式:饥饿模式下,mutex将锁直接交给等待队列的最前面的goroutine,新来的goroutine不会尝试获取锁,即使锁没有被持有,也不会去抢,也不会spin,会加入到等待队列的尾部....

十一月 4, 2022 · nobject

golang 可重入锁

可重入锁的应用场景,用同一段代码的多次调用(递归调用),方法体之内有锁嵌套。golang官方并不推荐使用可重入锁。但一些面试官比较喜欢让人去手撕可重入锁的代码。一般是java面试官,因为在java中,锁的概念非常丰富, 在golang中相对来说还是比较简单且实用的 什么是可重入锁 可重入锁又称为递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法时会自动获取锁,不会因为之前已经获取过还没释放而阻塞。 举个简单的例子: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var mu = &sync.Mutex{} func A() { mu.Lock() fmt.Println("A") B() mu.Unlock() } func B() { mu.Lock() fmt.Println("B") mu.Unlock() } 以下的代码,在目前golang使用mutex的时候,会导致死锁的情况 1 fatal error: all goroutines are asleep - deadlock! 可重入锁的思路 记住持有锁的线程(golang中对应的是协程) 累计重入的次数 相关代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 type ReentrantMutex struct { mutex sync....

十一月 4, 2022 · nobject