【go】简单问答八股,go的理解,接口,锁,channel
谈谈你对go的理解,与其他语言的优势与缺陷
- 我认为go是一个非常务实的语言,语法简单直接,支持原生高并发,开发速度块,性能强于java,有接近c语言的执行效率(静态编译成机器码)部署简单高效。
- go的缺陷:
- 相较于c++不支持
函数重载,宏
,且GC在超低延迟系统(高频交易)
比不过纯手控的c++ - 错误处理繁琐,频繁if err ,不过go1.13之后引入了错误包装
- 在
科学计算和AI领域
go的第三方库不如java和python丰富
- 相较于c++不支持
go的两个接口之间可以存在什么关系?
- 2个接口有相同的方法列表,那么就是等价的,可以互相赋值
- 接口可以嵌套
互斥锁与读写锁
- 互斥锁读写都锁,读写锁,读锁锁写操作,写锁读写都锁
- 读写锁适合读多写少,互斥锁适合写操作多
package mainimport ("fmt""sync""time"
)var (num = 0// lock = sync.Mutex{} // 使用互斥锁保护共享变量 50ms+lock = sync.RWMutex{} // 使用读写锁保护共享变量 100ms+
)func main() {start := time.Now()var wg sync.WaitGroup // 定义一个 WaitGroup,用于等待所有 goroutine 完成wg.Add(1) // 增加一个计数,表示有一个 goroutine 需要完成go func() {defer wg.Done() // 在 goroutine 完成时减少计数for i := 0; i < 100000; i++ {lock.Lock()num++lock.Unlock()}}()for i := 0; i < 100000; i++ {lock.Lock()num++lock.Unlock()}wg.Wait() // 等待所有 goroutine 完成fmt.Println(num) // 打印最终的 num 值fmt.Println(time.Since(start)) // 打印程序运行耗时
}// 总结:读写锁适合读多写少
// 互斥锁适合写操作多
channel特点,需要注意什么
Channel 是 goroutine 之间主要通讯方式,通信通过 Channel 完成同步。
-
类型安全
一个 Channel 只能传递指定类型的数据,比如chan int
只能放 int。 -
分为有缓冲通道和无缓冲通道:同步 or 缓冲
- 无缓冲 channel(
make(chan int)
):- 发送和接收必须同步,发的人等收的人准备好才能发。
- 有缓冲 channel(
make(chan int, 10)
):- 可以容纳一定数量的数据,发的人可以先存进去,缓冲满了才阻塞。
- 无缓冲 channel(
-
阻塞性
- 向 无缓冲channel 发送数据,如果没人接收,会阻塞。
- 从 无缓冲channel 接收数据,如果没人发送,也会阻塞。
-
关闭特性
close(chan)
可以关闭通道。- 关闭后不能再发送数据,但可以继续接收,直到读取完毕。
- 关闭后再次发送数据,会导致panic。关闭已经关闭的通道会panic。(对策:由发送方关闭channel,可通过sync.Once确保只关闭一次)
- 关闭后接收数据,返回类型零值
-
多路复用(select)
- 可以用
select
语句同时等待多个 channel 的操作(类似多路监听)。
- 可以用
-
nil Channel行为
-
发送到nil channel会永久阻塞
-
从nil channel接收会永久阻塞
-
关闭nil channel会panic
-
无缓冲的通道是同步的,有缓冲的通道是异步的
例子
package mainimport ("fmt""sync"
)func main() {var wg sync.WaitGroup // 等待组var ch chan int // nil channelvar ch1 = make(chan int) // 创建无缓冲channelfmt.Println(ch, ch1) // <nil> 0xc000086060wg.Add(1) // 等待组的计数器+1go func() {// ch <- 15 // 如果给一个nil的channel发送数据会造成永久阻塞// <-ch // 如果从一个nil的channel中接收数据也会造成永久阻塞ret := <-ch1fmt.Println(ret)ret = <-ch1 // 从一个已关闭的通道中接收数据,如果缓冲区中为空,则返回该类型的零值fmt.Println(ret)wg.Done() // 等待组的计数器-1}()go func() {// close(ch1)// close(ch) 关闭nil channel会panicch1 <- 15 // 给一个已关闭通道发送数据就会包panic错误close(ch1) // 关闭一个已关闭的channel会panic}()wg.Wait() // 等待组的计数器不为0时阻塞
}
讲解go中new
-
new 用于为给定类型
分配内存
并进行零值初始化
,并返回
对应类型的指针
。 -
和 make 不同,
new
适用于所有类型
,而make
只适用于slice、map 和 channel
。 -
new 只做
零值初始化
,不会
为内部数据结构做额外准备
;而 make 则会初始化
并返回
一个可用
的对象
(不是指针)。
p := new(int)
// p是*int类型,指向一个值为0的int变量
- 在
实际开发
中,除了极少
数需要明确指针的场景
,更多是直接使用字面量 &T{} 来创建对象
,所以 new 在 Go 中使用得比较少
。
go中的make
-
make 只用于 slice、map、channel。
-
make 负责内存分配 + 内部数据结构初始化。
-
返回的是值(不是指针)。
Printf、Sprintf、FprintF都是格式化输出,有什么不同?
-
Printf:格式化并输出到
标准输出
(终端/控制台)。 -
Sprintf:格式化并
返回字符串
,不直接输出,常用于后续处理。 -
Fprintf:格式化并输出到
实现了 io.Writer接口的对象
(比如文件、网络连接、缓冲区等)。
io.Writer接口只需要实现一个Write方法
type Writer interface {Write(p []byte) (n int, err error)
}
示例:实现一个简单内存Writer
type MemoryWriter struct {Buffer []byte
}func (m *MemoryWriter) Write(p []byte) (n int, err error) {m.Buffer = append(m.Buffer, p...)return len(p), nil
}func main() {var mw MemoryWriterfmt.Fprintf(&mw, "Hello, %s!", "World")fmt.Println(string(mw.Buffer)) // 输出: Hello, World!
}
go语言中值传递和地址传递(引用传递)如何运行?有什么区别?举例说明
- 值传递:参数拷贝到函数内
- 引用传递: 参数地址拷贝到函数内
- go没有真正的引用传递,都是值传递