七叶笔记 » golang编程 » 进大厂系列-Golang基础-02

进大厂系列-Golang基础-02

想进大厂,但不知道该如何入手,不妨从先过八股文的题量开始,比如先过个50题,然后一边面,一边学,进大厂就只不过是时间问题了,加油打工人!

本篇一共10题,大概花20分钟阅读。

1.golang的switch语句有什么特点?

switch关键字是通过对比key和case后面的value来选择需要执行的语句,与其他语言比如php和java不同的是,golang的switch默认不会去执行下一个case的语句,除非你显示的添加了一行fallthough关键字。

2.golang的select当有多个goroutine准备就绪,它是如何选择的?

select语句是用来处理与channel IO相关的逻辑,当有多个channel准备就绪的时候,其是伪随机选择一个goroutine来接收,然后执行相关的语句块。

Note: select关键字常用于和goroutine超时相关的逻辑设计。

3.golang什么时候会panic?

这里总结了8种,应付面试官应该是够了

  • nil空指针异常
  • 数组切片越界
  • 向未初始化的map赋值
 var mp map[string]interface{}
mp["123"] = 123
panic: assignment to entry in nil map  
  • 并发的写map
 var mp = make(map[string]interface{})
func main() {
   // 启动两个线程不断的去写map
   go func() {
      for {
         mp["key1"] = "value1"
      }
   }()
   go func() {
      for {
         mp["key1"] = "value1"
      }
   }()
   time.Sleep(10 * time.Second)
}
fatal error: concurrent map writes  
  • 关闭未初始化的channel,重复关闭channel,向关闭的channel写数据
 var mp chan string
close(mp)
panic: close of nil channel

var mp chan string = make(chan string)
close(mp)
close(mp)
close of closed channel

var mp chan string = make(chan string)
close(mp)
mp <- "123"
panic: send on closed channel  
  • 死锁,channel只有发送而没有接收
 var mp chan string = make(chan string)
mp <- "123"
fatal error: all goroutines are asleep - deadlock!

mutex := sync.Mutex{}
mutex1 := sync.Mutex{}
go func() {
   mutex.Lock()
   time.Sleep(time.Second * 1)
   mutex1.Lock()
   mutex.Unlock()
   mutex1.Unlock()
}()

mutex1.Lock()
time.Sleep(time.Second * 1)
mutex.Lock()
mutex1.Unlock()
mutex.Unlock()
fatal error: all goroutines are asleep - deadlock!  
  • interface类型断言失败
 var a interface{}
a = 1
fmt.Println(a.(string))
panic: interface conversion: interface {} is int, not string  
  • 递归死循环,堆栈溢出

4.子协程出现panic能在父协程使用recover()捕获吗?

不能,只能在子协程内部使用recover()捕获panic,协程只能捕获自己的panic。

5.什么样的panic不可恢复

  • 并发读写map
  • 递归死循环
  • 死锁

6.defer函数的执行顺序是怎么样的?

defer函数的执行顺序和栈的入栈出栈顺序一致,先声明的后执行

7.unsafe.Pointer和uintptr是用来干什么的呢?

在golang中,为了安全性,是不允许指针像C++那样进行类型转换以及计算的,但是有些场景又必须要这么做怎么办呢?于是出现了unsafe.Pointer用于指针类型转换,比如*int64可以转换为*int64,出现了uintptr用于指针运算。

对于unsafe.Point有以下几点性质:

  • 任意类型的指针都能转化成unsafe.Point
  • unsafe.Point能转化成任意类型的指针
  • unsafe.Point可以转化成uintptr
  • uintptr可以转化成unsafe.Point
 type Student struct {
   Name string
   Age int
}

type Student_Copy struct {
   Name string
   Age int
}
student := &Student{"lzj",18}
student_Copy := (*Student_Copy)(unsafe.Pointer(student))
fmt.Println(student_Copy.Age)
output:
    18  

uintptr官方的定义是

 // uintptr is an integer type that is large enough to hold the bit pattern of
// any pointer.
uintptr是一个整数,其足够大以至于能存储任何指针所指向的地址  

其是用来做指针运算的

 student := &Student{"lzj",18}
name := (*string)(unsafe.Pointer(student))
age := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(student)) + unsafe.Offsetof(student.Age) ))
fmt.Println(*name)
fmt.Println(*age)
output:
    lzj
    18  

Note:

第三行的目的是为了获取age属性,age属性在stuct中处于第二列,首先是把student转换成unsafe.Point,获取指向student的指针,然后再转换成uintptr进行指针运算,然后通过unsafe.offset获取student.age相对于student的偏移量加上student的起始地址就能获得student.age的起始地址,然后转换成*int类型,就可以读取age属性了。

8.常用unsafe.Point和uintptr做什么呢,这么做有什么好处呢?

unsafe.Point常用于操作结构体的私有变量,以及类型转换。

好处就是golang中只有unsafe.Point能做到这个事,其他方法都做不到,反射的底层也是用unsafe.Point做的。

9.unsafe.Point和unintptr有什么坑呢?

千万要小心,不要为uintptr起一个中间变量 ,例如这样:

 u := uintptr(unsafe.Pointer(student))
age := (*int)(unsafe.Pointer(u))  

这是因为当发生gc的时候,可能会修改变量的内存地址,同时也会修改指向该变量的指针指向新的地址。但是uintptr是一个整数,其不是一个指针,因此在gc修改变量的时候,可不会修改它的值,他还指向原来的地址,然后转化成unsafe.Point进行操作,当然会报错。

加分题

10. string转byte的零拷贝技术

string在golang中的的存储结构为:

 type stringStruct struct {
    str unsafe.Pointer
    len int
}  

我们可以定义一个一样的结构体,然后用unsafe.Point把其转化成我们定义结构体,这样就可以把其私有属性,映射成共有属性了。

这个结构体golang已经帮我们定义好了,如下:

 type StringHeader struct {
   Data uintptr
   Len  int
}  

同理,slice的存储结构可以映射成,如下结构体,golang也已经帮我们定义好了

 type SliceHeader struct {
   Data uintptr
   Len  int
   Cap  int
}  

接来下就是具体的代码:

 a := "我123"
b := make([]byte, 0, len(a))

s := (*reflect.StringHeader)(unsafe.Pointer(&a))
p := (*reflect.SliceHeader)(unsafe.Pointer(&b))

p.Data = s.Data
p.Len = s.Len

fmt.Println(b)
output:
    [230 136 145 49 50 51]  

其实就是修改了一下byte切片data所指向的地址空间以及len就行了。

参考资料:

unsafe.Point

相关文章