V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
yuanyao
V2EX  ›  Go 编程语言

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

  •  4
     
  •   yuanyao · 6 小时 28 分钟前 · 4111 次点击

    两个 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 基础了。

    第 1 条附言  ·  4 小时 6 分钟前
    工作职责
    1. 参与后端系统搭建与架构设计,保证其可扩展性,稳定性;为业务快速迭代提供保障;
    3. 作为业务 owner ,负责准时和高质量的交付;
    4. 根据业务需要,对后续架构的设计作出规划。
    任职要求
    1. 本科及以上学历,计算机科学、软件工程、信息技术或相关专业。
    2. 扎实的编程基础和良好的编码习惯,热爱编程,有 Go 或 Java 开发经验。
    3. 对产品有较好的理解,能够以用户体验为核心驱动力进行开发。
    4. 具备一定的系统架构设计能力,熟悉后端开发技术:协议、架构、存储、缓存、安全、消息队列等。
    5. 具备良好的沟通能力和团队合作精神,积极乐观,认真负责。
    63 条回复    2025-02-24 18:22:14 +08:00
    NerdHND
        1
    NerdHND  
       6 小时 22 分钟前   ❤️ 1
    拿我得指出哥们候选人真的有问题吧, 这, 这不是完全不会嘛...
    hingle
        2
    hingle  
       6 小时 20 分钟前   ❤️ 2
    薪资给多少?薪资低那就 “门当户对” 了,挺好的。
    kandaakihito
        3
    kandaakihito  
       6 小时 13 分钟前   ❤️ 7
    看了一下,上周那个 “一面出 LRU 算法题算难吗” 的帖子也是你发的。老哥你负责面的到底是什么类型岗位呀?怎么题目差距好像有点儿大。

    而且说实话,如果给你投简历的人里面,有 80%连 chan 的基础都用不清楚。。。那么你这个岗位面试的时候应该不适合问 LRU 。
    nomagick
        4
    nomagick  
       6 小时 10 分钟前   ❤️ 2
    你也别有莫名的优越感然后老来收铜币;
    先用务实标准筛选一下吧,统招本科四六级,3 年经验之类的
    Keystroke
        5
    Keystroke  
       5 小时 56 分钟前
    time.sleep……
    clemente
        6
    clemente  
       5 小时 56 分钟前
    如果说从通过率分布的角度来定义 题的难度的话
    90% 应该算 hard
    fruitmonster
        7
    fruitmonster  
       5 小时 52 分钟前
    虽然知道应该使用 waitgroup ,但是, "time sleep " 没解决没执行完就退出这个问题么?
    czfy
        8
    czfy  
       5 小时 44 分钟前 via Android   ❤️ 16
    钓鱼上瘾是病,得治
    xuanbg
        9
    xuanbg  
       5 小时 42 分钟前
    你这个不叫 go 基础,就是个编程基础。
    jworg
        10
    jworg  
       5 小时 42 分钟前
    可以用第三方库不,我脑子已经被 conc 惯坏了,只记得起 conc 的 WaitGroup , 系统的 WaitGroup 好像属于 sync 包。
    langhuishan
        11
    langhuishan  
       5 小时 41 分钟前
    golang 的精髓不就是并发吗?这都没掌握,等于没学
    cooooing
        12
    cooooing  
       5 小时 40 分钟前 via Android
    @fruitmonster time sleep 只是降低出现这个问题的概率,并没有解决问题。如果操作是请求 api 或者其他耗时操作,要 sleep 多久?这种方式不好的,还是得 wait group 或者其他方式来同步
    Vegetable
        13
    Vegetable  
       5 小时 32 分钟前
    这种情况至少说明完全没做过需要 graceful shutdown 的程序,不能说判死刑,但至少对 go 不太了解吧
    lasuar
        14
    lasuar  
       5 小时 14 分钟前
    可以说说招聘岗位要求几年经验,给多少薪资
    zljklang
        15
    zljklang  
       4 小时 50 分钟前
    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](@ref)
    }()

    // 接收方 goroutine
    for num := range ch {
    fmt.Println("Received:", num)
    }
    }
    这个对吗
    givenge
        16
    givenge  
       4 小时 42 分钟前
    天天发这些,不会是想转自媒体吧
    opengps
        17
    opengps  
       4 小时 36 分钟前
    对于你的状态来考他们不合格,那反过来他们提问题考验你你有信心通过吗?
    单个问题并不能说明人的整体能力
    hunterster
        18
    hunterster  
       4 小时 30 分钟前
    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)
    }
    codersdp1
        19
    codersdp1  
       4 小时 27 分钟前
    这个确实属于 go 基础了,没掌握这些写并发功能肯定会出问题的。
    voidmnwzp
        20
    voidmnwzp  
       4 小时 25 分钟前
    ```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 求和有啥区别 很难吗
    SingeeKing
        21
    SingeeKing  
       4 小时 22 分钟前
    想当年我也问过这类题,真的大量候选人答不出来😮‍💨
    expy
        22
    expy  
       4 小时 15 分钟前
    正常,卷是真的卷,但是水平差的人也大量存在。
    HappyAndSmile
        23
    HappyAndSmile  
       4 小时 13 分钟前
    你要求什么工资?我甚至可以答得比你好很多
    phpcyy
        24
    phpcyy  
       4 小时 12 分钟前
    @voidmnwzp 你这就写错了啊,不能保证读的 goroutine 结束
    bv
        25
    bv  
       4 小时 12 分钟前
    @SingeeKing 真的假的?都几年经验?这种人招进去也是边学边做吧,甚至还会拖队友后腿。
    main1234
        26
    main1234  
       4 小时 9 分钟前
    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
        27
    voidmnwzp  
       4 小时 6 分钟前
    @phpcyy range channel 的话 如果 channel 关闭了会退出的 你可以试试
    yuanyao
        28
    yuanyao  
    OP
       4 小时 4 分钟前
    @lasuar 三年左右,薪资我不清楚,我自己 22 年三年经验进来的时候给了 32k*14 ,不知道现在多少了
    yiqiao
        29
    yiqiao  
       3 小时 52 分钟前
    @zljklang 你这个方法如果主 goroutine 发送完后立刻退出接收方还在运行导致提前终止吧 ,你可以试着调大循环可以看出来
    还是要引入 WaitGroup
    Ayanokouji
        30
    Ayanokouji  
       3 小时 52 分钟前
    @jworg #10 是这个库吗,看起来不维护了
    https://github.com/sourcegraph/conc
    Licsber
        31
    Licsber  
       3 小时 32 分钟前
    笑死 最近写了个转发助手 和你这个题完全重叠
    写出来加双端调试 部署上线测试总共两天吧
    来看看我这水平能开多少(

    ```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
        32
    yuanyao  
    OP
       3 小时 30 分钟前 via iPhone
    @zljklang 要求是新开两个 goroutine ,我没说清楚。你这个也没问题
    yuanyao
        33
    yuanyao  
    OP
       3 小时 29 分钟前 via iPhone
    @hunterster 为啥要五个协程,不增加复杂度吗
    MoYi123
        34
    MoYi123  
       3 小时 24 分钟前
    我三年前写的困难版本 (多生产者多消费者关 chan) https://github.com/mmooyyii/mmooyyii/blob/master/docs/go/mpmc_channel.md
    yuanyao
        35
    yuanyao  
    OP
       3 小时 23 分钟前 via iPhone
    @nomagick 非 982 和几个特定的 211 ,简历都过不了吧
    jworg
        36
    jworg  
       3 小时 11 分钟前
    @Ayanokouji 是的,不过基础功能的封装库并不需要经常更新,看了眼作者也还活着。
    hanxiansheng
        37
    hanxiansheng  
       3 小时 6 分钟前 via Android
    法拉利跑车连犁田都不会,呵呵
    phpcyy
        38
    phpcyy  
       2 小时 57 分钟前
    @voidmnwzp 把 5 调大成 10000 ,多试几次,有概率不到 9999 就退出
    leehaoze98
        39
    leehaoze98  
       2 小时 55 分钟前
    面外包或者校招会问两个协程交替打印,一个打印 A~Z ,一个打印 1~100 。

    1. 有的人手打从'A'~'Z'的数组
    2. 有的人 goroutine 没启动,主进程就退出了
    3. 有的人只会处理两个协程打印数量一致的情况,一个打印 26 个,另一个打印 100 个,好多人会死锁。
    phpcyy
        40
    phpcyy  
       2 小时 55 分钟前
    @voidmnwzp 试了下,直接用 main 函数,不使用单元测试文件,即使是 5 也有概率到 3 就退出
    voidmnwzp
        41
    voidmnwzp  
       2 小时 47 分钟前
    @phpcyy #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()
    }
    smallparking
        42
    smallparking  
       2 小时 44 分钟前
    ```
    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, 所以确实不是一下子写出来的
    phpcyy
        43
    phpcyy  
       2 小时 43 分钟前
    @voidmnwzp 这个没问题。楼上有例子,你把生产者放 goroutine 先,然后消费者放到 main 函数里,,是最简单的实现
    ChatGOP
        44
    ChatGOP  
       2 小时 36 分钟前
    Gobyexample 大把例子。
    xiangxiangxiang
        45
    xiangxiangxiang  
       2 小时 34 分钟前
    写了快两年 go 。。。确实基本没用过 channel ,只有看 b 站学 go 语言的时候用过

    WaitGroup 倒是工作中经常用
    k9982874
        46
    k9982874  
       2 小时 27 分钟前
    这个不提前复习一下直接上手没 ai 辅助真写不出一次全对
    vhwwls
        47
    vhwwls  
       2 小时 19 分钟前
    运维路过,这个我觉得应该是贵司的简历筛选出了问题,一楼说的对。
    daimazha
        48
    daimazha  
       2 小时 17 分钟前
    我觉得这也是八股文的一种
    ChatGOP
        49
    ChatGOP  
       2 小时 14 分钟前
    @daimazha 是的, 八股文。 而且哥觉得凡是八股文,AI/Google 能做的都不算是真正的能力
    Nazz
        50
    Nazz  
       1 小时 54 分钟前
    我用 chan 从来不 close
    zhengfan2016
        51
    zhengfan2016  
       1 小时 38 分钟前   ❤️ 1
    只能说 op 招聘错方向了,你招的 985 ,211 都是市场里不缺面试机会的那一批。如果不是特别热爱代码的,也不会去钻研这些东西,反正 curd 就能轻松把钱拿,下班了打游戏不香吗。

    倒是那些学历低人一等的大专和普本,有少部分人为了在竞争中脱颖而出,可能真会去钻研这种东西,而聪明的 op 已经把这种可能性过滤掉了。

    至此,面试者百分之 90%不会不是很正常吗
    hunterster
        52
    hunterster  
       1 小时 35 分钟前
    @yuanyao 这个是看到要求随手写的,没注意到只要两个协程,我改一下

    package main

    import (
    "fmt"
    "sync"
    )

    var wg sync.WaitGroup

    func init() {
    wg.Add(5)
    }

    func writeData(c chan int, count int) {
    fmt.Println("Write rountine is executed.")
    for k := 0; k < count; k++ {
    defer wg.Done()
    c <- k
    }
    }

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

    func main() {

    var c = make(chan int)

    go writeData(c, 5)
    go readData(c, 5)

    wg.Wait()
    close(c)
    }
    Erroad
        53
    Erroad  
       1 小时 20 分钟前
    @zhengfan2016 #50 我建议是他先给他们公司的搞个考试,不合格的都开了再说
    birdhk
        54
    birdhk  
       1 小时 11 分钟前
    package main

    import "sync"

    func main() {
    var wg sync.WaitGroup
    ch := make(chan int)
    wg.Add(2)
    go func() {
    defer wg.Done()
    for i := 0; i < 10000; i++ {
    ch <- i
    }
    close(ch)

    }()
    go func() {
    defer wg.Done()
    for i := range ch {
    println(i)
    }
    }()
    wg.Wait()
    }
    我感觉我这样应该没问题,有大哥们 review 一下吗
    Co1a
        55
    Co1a  
       1 小时 2 分钟前
    楼主发下邮箱
    ForkNMB
        56
    ForkNMB  
       1 小时 2 分钟前
    刚开始学 go 也以为是八股文看看得了 不上手试试 写的也是简单版本(因为没有具体复杂的场景 写出来当然简单)等真用 go 开发写服务 多个协程下 服务优雅关闭 资源合理释放 生产者消费者怎么稳定的转起来 还是踩了很多坑的= = 面试这个确实能筛人
    sky3hao
        57
    sky3hao  
       53 分钟前
    这个还好, 我之前出的面试题是: 用两个 channel 循环打印水分子(H2O), 分子顺序不要求, 但是每一行必须是 2 个 H 加 1 个 O 的组合
    ChatGOP
        58
    ChatGOP  
       43 分钟前
    @ForkNMB 其实招人的, 如果自己没有看过这题没有准备, 拿这题考他自己, 也会被筛出局。
    body007
        59
    body007  
       37 分钟前
    @hunterster #52 看到循环里面用 defer ,jb 都报警告了。生产环境被同事这种代码坑过
    Jinnrry
        60
    Jinnrry  
       35 分钟前 via iPhone   ❤️ 1
    我面试也问这个题目。感觉和 hr 相关,很多 hr 只看背景,不懂技术,找一堆人过来就 90%都写不出来。

    我现在这里 hr 比较专业,基本上一半的人能写出来的。

    想来也是搞笑,这些连个协程通信都写不明白的人,讲起垃圾回收,数组扩容,乐观锁悲观锁却能头头是道
    guanhui07
        61
    guanhui07  
       30 分钟前
    也是八股文的一种 不过如果他能知道 waitGroup 我就让他过
    seakee
        62
    seakee  
       14 分钟前
    ```
    func main() {
    num := make(chan int, 5)
    done := make(chan struct{})

    go func() {
    for n := range num {
    fmt.Println(n)
    }
    close(done)
    }()

    go func() {
    for i := 0; i < 5; i++ {
    num <- i
    }
    close(num)
    }()

    <-done
    }
    ```
    Trim21
        63
    Trim21  
       2 分钟前
    这还八股啊,这不是 go 并发编程的基础知识吗 ...
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3837 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 10:24 · PVG 18:24 · LAX 02:24 · JFK 05:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.