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进行取操作
- 带缓冲区的channel:
写入阻塞条件:缓冲区满
2. 往已经close的channel读写会怎么样?
如果尝试向已经关闭的 channel 中发送数据,会导致 panic。如果尝试从已经关闭的 channel 中读取数据,则会立即返回已缓冲的数据(如果 channel 是有缓冲的),或者返回一个零值(如果 channel 是无缓冲的)。此外,从已经关闭的 channel 中读取数据不会阻塞当前 goroutine。关闭 channel 后,任何尝试发送数据到 channel 中的 goroutine 都会立即收到一个 panic 异常。因此,建议在关闭 channel 后不要再向其发送数据。
Var变量
3. go里面声明一个变量,它是放在栈上还是堆上
- 对于局部变量,如果它是一个基本数据类型,比如
int、float等,那么它会被分配在栈上;如果它是一个复合数据类型,比如struct、array、slice、map等,那么它的数据部分会被分配在堆上,但是指向数据的指针会被分配在栈上。 - 对于全局变量,它们通常会被分配在静态存储区,也就是堆上。
- 对于通过
new或者make函数动态分配的变量,它们通常会被分配在堆上。例如,通过new函数动态分配的指针变量就是在堆上分配的。
Map
4. map是并发安全的吗?怎么实现并发安全?
在 Go 中,map 并不是并发安全的数据结构,也就是说在多个 goroutine 并发读写同一个 map 的时候,可能会发生数据竞争(data race)导致程序出现错误或者崩溃。
为了实现 map 的并发安全,可以采用以下几种方法:
- 互斥锁
使用互斥锁(Mutex)可以保证同一时刻只有一个 goroutine 能够访问 map。具体实现可以在每个读写操作前后加上 Lock 和 Unlock 方法。但是这种方法的效率较低,因为需要频繁地获取和释放锁,且无法做到并发读取。
- 读写锁
使用读写锁(RWMutex)可以实现读多写少的场景下的高效并发访问。具体实现可以在读操作前后加上 RLock 和 RUnlock 方法,而在写操作前后加上 Lock 和 Unlock 方法。但是需要注意的是,当有写操作时,读操作会被阻塞。
由于读锁是共享的,多个 goroutine 可以同时读取 count 值,而不会发生冲突。而写锁是互斥的,只有一个 goroutine 可以写入 count 值,其他所有 goroutine 都必须等待,直到该 goroutine 释放写锁。
- 并发安全的
map类型
除了自己实现并发安全的 map,也可以使用一些第三方库中已经实现好的并发安全的 map 类型,比如 sync.Map。sync.Map 是 Go 标准库中提供的并发安全的 map 类型,它通过加锁的方式实现并发安全,且支持并发读写操作,具有较好的性能和可靠性。
需要注意的是,虽然sync.Map内部采用了读写锁实现并发安全,但是它并不能保证事务的原子性,也就是说,对于多个键值对的操作,可能会发生部分操作成功、部分操作失败的情况,因此在需要原子性保证的场景下,还需要使用sync包提供的其他同步原语来实现。
主要思想:读写分离
关键说明:
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