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

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

上周面试 5 个人只有 1 个人一次写出了执行没问题的代码,有 1 个经过提示也没写出来,剩下的能提示后逐步修改出正确的代码。
这个题还是很经典的,不用问 GMP 、垃圾回收算法等八股文,这个题就能看出 go 基础了。

工作职责

  1. 参与后端系统搭建与架构设计,保证其可扩展性,稳定性;为业务快速迭代提供保障;
  2. 作为业务 owner ,负责准时和高质量的交付;
  3. 根据业务需要,对后续架构的设计作出规划。
    任职要求
  4. 本科及以上学历,计算机科学、软件工程、信息技术或相关专业。
  5. 扎实的编程基础和良好的编码习惯,热爱编程,有 Go 或 Java 开发经验。
  6. 对产品有较好的理解,能够以用户体验为核心驱动力进行开发。
  7. 具备一定的系统架构设计能力,熟悉后端开发技术:协议、架构、存储、缓存、安全、消息队列等。
  8. 具备良好的沟通能力和团队合作精神,积极乐观,认真负责。

    拿我得指出哥们候选人真的有问题吧, 这, 这不是完全不会嘛...

    薪资给多少?薪资低那就 “门当户对” 了,挺好的。

    看了一下,上周那个 “一面出 LRU 算法题算难吗” 的帖子也是你发的。老哥你负责面的到底是什么类型岗位呀?怎么题目差距好像有点儿大。

而且说实话,如果给你投简历的人里面,有 80%连 chan 的基础都用不清楚。。。那么你这个岗位面试的时候应该不适合问 LRU 。

你也别有莫名的优越感然后老来收铜币;
先用务实标准筛选一下吧,统招本科四六级,3 年经验之类的

time.sleep……

如果说从通过率分布的角度来定义 题的难度的话
90% 应该算 hard

虽然知道应该使用 waitgroup ,但是, "time sleep " 没解决没执行完就退出这个问题么?

钓鱼上瘾是病,得治

你这个不叫 go 基础,就是个编程基础。

可以用第三方库不,我脑子已经被 conc 惯坏了,只记得起 conc 的 WaitGroup , 系统的 WaitGroup 好像属于 sync 包。

golang 的精髓不就是并发吗?这都没掌握,等于没学

time sleep 只是降低出现这个问题的概率,并没有解决问题。如果操作是请求 api 或者其他耗时操作,要 sleep 多久?这种方式不好的,还是得 wait group 或者其他方式来同步

这种情况至少说明完全没做过需要 graceful shutdown 的程序,不能说判死刑,但至少对 go 不太了解吧

可以说说招聘岗位要求几年经验,给多少薪资

package main

import "fmt"

func main() {
// 创建无缓冲通道
ch := make(chan int)

// 发送方 goroutine
go func() {
for i := 0; i < 5; i++ {
ch <- i // 顺序发送数字
}
close(ch) // 发送完成后关闭通道1,7
}()

// 接收方 goroutine
for num := range ch {
fmt.Println("Received:", num)
}
}
这个对吗

天天发这些,不会是想转自媒体吧

对于你的状态来考他们不合格,那反过来他们提问题考验你你有信心通过吗?
单个问题并不能说明人的整体能力

package main

import (
"fmt"
"sync"
)

func writeData(c chan int, i int) {
c <- i
}

func readData(c chan int) {
for i := 0; i < 5; i++ {
fmt.Println("The data is:", <-c)
}
}

func main() {
var wg sync.WaitGroup
wg.Add(5)

var c = make(chan int)

for k := 1; k <= 5; k++ {
go func() {
defer wg.Done()
writeData(c, k)
}()
}

go readData(c)

wg.Wait()
close(c)
}

这个确实属于 go 基础了,没掌握这些写并发功能肯定会出问题的。

func Test2(t *testing.T) {
s := make(chan int)
go func() {
for i := range s {
fmt.Println("recv:",i)
}
}()
for i := 0; i < 5; i++ {
s <- i
}
close(s)
}

不是这和 1+。。100 求和有啥区别 很难吗

想当年我也问过这类题,真的大量候选人答不出来😮‍💨

正常,卷是真的卷,但是水平差的人也大量存在。

你要求什么工资?我甚至可以答得比你好很多

你这就写错了啊,不能保证读的 goroutine 结束

真的假的?都几年经验?这种人招进去也是边学边做吧,甚至还会拖队友后腿。

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")
}

range channel 的话 如果 channel 关闭了会退出的 你可以试试

三年左右,薪资我不清楚,我自己 22 年三年经验进来的时候给了 32k*14 ,不知道现在多少了

你这个方法如果主 goroutine 发送完后立刻退出接收方还在运行导致提前终止吧 ,你可以试着调大循环可以看出来
还是要引入 WaitGroup

#10 是这个库吗,看起来不维护了
github.com/sourcegraph/conc

笑死 最近写了个转发助手 和你这个题完全重叠
写出来加双端调试 部署上线测试总共两天吧
来看看我这水平能开多少(

// 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
 }
 }
 }
}

要求是新开两个 goroutine ,我没说清楚。你这个也没问题

为啥要五个协程,不增加复杂度吗

我三年前写的困难版本 (多生产者多消费者关 chan) github.com/mmooyyii/mmooyyii/blob/master/docs/go/mpmc_channel.md

非 982 和几个特定的 211 ,简历都过不了吧

是的,不过基础功能的封装库并不需要经常更新,看了眼作者也还活着。

法拉利跑车连犁田都不会,呵呵

把 5 调大成 10000 ,多试几次,有概率不到 9999 就退出

面外包或者校招会问两个协程交替打印,一个打印 A~Z ,一个打印 1~100 。

  1. 有的人手打从'A'~'Z'的数组
  2. 有的人 goroutine 没启动,主进程就退出了
  3. 有的人只会处理两个协程打印数量一致的情况,一个打印 26 个,另一个打印 100 个,好多人会死锁。

    试了下,直接用 main 函数,不使用单元测试文件,即使是 5 也有概率到 3 就退出

    #40 func Test2(t *testing.T) {
    s := make(chan int)
    wg:=sync.WaitGroup{}
    wg.Add(1)
    go func() {
    for i := range s {
    fmt.Println("go2:",i)
    }
    fmt.Println("done")
    wg.Done()
    }()
    for i := 0; i < 5; i++ {
    s <- i
    }
    close(s)
    wg.Wait()
    }

    package main
    
    import (
    "fmt"
    "sync"
    )
    
    func send(ch1 chan int) {
    for i := range 5 {
    ch1 <- i
    fmt.Printf("send i: %d\n", i)
    }
    close(ch1)
    }
    func recv(wg *sync.WaitGroup, ch1 chan int) {
    
    // wait receive all data and done
    for i := range ch1 {
    fmt.Printf("receive i: %d\n", i)
    }
    wg.Done()
    
    }
    func main() {
    var wg sync.WaitGroup
    ch1 := make(chan int)
    wg.Add(1)
    go send(ch1)
    go recv(&wg, ch1)
    wg.Wait()
    }

确实有点难度,一般不会启两个 goroutine, 所以确实不是一下子写出来的

这个没问题。楼上有例子,你把生产者放 goroutine 先,然后消费者放到 main 函数里,,是最简单的实现

Gobyexample 大把例子。

写了快两年 go 。。。确实基本没用过 channel ,只有看 b 站学 go 语言的时候用过

WaitGroup 倒是工作中经常用