某云大厂面试之Go 实际问题及答案
本文总结了Go语言常见问题和编程技巧:1)Go调度器使用GOMAXPROCS控制并发;2)struct比较需字段可比较;3)defer在循环中多次注册;4)select用于多channel通信;5)context包管理goroutine生命周期;6)http.Client实现长连接;7)WaitGroup同步协程;8)切片共享底层数组;9)map无序需额外排序;10)用map实现set;11)ch
1. **Go的调度**
- Go使用goroutine进行并发处理,调度器通过M:N模型将goroutine调度到操作系统线程上。Go的调度是由Go运行时的调度器(scheduler)管理的,使用GOMAXPROCS来控制并发的最大CPU核数。调度器使用抢占式调度,并在goroutine之间动态分配计算资源。
2. **Go struct能否比较**
- Go中的`struct`是可以比较的,但有一些限制。结构体中的字段必须是可以比较的类型。如果结构体包含不可比较的字段(如切片、映射、函数等),则该结构体就不可比较。
3. **Go defer(for defer)**
- `defer`语句用于在函数返回前执行清理操作。对于`for defer`,如果`defer`放在`for`循环内,`defer`会延迟执行,但它会根据循环的每次迭代被调用,因此每次循环结束时都会注册一个defer,直到`for`循环结束。
4. **select可以用于什么**
- `select`语句主要用于多个`channel`之间的选择,它会等待多个`channel`中的一个准备好(发送或者接收),然后执行相应的代码块。常用于多goroutine间的同步和消息传递。
5. **context包的用途**
- `context`包用于在不同的goroutine之间传递请求作用域的数据,特别是在处理并发任务时,提供了取消信号、超时控制等功能。常用于HTTP请求、数据库操作等长时间运行的任务。
6. **client如何实现长连接**
- Go的`http`包中的`http.Client`支持持久连接(HTTP Keep-Alive)。通过复用同一个`http.Client`实例,可以实现长连接。连接会被复用,直到超时或手动关闭。
7. **主协程如何等待其余协程完再操作**
- 使用`sync.WaitGroup`可以等待多个goroutine完成。主协程调用`WaitGroup.Add(n)`来设置等待的goroutine数,其他goroutine调用`WaitGroup.Done()`来减少等待数,最后主协程调用`WaitGroup.Wait()`等待所有goroutine完成。
8. **slice,len,cap,共享,扩容**
- `len`返回切片的元素个数,`cap`返回切片的容量(最大存储空间)。切片共享底层数组,因此修改切片元素会影响到其他共享该数组的切片。切片扩容时会创建一个新的底层数组,原有数组会被拷贝到新数组中。
9. **map如何顺序读取**
- Go中的`map`是无序的,不能保证元素的顺序。如果需要有序读取,可以先将`map`的键提取到一个切片中,然后对该切片进行排序,最后根据排序的键顺序访问`map`中的元素。
10. **实现set**
- Go本身没有提供`set`类型,但可以通过`map`实现一个`set`。例如:
```go
type Set struct {
data map[int]struct{}
}
func NewSet() *Set {
return &Set{data: make(map[int]struct{})}
}
func (s *Set) Add(value int) {
s.data[value] = struct{}{}
}
func (s *Set) Contains(value int) bool {
_, exists := s.data[value]
return exists
}
```
11. **实现消息队列(多生产者,多消费者)**
- 可以使用Go的`channel`来实现消息队列。例如,使用`goroutine`来模拟多个生产者和消费者:
```go
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
ch := make(chan int, 10)
// 生产者
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
ch <- i
fmt.Println("Produced:", i)
}
}()
// 消费者
go func() {
defer wg.Done()
for i := 0; i < 5; i++ {
msg := <-ch
fmt.Println("Consumed:", msg)
}
}()
wg.Add(2)
wg.Wait()
}
```
12. **大文件排序**
- 对于大文件排序,通常需要外部排序。将文件分块,分块排序后合并。可以通过`io`和`bufio`进行高效的文件读写。
13. **基本排序,哪些是稳定的**
- Go内置的`sort`包提供的排序算法是基于快速排序和插入排序的,通常是**不稳定的**。稳定排序的例子有归并排序。
14. **HTTP GET和HEAD**
- `GET`请求用于获取资源,服务器返回资源的内容。`HEAD`请求与`GET`类似,但不返回响应体,只返回响应头,常用于检查资源的状态。
15. **HTTP 401, 403**
- 401:表示未授权,通常在请求头中缺少有效的身份验证信息时返回。
- 403:表示禁止访问,服务器理解请求但拒绝执行,常因权限不足而返回。
16. **HTTP Keep-Alive**
- HTTP Keep-Alive是一种在同一TCP连接中复用多个HTTP请求和响应的机制。减少了TCP连接的建立和关闭开销,提高了性能。
17. **HTTP能不能一次连接多次请求,不等后端返回**
- 可以。HTTP/2协议支持在同一个连接上并发多个请求和响应,且不需要等待前一个请求返回即可发送后续请求。
18. **TCP与UDP区别,UDP优点,适用场景**
- TCP是面向连接的协议,保证数据的可靠性、顺序性和完整性;而UDP是无连接的协议,不保证数据可靠性。UDP的优点是速度快、开销小,适用于实时性要求高、容错能力强的场景(如视频流、语音通信等)。
19. **TIME-WAIT的作用**
- `TIME-WAIT`状态是TCP连接的四次挥手中的最后一步,目的是确保数据完全传输并防止旧数据包影响新连接。
20. **数据库如何建索引**
- 建立索引可以通过SQL语句`CREATE INDEX`来实现。通过在表的字段上创建索引,可以加速查询操作,但会增加插入和更新操作的开销。
21. **孤儿进程,僵尸进程**
- 孤儿进程是其父进程已退出,但仍然运行的进程。僵尸进程是已经终止,但其父进程未收集其退出状态的进程。可以通过`wait()`函数来清理僵尸进程。
22. **死锁条件,如何避免**
- 死锁发生时多个进程相互等待对方释放资源。避免死锁的方法包括资源排序、加锁时避免嵌套锁和使用`timeout`机制等。
23. **Linux命令,查看端口占用,CPU负载,内存占用,如何发送信号给一个进程**
- 查看端口占用:`netstat -tuln` 或 `lsof -i:端口号`
- 查看CPU负载:`top` 或 `uptime`
- 查看内存占用:`free -m` 或 `top`
- 发送信号:`kill -SIGTERM <PID>` 或 `kill -9 <PID>`
24. **Git文件版本,使用顺序,merge跟rebase**
- 文件版本:Git通过`git add`、`git commit`来管理文件的版本。
- 使用顺序:`git pull`获取远程更新,`git add`跟踪更改,`git commit`提交更改,`git push`推送到远程仓库。
- Merge:`merge`将两个分支的历史保留,并将它们合并。Rebase:`rebase`将一个分支的变更应用到另一个分支上,常用于保持历史记录整洁。
更多推荐
所有评论(0)