最基础的 go 并发编程题,难倒了 90%的候选人

199 天前
 yuanyao

两个 goroutine 用 channel 通信,一个 goroutine 顺序发送 0,1,2,3,4 个数字,另一个 goroutine 接收并输出。 考察了 goroutine 的控制、channel 的关闭等基础知识,面试者写的代码各种问题。

  1. 有的 goroutine 还没启动程序就退出了,提示后仍想不到使用 waitgroup ,context ,done channel 等手段,而是用 time sleep 等待;
  2. 有的 channel 不知道由生产者关闭,直接在主程序生产者还未发送结束就关闭结果 panic ;
  3. 有的不会检查消费者读关闭 channel 的返回值,程序直接死循环死锁。

上周面试 5 个人只有 1 个人一次写出了执行没问题的代码,有 1 个经过提示也没写出来,剩下的能提示后逐步修改出正确的代码。

这个题还是很经典的,不用问 GMP 、垃圾回收算法等八股文,这个题就能看出 go 基础了。

11937 次点击
所在节点    Go 编程语言
108 条回复
SingeeKing
199 天前
想当年我也问过这类题,真的大量候选人答不出来😮‍💨
expy
199 天前
正常,卷是真的卷,但是水平差的人也大量存在。
HappyAndSmile
199 天前
你要求什么工资?我甚至可以答得比你好很多
phpcyy
199 天前
@voidmnwzp 你这就写错了啊,不能保证读的 goroutine 结束
bv
199 天前
@SingeeKing 真的假的?都几年经验?这种人招进去也是边学边做吧,甚至还会拖队友后腿。
main1234
199 天前
package main

import (
"fmt"
"sync"
)

func main() {
sw := sync.WaitGroup{}
sw.Add(1)
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
go func() {
defer sw.Done()
for i := range ch {
fmt.Println(i)
}
}()
sw.Wait()
fmt.Println("done")
}
voidmnwzp
199 天前
@phpcyy range channel 的话 如果 channel 关闭了会退出的 你可以试试
yuanyao
199 天前
@lasuar 三年左右,薪资我不清楚,我自己 22 年三年经验进来的时候给了 32k*14 ,不知道现在多少了
yiqiao
199 天前
@zljklang 你这个方法如果主 goroutine 发送完后立刻退出接收方还在运行导致提前终止吧 ,你可以试着调大循环可以看出来
还是要引入 WaitGroup
Ayanokouji
199 天前
@jworg #10 是这个库吗,看起来不维护了
https://github.com/sourcegraph/conc
Licsber
199 天前
笑死 最近写了个转发助手 和你这个题完全重叠
写出来加双端调试 部署上线测试总共两天吧
来看看我这水平能开多少(

```go
// listener 省略
func handleConnection(conn net.Conn) {
s := &Session{
Cmd: link_start,
Remote: remote,
ServerSideRemote: conn.RemoteAddr().String(),
ClientID: clientID,
}

ctx, cancel := context.WithTimeout(context.Background(), connectionTimeout)
sessionManager.Set(s.ServerSideRemote, conn)
sessionManager.Bind(s.ServerSideRemote, cancel)

send, _ := json.Marshal(s)
token := mqttClient.Publish(controlTopic, defaultQOS, false, send)
if token.Wait() && token.Error() != nil {
slog.Error("Publish control packet failed:", "err", token.Error())
sessionManager.Remove(s.ServerSideRemote)
return
}

<-ctx.Done()
switch ctx.Err() {
case context.Canceled:
slog.Info("Link confirmed successfully:", "serversideremote", s.ServerSideRemote)
case context.DeadlineExceeded:
slog.Warn("Confirmation timeout after 5s:", "serversideremote", s.ServerSideRemote)
sessionManager.Remove(s.ServerSideRemote)
}
}

func runServerForwarding(s *Session) {
conn, ok := sessionManager.Get(s.ServerSideRemote)
if !ok {
slog.Error("Connection not found:", "connID", s.ServerSideRemote)
return
}
defer conn.Close()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

topic := topicPrefix + s.ClientSideLocal
token := mqttClient.Subscribe(topic, defaultQOS, func(c mqtt.Client, m mqtt.Message) {
select {
case <-ctx.Done():
return
default:
slog.Debug("Recv client down:", "recv", m.Payload())
recv, err := hex.DecodeString(string(m.Payload()))
if err != nil {
slog.Error("HEX decode error:", "err", err)
cancel()
return
}

_, err = conn.Write(recv)
if err != nil {
slog.Warn("TCP write error:", "err", err)
cancel()
}
}
})
if token.Wait() && token.Error() != nil {
slog.Error("Subscribe client down error:", "err", token.Error())
return
}

buf := make([]byte, defaultBufSize)
for {
select {
case <-ctx.Done():
return
default:
conn.SetReadDeadline(time.Now().Add(readTimeout))
n, err := conn.Read(buf)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
slog.Debug("TCP read timeout:", "connID", s.ServerSideRemote)
continue
}

slog.Warn("TCP read error:", "err", err)
return
}

send := hex.EncodeToString(buf[:n])
slog.Debug("Send up:", "send", send)
topic := topicPrefix + s.ServerSideRemote
token := mqttClient.Publish(topic, defaultQOS, false, send)
if token.Wait() && token.Error() != nil {
slog.Error("Publish up error:", "err", token.Error())
return
}
}
}
}
```
yuanyao
199 天前
@zljklang 要求是新开两个 goroutine ,我没说清楚。你这个也没问题
yuanyao
199 天前
@hunterster 为啥要五个协程,不增加复杂度吗
MoYi123
199 天前
我三年前写的困难版本 (多生产者多消费者关 chan) https://github.com/mmooyyii/mmooyyii/blob/master/docs/go/mpmc_channel.md
yuanyao
199 天前
@nomagick 非 982 和几个特定的 211 ,简历都过不了吧
jworg
199 天前
@Ayanokouji 是的,不过基础功能的封装库并不需要经常更新,看了眼作者也还活着。
hanxiansheng
199 天前
法拉利跑车连犁田都不会,呵呵
phpcyy
199 天前
@voidmnwzp 把 5 调大成 10000 ,多试几次,有概率不到 9999 就退出
leehaoze98
199 天前
面外包或者校招会问两个协程交替打印,一个打印 A~Z ,一个打印 1~100 。

1. 有的人手打从'A'~'Z'的数组
2. 有的人 goroutine 没启动,主进程就退出了
3. 有的人只会处理两个协程打印数量一致的情况,一个打印 26 个,另一个打印 100 个,好多人会死锁。
phpcyy
199 天前
@voidmnwzp 试了下,直接用 main 函数,不使用单元测试文件,即使是 5 也有概率到 3 就退出

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1113786

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX