Momenta 实习一面
实习一面已过
make 和 new的区别
特性 |
|
|
用途 | 分配内存并初始化为零值 | 创建和初始化切片、映射和通道 |
返回值 | 返回指向类型的指针 | 返回初始化后的数据结构本身 |
适用类型 | 任何类型 | 仅限于切片、映射和通道 |
内存分配方式 | 在堆上分配内存 | 在堆或栈上分配内存(取决于数据结构) |
示例 |
|
|
初始化状态 | 初始化为零值 | 初始化为有效的切片/映射/通道 |
在 Go 中,new
和 make
的主要区别在于它们用于不同的类型和场景。
首先,new
是一个内置函数,它用于分配内存并初始化为零值。它返回的是一个指向类型的指针。比如说,如果你有一个 int
类型,使用 new(int)
会返回一个指向新分配的 int
的指针,初始值为 0
。可以想象成你在堆上申请了一块内存,但你只得到了内存的地址。
而 make
则是专门用于初始化切片(slice)、映射(map)和通道(channel)这三种内置数据结构。它不仅分配内存,还会初始化这些数据结构的内部状态。比如,如果你用 make([]int, 10)
,这会创建一个长度为 10 的切片,并且每个元素都初始化为 0
。所以,make
更像是一个“构造器”,确保你的数据结构是可以直接使用的。
总结一下,new
用于分配并返回类型的指针,适用于任何类型;而 make
只用于切片、映射和通道,负责创建和初始化这些特定的数据结构。
如何判断make 和new 执行失败
在 Go 语言中,new
和 make
的执行通常不会直接失败,因为它们都是内置函数,不会返回错误。但是,在特定情况下,我们仍然可以通过一些间接的方式判断内存分配是否成功或有效。以下是一些判断的思路:
1. 使用 new
的情况
- 返回值检查:虽然
new
返回一个指向类型的指针,但通常你只需使用返回值。由于 Go 的内存管理是自动的,new
不会失败,但可以检查指针是否为nil
。不过,在正常情况下,new
不会返回nil
。
ptr := new(int) if ptr == nil { fmt.Println("内存分配失败") }
2. 使用 make
的情况
- 返回值检查:与
new
类似,make
也不会直接失败。但你可以检查返回的切片、映射或通道的长度或状态。比如,对于切片,可以检查其长度是否为 0。
slice := make([]int, 10) if len(slice) == 0 { fmt.Println("切片创建失败") }
- 内存限制:在内存使用极限的情况下,例如,当程序试图分配过大的切片或映射时,可能会引发运行时错误(如“out of memory”)。这种情况通常通过捕获 panic 来处理。
defer func() { if r := recover(); r != nil { fmt.Println("内存分配失败:", r) } }() slice := make([]int, 1e12) // 尝试分配一个非常大的切片
3. 一般建议
- 监控内存使用:使用工具监控应用的内存使用情况,以便在高负载或大量内存分配时及早发现问题。
- 异常处理:对于任何可能导致内存溢出的操作,使用
recover
来捕获 panic,避免程序崩溃。
讲一讲defer 关键字
口语化解释
在 Go 语言中,defer
关键字用于延迟一个函数的执行,直到包含它的函数执行完毕时。这意味着你可以在函数的最后一行调用 defer
,确保在函数退出之前执行某些清理操作,比如关闭文件、释放锁或处理其他资源。defer
语句会按后进先出(LIFO)的顺序执行,也就是说,最后一个被 defer
的函数会最先被调用。
举个例子,如果你打开一个文件,想在函数结束时确保这个文件被关闭,你可以这样做:
func readFile() { file, err := os.Open("example.txt") if err != nil { log.Fatal(err) } defer file.Close() // 确保在函数结束时关闭文件 // 读取文件的代码 }
在这个例子中,即使在读取文件的过程中发生了错误,defer
也会保证 file.Close()
被调用,从而防止资源泄漏。
表格形式
特性 | 描述 |
定义 |
|
执行时机 | 在包含它的函数返回之前执行。 |
执行顺序 |
|
用途 | 常用于清理资源,例如关闭文件、解锁互斥锁等。 |
语法示例 |
|
注意事项 |
|
性能影响 | 过多的 |
通过这两种方式,你可以清楚地理解 defer
关键字在 Go 中的作用及其使用场景。
闭包:代码输出是什么?
func a() int { x := 10 defer func() { x += 10 fmt.Println(x) // 打印 21 }() x += 1 return x } func main() { println(a()) // 打印 11 }
func a() int { x := 10 defer func(x int) { x += 10 fmt.Println(x) // 20 }(x) x += 1 return x } func main() { println(a()) // 11 }
defer 和 return 的执行
先计算return
语句中所有的表达式,然后再执行函数的defer
语句,最后才真正地返回结果。
- return 的执行时机
- return 语句用于指定函数的返回值并结束函数的执行。return 语句在遇到时立即停止函数的执行,并开始返回值的处理。
- 当函数执行到 return 语句时,返回值会被计算出来,并准备返回给调用者。
- defer 的执行时机
- defer 语句用于延迟执行某个函数,直到包含它的函数执行结束。在函数返回之前,所有被 defer 的函数会按后进先出(LIFO)的顺序执行。
- defer 语句的执行时机是在函数的 return 语句之后,但在函数真正返回之前。
讲一讲select这个关键词?
在 Go 语言中,select
是一个非常有用的关键字,主要用于处理多个通道(channel)之间的通信。你可以把 select
想象成一个交通指挥员,它可以帮助你决定从哪条通道接收数据。
主要特点:
- 多个通道的选择:
select
允许你同时等待多个通道的操作。如果其中一个通道准备好了,你就可以处理它的数据。例如,如果有两个 goroutine 向你发送消息,select
会自动帮你选择第一个到达的消息来处理。 - 随机选择:如果多个通道同时准备好,Go 会随机选择一个通道来执行。这种随机性让程序更公平,不会总是优先处理同一个通道的数据。
- 非阻塞选择:使用
select
时,如果所有的通道都没有准备好,你可以通过default
语句来避免阻塞,这样你就可以执行一些其他的操作,而不是一直等着。
特性和功能
- 多个通道操作:
- 非阻塞选择:
- 与 goroutine 的结合:
讲一讲chanal这个数据结构
实现原理
nil、关闭的 channel、有数据的 channel,再进行读、写、关闭会怎么样
在 Go 语言中,通道(channel)是一个非常重要的并发数据结构,理解它们在不同状态下的行为(例如 nil
通道、关闭的通道、有数据的通道)是非常关键的。以下是对这几种情况的详细说明:
1. nil
通道
- 定义:
nil
通道是一个未初始化的通道,通常表示该通道没有被分配任何内存。 - 读操作:从一个
nil
通道进行读取会导致阻塞,因为没有任何数据可供读取。 - 写操作:向一个
nil
通道写入数据也会导致阻塞,因为没有任何接收者。 - 关闭操作:关闭一个
nil
通道会导致运行时 panic,因未初始化的通道不能被关闭。
示例代码
var ch chan int // nil 通道 go func() { ch <- 1 // 这会导致阻塞,永远等不到接收者 }() // 或者尝试关闭 close(ch) // 会导致 panic: close of unbuffered channel
2. 关闭的通道
- 定义:关闭的通道是一个已经被调用
close()
的通道。 - 读操作:从关闭的通道读取数据时,如果通道中有数据,则会读取到最后一个值。如果通道为空,则会返回零值,并且还会返回一个
false
标志。 - 写操作:向一个关闭的通道写入数据会导致运行时 panic,因为关闭的通道不能再写入任何数据。
- 关闭操作:再次尝试关闭已经关闭的通道会导致 panic。
示例代码
ch := make(chan int) close(ch) // 关闭通道 value, ok := <-ch // 读取值,ok 为 false,value 为 0(零值) fmt.Println(value, ok) // 输出: 0 false ch <- 1 // 会导致 panic: send on closed channel close(ch) // 会导致 panic: close of closed channel
3. 有数据的通道
- 定义:有数据的通道是一个已初始化并且包含数据的通道。
- 读操作:从通道读取数据时,会返回并删除通道中的数据。读取操作是阻塞的,直到有数据可读取。
- 写操作:向通道写入数据时,如果通道有缓冲且未满,则写入操作不会阻塞。如果通道未缓冲或已经满,则写入操作会阻塞,直到有空间可用。
- 关闭操作:关闭一个有数据的通道是允许的,但必须确保所有的数据都已经被读取,否则后续的读取会返回零值。
示例代码
ch := make(chan int, 1) // 创建一个缓冲通道,大小为 1 ch <- 42 // 向通道写入数据 value := <-ch // 从通道读取数据,value 为 42 fmt.Println(value) // 输出: 42 close(ch) // 关闭通道
总结
- 从
nil
通道读取或写入 会导致阻塞。 - 向关闭的通道写入数据 会导致 panic,而 从关闭的通道读取数据 会返回零值和一个
false
标志。 - 从有数据的通道读取数据 是正常的,而 写入数据时 需要注意通道的缓冲区状态。
有缓冲channel和无缓冲channel的区别
主要区别总结
特性 | 无缓冲通道 | 有缓冲通道 |
创建时机 | 不指定缓冲区大小 | 指定缓冲区大小 |
数据传输方式 | 发送和接收是同步的 | 发送和接收是异步的 |
阻塞行为 | 发送和接收都阻塞 | 发送阻塞(直到缓冲区满) |
接收阻塞(直到缓冲区空) | ||
使用场景 | 通常用于需要严格同步的场合 | 通常用于减少阻塞,提高并发性能的场合 |
何时使用
- 无缓冲通道:适合用于需要严格同步的场合,例如,需要确保发送和接收在同一时刻发生的场合。
- 有缓冲通道:适合用于减少阻塞,提高程序性能的场合,尤其是在生产者-消费者模型中,发送者可以继续工作,而不必等待接收者处理数据。
讲一讲 map 的数据结构,如何实现的
https://golang.design/go-questions/map/principal/
map 线程安全么?不安全怎么解决
- 加锁
- sync.map
- channel
方法 | 简介 | 优点 |
互斥锁 (Mutex) | 使用 | 简单且安全,适用于对 |
读写锁 (RWMutex) | 使用 | 提高读操作频繁场景的并发性能 |
通道 (Channel) | 所有 | 避免数据竞争,适用于高安全性需求场景 |
进程、线程、协程的区别
此处为语雀内容卡片,点击链接查看:https://www.yuque.com/shianchen/hbdbh6/aielfdmw81wg2hky#du1QR
常用Linux 命令
ps、df、top、ls、kill、du、ufw、netstat、grep
#实习##Momenta##一面#