Momenta 实习一面

实习一面已过

make 和 new的区别

特性

new

make

用途

分配内存并初始化为零值

创建和初始化切片、映射和通道

返回值

返回指向类型的指针

返回初始化后的数据结构本身

适用类型

任何类型

仅限于切片、映射和通道

内存分配方式

在堆上分配内存

在堆或栈上分配内存(取决于数据结构)

示例

ptr := new(int)

slice := make([]int, 10)

初始化状态

初始化为零值

初始化为有效的切片/映射/通道

在 Go 中,newmake 的主要区别在于它们用于不同的类型和场景。

首先,new 是一个内置函数,它用于分配内存并初始化为零值。它返回的是一个指向类型的指针。比如说,如果你有一个 int 类型,使用 new(int) 会返回一个指向新分配的 int 的指针,初始值为 0。可以想象成你在堆上申请了一块内存,但你只得到了内存的地址。

make 则是专门用于初始化切片(slice)、映射(map)和通道(channel)这三种内置数据结构。它不仅分配内存,还会初始化这些数据结构的内部状态。比如,如果你用 make([]int, 10),这会创建一个长度为 10 的切片,并且每个元素都初始化为 0。所以,make 更像是一个“构造器”,确保你的数据结构是可以直接使用的。

总结一下,new 用于分配并返回类型的指针,适用于任何类型;而 make 只用于切片、映射和通道,负责创建和初始化这些特定的数据结构。

如何判断make 和new 执行失败

在 Go 语言中,newmake 的执行通常不会直接失败,因为它们都是内置函数,不会返回错误。但是,在特定情况下,我们仍然可以通过一些间接的方式判断内存分配是否成功或有效。以下是一些判断的思路:

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 用于延迟函数的执行,直到包含它的函数返回。

执行时机

在包含它的函数返回之前执行。

执行顺序

defer 语句按照后进先出(LIFO)顺序执行。

用途

常用于清理资源,例如关闭文件、解锁互斥锁等。

语法示例

defer funcName(args...)

注意事项

defer 中的函数参数在声明时就会被求值,而不是在执行时。

性能影响

过多的 defer 可能会对性能产生一定影响,尤其是在循环中。

通过这两种方式,你可以清楚地理解 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语句,最后才真正地返回结果。

  1. return 的执行时机
  • return 语句用于指定函数的返回值并结束函数的执行。return 语句在遇到时立即停止函数的执行,并开始返回值的处理。
  • 当函数执行到 return 语句时,返回值会被计算出来,并准备返回给调用者。
  1. defer 的执行时机
  • defer 语句用于延迟执行某个函数,直到包含它的函数执行结束。在函数返回之前,所有被 defer 的函数会按后进先出(LIFO)的顺序执行。
  • defer 语句的执行时机是在函数的 return 语句之后,但在函数真正返回之前。

讲一讲select这个关键词?

在 Go 语言中,select 是一个非常有用的关键字,主要用于处理多个通道(channel)之间的通信。你可以把 select 想象成一个交通指挥员,它可以帮助你决定从哪条通道接收数据。

主要特点:

  1. 多个通道的选择:select 允许你同时等待多个通道的操作。如果其中一个通道准备好了,你就可以处理它的数据。例如,如果有两个 goroutine 向你发送消息,select 会自动帮你选择第一个到达的消息来处理。
  2. 随机选择:如果多个通道同时准备好,Go 会随机选择一个通道来执行。这种随机性让程序更公平,不会总是优先处理同一个通道的数据。
  3. 非阻塞选择:使用 select 时,如果所有的通道都没有准备好,你可以通过 default 语句来避免阻塞,这样你就可以执行一些其他的操作,而不是一直等着。

特性和功能

  1. 多个通道操作
  2. 非阻塞选择
  3. 与 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)

使用 sync.Mutex 锁定整个 map 的访问,确保同一时间只有一个 goroutine 可以访问

简单且安全,适用于对 map 的简单读写操作

读写锁 (RWMutex)

使用 sync.RWMutex,允许多个读操作并发,但写操作独占

提高读操作频繁场景的并发性能

通道 (Channel)

所有 map 操作通过一个 goroutine 处理,其他 goroutine 通过通道请求访问

避免数据竞争,适用于高安全性需求场景

进程、线程、协程的区别

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/shianchen/hbdbh6/aielfdmw81wg2hky#du1QR

常用Linux 命令

ps、df、top、ls、kill、du、ufw、netstat、grep

#实习##Momenta##一面#
全部评论

相关推荐

1 14 评论
分享
牛客网
牛客企业服务