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

207 天前
 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 基础了。

11998 次点击
所在节点    Go 编程语言
108 条回复
phenixc
206 天前
package main

import (
"fmt"
"sync"
)

func senddata(out chan <- int, wg *sync.WaitGroup) {
defer wg.Done()
for i := range 5 {
out <- i
}

close(out)
}

func receivedata(in <- chan int, wg *sync.WaitGroup) {
defer wg.Done()

for i := range in {
fmt.Println(i)
}
}

func main() {
var wg sync.WaitGroup
ch := make(chan int)

wg.Add(2)
go senddata(ch, &wg)
go receivedata(ch, &wg)

wg.Wait()
}
capgrey
206 天前
我比较好奇,为什么你懂,候选者不懂。是你们学习用的阅读材料的差异吗? op 看哪个文档的,Effective Go 吗?
caicaiwoshishui
206 天前
func main() {

ch := make(chan int, 1)
wg := sync.WaitGroup{}
count := 5
wg.Add(1) // Add 1 to the WaitGroup before starting the producer goroutine
go func(wg *sync.WaitGroup, ch chan int, count int) {
defer wg.Done()
for i := 0; i <= count; i++ {
ch <- i
}
}(&wg, ch, count)

go func() {
for {
select {
case v, ok := <-ch:
if !ok { // Check if the channel is closed
return
}
fmt.Println(v)
}
}
}()

wg.Wait()
close(ch)
fmt.Println("end")
}
sardina
206 天前
https://go.dev/play/p/Te_KHn3sWYN 只用 channel 实现
大哥,你们公司在哪个城市,我想去
2018yuli
206 天前
+1
duty
206 天前
只能说感谢楼主,本来是一个 Go 开发,来到这家公司除了第一个项目后面全是写 python ,看到这个题的时候打开编辑器,基础语法都错了五六次,虽然最后实现了,但还是该反思自己把基础知识都整忘了,该重新抓起来了,也该开始计划换工作了,这样下去,感觉以后的路也会被糟蹋了
hunterster
206 天前
@body007 我用的是 liteide 写的,没有这个错误提示
mayli
206 天前
正常吧,go 基础,没写过的很容易挂
Rehtt
206 天前
也就两个 goroutine ,生产者和消费者都只有一个 goroutine 根本不需要 waitgroup ,两个 channel 就可以了,一个负载消息一个用来退出阻塞就行了
loginv2
206 天前
v2 是回复里面不支持 md 语法么 怎么这么多人都发的没缩进了
UN2758
206 天前
@harryge 你这个 time.sleep 真是把我干沉默了,假如如果我是面试官,你没有主动解释这样思路不规范的写法的话,评分不会很好
UN2758
206 天前
```
package main

import (
"fmt"
"sync"
)

func producer(c chan int, wg *sync.WaitGroup) {
for i := 0; i < 10000; i++ {

c <- i
}
close(c)
}

func consumer(c chan int, wg *sync.WaitGroup) {
for i := range c {
fmt.Println("consumer", i)
}
wg.Done()
}

func main() {
c := make(chan int)
wg := sync.WaitGroup{}
wg.Add(1)

go producer(c, &wg)

go consumer(c, &wg)
wg.Wait()
}
```
buffzty
206 天前
用不着 waitgroup ,context ,done channel 这些啊 for 循环就行了,关闭了循环就退出了
package main

import "log"

var (
jobs = make(chan int)
)

func t2() {
for v := range jobs {
log.Println(v)
}
}
func t1() {
for i := 0; i < 5; i++ {
jobs <- i
}
close(jobs)
}
func main() {
go t1()
t2()
}
angeni
206 天前
平台决定的候选人素质
lesismal
206 天前
> 2. 有的 channel 不知道由生产者关闭,直接在主程序生产者还未发送结束就关闭结果 panic ;

这种面试题用一个 chan 可以,但但就这个面试题的功能的话似乎就没必要俩协程了,不需要用俩协程也就不需要用 chan 了。
所以这种题如果出给我、只是纸面作答、我是不知道怎么答只能空着,因为需求不合理。

很多基础场景生产者不是唯一的,可能会是并发多协程会生产,所以通常是应该把用于发送的和用于关闭的分开两个 chan 、用于 close 的 chan 再配个 atomic compareandswap ,避免用单个 chan 、某个地方关闭后、其他协程还在给 chan 发数据直接就 panic 了,一些粗暴的实现直接 recover 这种 panic 虽然也问题不大但毕竟它不是个好的处理方式、比如还得纠结 panic recover 是否再给调用者一个 ErrClosed 返回,还是两个 chan 好些。

另外,如果不需要清理 chan 内遗留的数,chan 本身用完之后是不需要 close 的。
gaifanking
206 天前
类似生产者消费者、状态机这种比较典型的场景/思路都有哪些呢?
sampeng
206 天前
说实话,我面试的时候也被问过这个类似的问题。没答上来,得到的评价是不会 golang 。这妨碍我能开发 golang 完整的项目吗?我当时回答的思路基本是自己撸一个 waitgroup ,因为当时一瞬间压根没想到 waitgroup 现成的。但我知道要有类似 waitgroup 的极致。实际开发不管是问 AI 还是 google 搜索,只要我思路有了,这也不费事。所以之后我在面试基本不会卡关键词,重点是有没思路。思路是不是自己想的和经验得来的…
Steaven
206 天前
怎么跟我面试差不多,上周四下班后面试的
Steaven
206 天前
两种方法,第一种:
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
initial := make(chan struct{})
ch := make(chan int)
prev := initial
for i := 1; i <= 100; i++ {
wg.Add(1)
next := make(chan struct{})
go func(i int, prev, next chan struct{}) {
<-prev
ch <- i
close(prev)
if i < 100 {
next <- struct{}{}
}
}(i, prev, next)
prev = next
}

initial <- struct{}{}
go func() {
for i := range ch {
wg.Done()
fmt.Println(i)
}
}()

wg.Wait()
}

第二种(使用锁):
package main

import (
"fmt"
"sync"
)

func main() {
var wg sync.WaitGroup
var mu sync.Mutex
ch := make(chan int)
cond := sync.NewCond(&mu)
current := 1

for i := 1; i <= 100; i++ {
wg.Add(1)
go func(num int) {
mu.Lock()
for current != num {
cond.Wait()
}
ch <- i
current++
cond.Broadcast()
mu.Unlock()
}(i)
}

go func() {
for i := range ch {
wg.Done()
fmt.Println(i)
}
}()

wg.Wait()
}
tuxz
205 天前
```go
func main() {
ch := make(chan int)
doneCh := make(chan struct{})

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

go func(ch <-chan int) {
for v := range ch {
fmt.Println(v)
}
close(doneCh)
}(ch)

<-doneCh
}
```

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

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

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

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

© 2021 V2EX