Go 的特色不是语法的便捷,而是在工程

2023-02-08 01:00:55 +08:00
 GopherDaily

Envoy 这样的工程构建已经是非常复杂了,当然 Go 大型工程也不简单。 但是入门写 Go 基本就一条路,入门 C++ 就很依赖人的主观判断。

学了两天 bazel ,又学了一天 cmake ,加深了这个想法。

7856 次点击
所在节点    Go 编程语言
77 条回复
lanlanye
2023-02-08 13:42:34 +08:00
@GeruzoniAnsasu 我想了想,开辟一个用于保存结果的数组,启动 worker 的时候传入对应顺序的数组下标,直接把结果写进对应位置……应该可行吧
Smilecc
2023-02-08 13:45:24 +08:00
@Nazz await 未必一定具有传染性,本质上是因为 JS 或 Rust 等语言都是无栈协程,实现方式决定具有传染性,Go 的协程是有栈的,不依赖状态机做上下文切换,我认为是可以实现的
8355
2023-02-08 13:47:19 +08:00
@fioncat #12 太对了
这才是严谨的体现
而不是瞎糊一通结果来个兜底
leonshaw
2023-02-08 13:51:32 +08:00
@GeruzoniAnsasu #22 没明白你说的,对 Go 来说大部分情况只要把无栈协程模式的 await 直接改成同步调用就行了,并不需要启 goroutine 。
举的例子只要在 goroutine 把结果写到一个 slice 对应位置就可以了,“回到同一个与 spwaner 共享的 chan 里”是伪需求,因为 chan 是 Go 特有的。
5h4nh
2023-02-08 14:33:01 +08:00
@fioncat 不赞同。我用 sourcegraph.com 查了一下 `if err != nil`,基本都是 `return err`,没有处理。而且还有不少人直接写 `_`。相反我认为 Java 的 checked exception 机制,强制要求 caller 写 try-catch 或者 caller 签名也加上,才是所谓「强迫你认真对待每个 error 」。另外,你说「某些 Javaer 一有 exception 就无脑直接 throw 」,我觉得他们如果换用 Go ,情况只会更糟糕吧..
5h4nh
2023-02-08 14:35:53 +08:00
@Nazz 我也觉得 Go 的源码很难读,其中一个原因就是变量名太追求 “Unix 风格”,我觉得有点过头了。比如这个 `sudog`.. https://stackoverflow.com/questions/68569386/whats-the-mearning-of-sudog-in-the-channel-struct-in-go
Nazz
2023-02-08 14:46:29 +08:00
@5h4nh 源码很难读是因为复杂性, 实现得异常复杂. 当然简洁不代表性能.
macscsbf
2023-02-08 14:50:23 +08:00
知乎上在哪里看到的,go 的最大特色是无聊
chenqh
2023-02-08 14:53:13 +08:00
既然 golang 代码看起来这么简单,为什么我看 crowedsec 看不懂呢?
th00000
2023-02-08 14:56:24 +08:00
@fioncat #12
这个逻辑反过来才是对的吧
强迫处理 error 怎么就变成优势了,这会让人增加大量的心智负担
相反让人有选择的余地,自己根据需求去决策才是好的设计吧
learningman
2023-02-08 15:00:28 +08:00
@GeruzoniAnsasu #22 启动的时候带上个序号,返回结果的时候带上序号,waitgroup 等待所有 worker 完成。
就算有 async await 不也是这么操作吗,难道你想 for 1 to n await result ?这样写是符合直觉,但又不是唯一的标准答案。
Slurp
2023-02-08 15:14:56 +08:00
靠元组实现标签联合的垃圾类型系统。基于此出来的错误处理也是一坨大便,这也有人吹?
wupher
2023-02-08 17:05:49 +08:00
catch / throw 当然不完美

if err!= nil 多层嵌套有时更变态,毕竟 runtime exception 你还可以不处理。

学了 Rust 之后确实相信这才是更优雅的设计。
quicksand
2023-02-08 17:05:57 +08:00
各有千秋吧,不同人肯定喜好都不一样的,这也是语言多样性的原因,没必要强行来比较。我最近也在学 go ,感觉不舒服的点就是注释文档,感觉 javadoc 这方面做的更好一些。
JamesMackerel
2023-02-08 17:19:44 +08:00
看到贵贴,想来请教一下 go 的 thread local (或类似机制)的进展……

我知道可以用 Context 然后把每个 function 都加个 Context 参数,可是除了这种方法还有没有别的办法?
liuxu
2023-02-08 17:36:05 +08:00
最后发现 php 依然是最好的语言,而你们都会来写 rust
GeruzoniAnsasu
2023-02-08 17:48:56 +08:00
@RedisMasterNode @Nazz @leonshaw

不是的,wait group 只能在所有任务完成前一直阻塞住。而作为一个 spawner ,你需要时刻维护一个有长度的队列,当队列空出来时立即解除正在预约( schedule )任务的 routine 的阻塞,wait group 显然不合适。


注意我们的目标是,让结果按照添加的顺序依次输出,而不是一次性等待所有的结果一起输出。


有异步语法的语言,在这个场景的做法是

- 一个有长度的阻塞队列
- 当外界 scheduling 新任务时,spwaner 向队列获取一个空槽,如果队列已满,那么 spanwer 和 请求者都会被阻塞
- 如果获取了空槽,将任务放入空槽,获得一个 promise
- 创建新 promise, 在这个 promise 里 { await 任务队列的尾部任务(因为我们需要按任务的添加顺序而不是任务完成顺序来返回),await 到之后返回上一步获得的 promise }
- 把上面这个 promise 加到 out 队列里,每次提取结果时 await out 队列的头部


而 golang 要模拟这个做法的话,首先它没有 promise ,也没有 goroutine 的 handler ,然后要实现跟上述等价的 spawner 必须使所有调用 spanwer 的线程共享同一个 channel ,意味着 chan 要么是全局的,要么扔到 context 里。先简单考虑全局唯一 chan 的做法。(但复制 chan 用 context 传这种逆天玩意我也写过)

提取任务槽这步没问题,但怎么模拟一个 promise ?
- c := make(chan,1) ; go func(){c<-do();)}

那怎么获取任务队列的尾部任务并 await 它?
- 如果任务队列只是个简单的 channel 是做不到的,因此需要一个 slice + channel ,可是 slice 就没有锁了,你这时候要考虑一个可阻塞环境( chan )下的锁问题,头开始疼起来了

怎么返回 await 了 c 的新 promise ?
- ……

对了,这个新 promise 还要放到 out 队列里
- …………
Nazz
2023-02-08 17:54:50 +08:00
@GeruzoniAnsasu 添加任务的时候加序列号, 线程同步后给输出结果排序
GeruzoniAnsasu
2023-02-08 18:00:12 +08:00
@Nazz @learningman @leonshaw @lanlanye @RedisMasterNode

我提醒你们一下关于放到对应序号结果槽的实现:


- 这个结果 array (它有大小,我这里用 array 来称呼,并不是指实现),是有「洞」的,需要有个机制能按顺序检查每个位置是否完成了,没完成要能阻塞住,意味着 array 里放的是锁或 chan 或任意什么东西总之是一个可锁对象,但有 promise 的情况下不需要这种可锁对象

- 我们不能一次性等待一批 worker 全部完成,而是要时刻能分派已完成的 worker 占用的任务槽

- spwaner 本身要可以等待或阻塞
Nazz
2023-02-08 18:23:43 +08:00
@GeruzoniAnsasu 不知道我写的这个库是否满足你的需求
https://github.com/lxzan/concurrency

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

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

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

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

© 2021 V2EX