go常见面试问题

506 阅读5分钟

Channel

1. channel有缓冲和无缓冲有什么区别(往channel读写过程)?
  • 无缓冲的 channel(也称为同步 channel)在发送和接收操作之前,必须有另一个 goroutine 准备好进行接收和发送,否则发送和接收操作都会阻塞当前 goroutine,直到有另一个 goroutine 准备好接收或发送数据。因此,无缓冲 channel 保证了发送和接收操作的同步性,可以用于在不同的 goroutine 之间进行数据同步。

  • 有缓冲的 channel 在创建时需要指定一个缓冲区大小,可以容纳一定数量的元素。在向有缓冲 channel 中发送数据时,如果缓冲区未满,则发送操作会立即完成,否则发送操作会阻塞当前 goroutine,直到有其他 goroutine 从 channel 中接收数据并释放出缓冲区空间。在从有缓冲 channel 中接收数据时,如果缓冲区不为空,则接收操作会立即完成,否则接收操作会阻塞当前 goroutine,直到有其他 goroutine 向 channel 中发送数据并占用缓冲区空间。因此,有缓冲 channel 可以用于实现异步的、非阻塞的数据传输,缓冲区的大小可以控制发送和接收操作之间的解耦程度。

  • 总结

    • 带缓冲区的channel: 写入阻塞条件:缓冲区满
      取出阻塞条件:缓冲区没有数据
    • 不带缓冲区的channel: 写入阻塞条件:同一时间没有另外一个线程对该chan进行读操作
      取出阻塞条件:同一时间没有另外一个线程对该chan进行取操作
2. 往已经close的channel读写会怎么样?

如果尝试向已经关闭的 channel 中发送数据,会导致 panic。如果尝试从已经关闭的 channel 中读取数据,则会立即返回已缓冲的数据(如果 channel 是有缓冲的),或者返回一个零值(如果 channel 是无缓冲的)。此外,从已经关闭的 channel 中读取数据不会阻塞当前 goroutine。关闭 channel 后,任何尝试发送数据到 channel 中的 goroutine 都会立即收到一个 panic 异常。因此,建议在关闭 channel 后不要再向其发送数据。

Var变量

3. go里面声明一个变量,它是放在栈上还是堆上
  • 对于局部变量,如果它是一个基本数据类型,比如 intfloat 等,那么它会被分配在栈上;如果它是一个复合数据类型,比如 structarrayslicemap 等,那么它的数据部分会被分配在堆上,但是指向数据的指针会被分配在栈上。
  • 对于全局变量,它们通常会被分配在静态存储区,也就是堆上。
  • 对于通过 new 或者 make 函数动态分配的变量,它们通常会被分配在堆上。例如,通过 new 函数动态分配的指针变量就是在堆上分配的。

Map

4. map是并发安全的吗?怎么实现并发安全?

在 Go 中,map 并不是并发安全的数据结构,也就是说在多个 goroutine 并发读写同一个 map 的时候,可能会发生数据竞争(data race)导致程序出现错误或者崩溃。 为了实现 map 的并发安全,可以采用以下几种方法:

  1. 互斥锁

使用互斥锁(Mutex)可以保证同一时刻只有一个 goroutine 能够访问 map。具体实现可以在每个读写操作前后加上 LockUnlock 方法。但是这种方法的效率较低,因为需要频繁地获取和释放锁,且无法做到并发读取。

  1. 读写锁

使用读写锁(RWMutex)可以实现读多写少的场景下的高效并发访问。具体实现可以在读操作前后加上 RLockRUnlock 方法,而在写操作前后加上 LockUnlock 方法。但是需要注意的是,当有写操作时,读操作会被阻塞。

由于读锁是共享的,多个 goroutine 可以同时读取 count 值,而不会发生冲突。而写锁是互斥的,只有一个 goroutine 可以写入 count 值,其他所有 goroutine 都必须等待,直到该 goroutine 释放写锁。

  1. 并发安全的 map 类型

除了自己实现并发安全的 map,也可以使用一些第三方库中已经实现好的并发安全的 map 类型,比如 sync.Mapsync.Map 是 Go 标准库中提供的并发安全的 map 类型,它通过加锁的方式实现并发安全,且支持并发读写操作,具有较好的性能和可靠性。

需要注意的是,虽然sync.Map内部采用了读写锁实现并发安全,但是它并不能保证事务的原子性,也就是说,对于多个键值对的操作,可能会发生部分操作成功、部分操作失败的情况,因此在需要原子性保证的场景下,还需要使用sync包提供的其他同步原语来实现。 主要思想:读写分离

image.png

关键说明:

  • read map 是一个只读的 map,不能往里面添加 key。而 dirty map 是一个可读写的 map,可以往里面添加 key
  • sync.Map 实现中,基本都是会先从 read map 中查找 key,如果没有找到,再从 dirty map 中查找 key。然后根据查找结果来进行后续的操作。
  • 如果 read map 中没有找到 key,需要加锁才能从 dirty map 中查找 key。因为 dirty map 是一个可读写的 map,所以需要加锁来保证并发安全。
4. 哪些数据类型不能作为map里面的key,哪些可以,有没有什么评判标准?
  • golang中能够用 == 号直接比较的数据类型

    • 数字类型:int、int8、int16、int32、int64、uint、uint8、uint16、uint32、uint64、float32、float64、complex64、complex128;
    • 布尔类型:bool;
    • 字符串类型:string;
    • 指针类型:指向同一类型的指针可以进行比较,指针类型与 nil 进行比较也是允许的;
    • 通道类型:同一类型的通道可以进行比较;
    • 接口类型:同一类型的接口可以进行比较,如果接口底层的值是可比较的,则接口也是可比较的。
  • 不能作为map key 的类型包括:

    • slices
    • maps
    • functions