下面的程序的输出
文章目录
- 下面的程序的输出
- 资料集合
- 其他面试题
- channle
- Golang面试题
- [50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs](http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html)
- 如果让你来实现channel,你要怎么做
- http协议是怎么划分边界的
- io多路复用
- 几种常见的gc算法
- 并发和并行
- 进程,线程,协程
- goalng的sync.Mutex和sync.RWMutex
- golang的并发控制
- golang的gc算法
- golang的gmp模型
- golang的函数是传值还是传指针
- httprouter的底层结构
- 非golang面试
- 某个公司的电话面试题
- MySQL面试题
第1题
package main
func f1(){
defer println("f1 begin")
f2()
defer println("f1 end")
}
func f2(){
defer println("f2 begin")
f3()
defer println("f2 end")
}
func f3(){
defer println("f3 begin")
panic(0)
defer println("f3 end")
}
func main(){
f1()
}
- 输出:
f3 begin
f2 begin
f1 begin
panic: 0 - 如果没有panic:
f3 end
f3 begin
f2 end
f2 begin
f1 end
f1 begin
第2题
package main
import "fmt"
type Object interface {}
type Reader interface {
ReadToBuffer()
Buffer()string
}
type read struct {
buffer string
}
func (r read) ReadToBuffer(){
r.buffer = "hello"
}
func (r read) Buffer() string{
return r.buffer
}
func main(){
var r = &read{}
doSomething(r)
r.ReadToBuffer()
buf := r.Buffer()
fmt.Println("main,buf = ", buf)
}
func doSomething(obj Object){
reader,ok := obj.(Reader)
if !ok{
panic("invalid type")
}
reader.ReadToBuffer()
buf := reader.Buffer()
fmt.Println("doSomething,buf = ", buf)
}
输出:
doSomething,buf =
main,buf =
原因
方法的receiver不是引用,所以这里只是copy
How to set and get fields in struct’s method
第3题
和第2题结合起来看,来源于上一题的链接,这里
receiver
是指针,虽然传入的不是指针,但是golang会自动转换
package main
import "fmt"
type Foo struct {
name string
}
// SetName receives a pointer to Foo so it can modify it.
func (f *Foo) SetName(name string) {
f.name = name
}
// Name receives a copy of Foo since it doesn't need to modify it.
func (f Foo) Name() string {
return f.name
}
func main() {
// Notice the Foo{}. The new(Foo) was just a syntactic sugar for &Foo{}
// and we don't need a pointer to the Foo, so I replaced it.
// Not relevant to the problem, though.
p := Foo{}
p.SetName("Abc")
name := p.Name()
fmt.Println(name)
}
输出
Abc
第4题
package main
import (
"fmt"
"unsafe"
)
func main(){
s := make([]byte,200)
ptr := unsafe.Pointer(&s[0])
fmt.Printf("%T\n",s)
fmt.Printf("%T\n",ptr)
s1 := ((*[1<<10]byte)(ptr))[:200]
fmt.Printf("%T,%d,%d\n",s1,len(s1),cap(s1))
var sl = struct {
addr uintptr
len int
cap int
}{uintptr(ptr),200,512}
s2 := *(*[]byte)(unsafe.Pointer(&sl))
fmt.Printf("%T,%d,%d\n",s2,len(s2),cap(s2))
}
输出:
[]uint8
unsafe.Pointer
[]uint8,200,1024
[]uint8,200,512
第5题
package main
import (
"fmt"
"time"
)
var (
sem = make(chan int,5)
)
type Request int
func init(){
for i:=0;i<5;i++{
sem<-1
}
}
func handle(r Request){
<-sem
process(r)
sem<-1
}
func process(r Request){
fmt.Println("process:",r)
}
func serve(queue chan Request){
for{
req,ok := <-queue
if ok{
go handle(req)
}else{
break
}
}
time.Sleep(2*time.Second)
}
func main(){
queue := make(chan Request)
go func() {
for i:=0;i<5;i++{
time.Sleep(1*time.Second)
queue <- Request(i)
}
close(queue)
}()
serve(queue)
}
输出:
process: 0
process: 1
process: 2
process: 3
process: 4
这个题不知道要考啥…
第6题
package main
import "fmt"
func main(){
fmt.Println("f():", f())
fmt.Println("f1():", f1())
fmt.Println("f2():", f2())
}
func f()(res int){
defer func(){
res++
}()
return 0
}
func f1()(res int){
t:=5
defer func(){
t += 5
}()
return t
}
func f2()(res int){
defer func(res int){
res += 5
}(res)
return 1
}
输出:
f(): 1
f1(): 5
f2(): 1
第7题
package main
import "fmt"
type People struct{}
func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}
type Teacher struct {
People
}
func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}
func main() {
t := Teacher{}
t.ShowA()
}
输出:
showA
showB
第8题
package main
import (
"fmt"
)
func main() {
const (
a = 0.1
b = 0.2
c = 0.3
)
fmt.Println(a+b == c)
var (
a1 = 0.1
b1 = 0.2
c1 = 0.3
)
fmt.Println(a1+b1 == c1)
}
输出
true
false
原因
In a related way, floating-point constants may have very high precision, so that arithmetic involving them is more accurate. The constants defined in the math package are given with many more digits than are available in a float64.
来源:The Go BlogConstants
第9题
package main
import (
"fmt"
)
func main() {
b := []int{1}
notChange(b)
fmt.Printf("经历过notChange函数后b = %+v\n",b)
b = []int{1}
change(b)
fmt.Printf("经历过change函数后b = %+v\n",b)
}
func notChange(s []int){
s = append(s,3)
fmt.Println("in notChange, after append ", s)
s = []int{4,5}
fmt.Println("in notChange, after assign ", s)
}
func change(s []int){
s[0]=100
fmt.Printf("in change, s = %+v\n", s)
}
输出:
in notChange, after append [1 3]
in notChange, after assign [4 5]
经历过notChange函数后b = [1]
in change, s = [100]
经历过change函数后b = [100]
原因:
append和直接赋值导致底层数组改变,所以没有改变实参
第10题
package main
import "fmt"
func main() {
f1,f2:=A(10)
f1()
f2()
}
func A(x int)(func(),func()){
return func(){
fmt.Println("1",x)
x+=10
},func(){
fmt.Println("2",x)
}
}
输出:
1 10
2 20
原因:
就是闭包
第11题
package main
func main() {
t()
}
func t()(result []bool){
result[0]=false // panic
// result = append(result,false) //
return result
}
原因:
返回命名参数对于slice来说在函数体里面是不用make,但是像第一行那样是不行的,第二行是可以的
第12题
package main
import "fmt"
func main() {
var a interface{}
fmt.Printf("a == nil is %t\n", a == nil)
var b interface{}
var p *int = nil
b = p
fmt.Printf("b == nil is %t\n", b == nil)
}
/*
输出:
a == nil is true
b == nil is false
*/
- 原因():
第13题
package main
import (
"fmt"
)
const (
a=iota
b
c=20
d // d=iota
e
f
)
func main() {
fmt.Println(a,b,c,d,e,f)
}
输出: 0 1 20 20 20 20
d=iota, 输出: 0 1 20 3 4 5
第14题
package main
import "fmt"
func main() {
a:=[]int{1,2,3,4}
m:=make(map[int]*int)
for index,value:=range a{
m[index]=&value
}
for k,v:=range m{
fmt.Printf("%v->%v\n",k,*v)
}
}
- 输出:
1->4
2->4
3->4
0->4 - 解析: https://www.jianshu.com/p/b64221beb2f2
- 另一个类似的
func main() { arr := []int{1, 2, 3} newArr := []*int{} for _, v := range arr { newArr = append(newArr, &v) } for _, v := range newArr { fmt.Println(*v) } }
第14题
package main
import "fmt"
import "strings"
func main() {
// If s does not contain sep and sep is not empty, Split returns a slice of length 1 whose only element is s
ret := strings.Split("", " ")
fmt.Println(len(ret), ret)
}
package main
import "strings"
import "fmt"
func main() {
a:=strings.Split("abc", "d")
fmt.Println(len(a),a) // 1 [abc],
}
strings.Split an empty string should get a zero length slice, but the return slice’s len is 1
资料集合
- Go 语言问题集(Go Questions)
- 被问过的问题:
- Golang 面试题搜集
- 被问过的问题
- 简单聊聊内存逃逸? 里的几种类型,其中fmt.Println也会内存逃逸
- 比较惊讶的
- 对已经关闭的的chan进行读写,会怎么样?为什么? 这个答案,确实是对的,关闭了只要还有就可以读到
- 被问过的问题
- 超全golang面试题合集+golang学习指南+golang知识图谱+成长路线
其他面试题
channle
- 在nil的channel上发送和接收:阻塞,Send and receive on a nil channel in Go (Golang)
- 在已关闭的channel上发送:panic
- 如何优雅地关闭Go channel
- 在使用Go channel的时候,一个适用的原则是不要从接收端关闭channel,也不要关闭有多个并发发送者的channel
- 关闭已经关闭的channel会panic
Golang面试题
50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs
如果让你来实现channel,你要怎么做
http协议是怎么划分边界的
io多路复用
- 定义:
IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操 作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程
- 又叫事件驱动
- 三种方式: select, poll, epoll
- 区别:
- 出现顺序: select,poll,epoll
- select的缺点:
1. 单个进程所打开的FD是有限制的,通过FD_SETSIZE设置,默认1024 2. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 3. 对socket扫描时是线性扫描,采用轮询的方法,效率较低(高并发时)
- poll对select的改进:
poll与select相比,只是没有fd的限制,其它基本一样
- epoll
- 只能工作在linux下
- 不是轮询,是事件回调,O(1)
- [Windows IOCP模型与Linux EPOLL模块之比较](Windows IOCP模型与Linux EPOLL模块之比较)
- 参考资料:
几种常见的gc算法
- 参考资料:常见gc算法
并发和并行
- 并发: 在操作系统中,某一时间段,几个程序在同一个CPU上运行,但在任意一个时间点上,只有一个程序在CPU上运行
- 并行: 当操作系统有多个CPU时,一个CPU处理A线程,另一个CPU处理B线程,两个线程互相不抢占CPU资源,可以同时进行,这种方式成为并行
- 参考:【面试高频问题】线程、进程、协程
进程,线程,协程
- 对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。
- 线程从属于进程,是程序的实际执行者。一个进程至少包含一个主线程,也可以有更多的子线程。
- 进程拥有代码和打开的文件资源、数据资源、独立的内存空间
- 直白地讲,进程就是应用程序的启动实例
- 无论进程还是线程,都是由操作系统所管理的。
- 协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程
- 协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)
- 协程的开销远远小于线程的开销
- 参考资料:
goalng的sync.Mutex和sync.RWMutex
- sync.RWMutex: 读写互斥,读读共享,写写互斥。
- sync.Mutex: 全部互斥
golang的并发控制
- 全局变量加锁
- channel
- waitgroup
- context
- 参考资料:Go 并发控制
golang的gc算法
- 三色标记+写屏障
- 三色标记
程序创建的对象都标记为白色。 gc开始:扫描所有可到达的对象,标记为灰色 从灰色对象中找到其引用对象标记为灰色,把灰色对象本身标记为黑色 监视对象中的内存修改,并持续上一步的操作,直到灰色标记的对象不存在 此时只剩白色和黑色,清理白色
- 写屏障
- 该屏障之前的写操作和之后的写操作相比,先被系统其它组件感知。
- 通俗的讲:就是在gc跑的过程中,可以监控对象的内存修改,并对对象进行重新标记。(实际上也是超短暂的stw,然后对对象进行标记)
- gc一旦开始,无论是创建对象还是对象的引用改变,都会先变为灰色
- 三色标记
golang的gmp模型
- 协程由用户态调度是协作式的
- M是与内核线程关联的
- G代表goroutine
- P是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列
- goroutine可能切换的点
- I/O,select
- channel
- 等待锁
- runtime.Gosched()
- 参考
全局队列里的goroutine什么时候得到执行
- 线程想运行任务就得获取 P,从 P 的本地队列获取 G,P 队列为空时,M 也会尝试从全局队列拿一批 G 放到 P 的本地队列,或从其他 P 的本地队列偷一半放到自己 P 的本地队列
golang的函数是传值还是传指针
- golang的函数只有传值:Passing by reference and value in Go to functions
httprouter的底层结构
非golang面试
TCP主动断开连接会出现什么状态
交换机和路由器的区别
- 工作层次不同:
交换机主要工作在数据链路层(第二层)
路由器工作在网络层(第三层)。 - 转发依据不同:
交换机转发所依据的对象时:MAC地址。(物理地址)
路由转发所依据的对象是:IP地址。(网络地址) - 主要功能不同:
交换机主要用于组建局域网,
而路由主要功能是将由交换机组好的局域网相互连接起来,或者接入Internet。
交换机能做的,路由都能做。
交换机不能分割广播域,路由可以。
路由还可以提供防火墙的功能。
路由配置比交换机复杂
OSI七层模型
- 速记
物,数,网,传,会,表,应 - 具体
第7层 应用层
第6层 表示层
第5层 会话层
第4层 传输层
第3层 网络层
第2层 数据链路层
第1层 物理层
某个公司的电话面试题
-
golang如何检查datarace的数据竞争
Use -race to enable the built-in data race detector. -
如何检查死锁
- 检查业务,确定大概的死锁位置
- 靠人工走读代码
-
如果需要作一个整数类型的计数器?
- 如果用channel好还是你说的用锁好一点
- channel的底层实现
-
实现一个客户端ping一百台服务器,最大10个并发数去ping
-
多线程不加锁的情况下,同一个线程只读写一个key,每个线程读写不同的key,会不会造成data race
-
多个线程去调同一个函数,这个函数可能是耗时的操作,第一个线程去调用了,其他线程就不要去调用,其他线程被阻塞等待,直接拿到结果,怎么实现
-
如果一个函数传递的是非指针的互斥锁,会有什么样的结果
-
如果从一个map里随机抽取3个key,概率保持一样,要怎么做
- 直接for…range遍历取3个就可以了,遍历的时候key就是随机的
- Go Map深度解析以便实现随机获取Map值(译)
-
channel,2个goroutine同时发送和接收,会发生什么?
一个协程给 channel 发消息,一个协程从 channel 接收,要求是这两个协程都准备好,才能完成收发操作。 形象一点的例子就是,快递员给你送快递,你要去拿快递,通过沟通约定了时间地点,快递员得带着快递过去,你得过去拿,这样才能完成这个流程。 而对于有缓冲的 channel,是不需要收发同步的,但是队列满了之后就和无缓冲的一样了。 所以,同时收发会直接完成这一次消息传递。非同时收发,那么发送端会被阻塞,接收端也会被阻塞。
-
map里的元素被delete后,map的内存的体积会不会立即减小
- 这个在github上是个issue: runtime: maps do not shrink after elements removal (delete)
- 什么是 map
- 解剖Go语言map底层实现
- 如果
key
是一个指针类型的,则直接将其置为空,等待GC清除; - 如果是值类型的,则清除相关内存。
- 同理,对
value
做相同的操作。 - 最后把key对应的高位值对应的数组index置为空。
- 如果
-
一个work server,每次收到一个请求,就创建一个协程去处理这个请求,发生slice,append这种情况,短时间请求特别多,这个服务器会发生什么情况
-
封装一个接口,接收2个参数,一个是名字一个是分数,要更新这个名字和分数,并返回其排名
-
CDN缓存静态资源60秒,怎么在你的服务器上操作
-
golang, windows跨平台编译linux,需要怎么操作
- CGO_ENABLED=0
- GOOS=linux
- GOARCH=amd64
-
如果有用cgo要怎么跨平台编译
-
还是int的整形计数器,加一个方法, 等待计数器变成0,不会就阻塞等待,要怎么实现
MySQL面试题
https://dongzl.github.io/2020/03/15/12-MySQL-Master-Slave-Replication/index.html
主从之间如何复制的
https://cheng-dp.github.io/2019/05/06/mysql-binlog-replica/#%E5%A4%8D%E5%88%B6%E8%BF%87%E7%A8%8B
MyISAM和Innodb的区别
几种隔离级别
- 读未提交
- 读已提交
- 可重复读
- 串行化
脏读和幻读分别出现在哪几种隔离
binlog和redolog的作用分别是啥
- MySQL日志系统:redo log、binlog、undo log 区别与作用
- redo log是属于innoDB层面,binlog属于MySQL Server层面的,这样在数据库用别的存储引擎时可以达到一致性的要求。
- redo log是物理日志,记录该数据页更新的内容;binlog是逻辑日志,记录的是这个更新语句的原始逻辑
- redo log是循环写,日志空间大小固定;binlog是追加写,是指一份写到一定大小的时候会更换下一个文件,不会覆盖。
- binlog可以作为恢复数据使用,主从复制搭建,redo log作为异常宕机或者介质故障后的数据恢复使用。
binlog在MySQL的那一层
- binlog是MySQL Server层记录的日志
- MySQL分2层server层和引擎层
- 引申问题: server层有哪些东西
- 连接器
- 查询缓存
- 分析器
- 优化器
- 执行器