最基础的 go 并发编程题,难倒了 90%的候选人
两个 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 基础了。
工作职责
- 参与后端系统搭建与架构设计,保证其可扩展性,稳定性;为业务快速迭代提供保障;
- 作为业务 owner ,负责准时和高质量的交付;
- 根据业务需要,对后续架构的设计作出规划。
任职要求 - 本科及以上学历,计算机科学、软件工程、信息技术或相关专业。
- 扎实的编程基础和良好的编码习惯,热爱编程,有 Go 或 Java 开发经验。
- 对产品有较好的理解,能够以用户体验为核心驱动力进行开发。
- 具备一定的系统架构设计能力,熟悉后端开发技术:协议、架构、存储、缓存、安全、消息队列等。
具备良好的沟通能力和团队合作精神,积极乐观,认真负责。
拿我得指出哥们候选人真的有问题吧, 这, 这不是完全不会嘛...
薪资给多少?薪资低那就 “门当户对” 了,挺好的。
看了一下,上周那个 “一面出 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 。
- 有的人手打从'A'~'Z'的数组
- 有的人 goroutine 没启动,主进程就退出了
有的人只会处理两个协程打印数量一致的情况,一个打印 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 倒是工作中经常用
下载了阿里小号,天天都去看有没有号,好多天啦都没有,是不是不卖了 运营商不放号 阿里小号的应用不更新了,不好用。不如用运营商的小号 2030 年到期的用户路过 2035…
以前觉得通义灵码已经很香了,用过 cursor 后,通义灵码难用死了。完全不是一个量级上的东西。 程序员真的要大批失业了 几乎不用敲代码,只要不停的聊天、apply 、然后…
很多年前用过二手威联通 TS-212P3 ,是两盘位,当时被绿联 4600Pro 忽悠,就卖了威联通买了绿联 4600Pro ,然后就如同大家所知道的,绿联 NAS 虽然做工极…