V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 56 页 / 共 63 页
回复总数  1248
1 ... 48  49  50  51  52  53  54  55  56  57 ... 63  
2021-05-14 15:08:01 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
“不过去掉偶尔存在的乱序问题,连续两次的输出可以认为是 chan 等待队列机制的作用吗?”
—— 你搜下 golang 内存模型、happens before,结合 #20 的例子,其实这个是对 chan 在要求时序场景用法的误解,保证内存读写顺序 /临界区顺序,跟多个并发流非锁定(包括类似#20 用 chan 做类似的穿行方式)区域内代码段调度顺序是两码事。
如果想明白了,你就能理解其实这个现象跟 chan 没直接关系,你只要思考代码段、调度就行了:楼主代码里的 chan send 和 recv 后面直到下次循环 chan recv 阻塞之前的代码段,其实都是无串行化的两个或者多个并发流,这些代码段(相当于#20 里的 [3->4->1] 与 [2->5->6],这两个过程中互相没影响没有被串行化),并不受 chan 内部实现逻辑的影响,而是被调度器决定运行时机
2021-05-14 15:01:34 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 咱们再看下 print 的源码

```golang
package main

func main() {
print("hello world")
}
```

print 是 buildin,对应的汇编源码:

```sh
go tool compile -S .\print.go > print.s
```

有点长,只看 print 的部分:

```asm
0x0024 00036 (.\print.go:4) CALL runtime.printlock(SB) // 加锁
0x0029 00041 (.\print.go:4) LEAQ go.string."hello world %d, %s\n"(SB), AX
0x0030 00048 (.\print.go:4) MOVQ AX, (SP)
0x0034 00052 (.\print.go:4) MOVQ $19, 8(SP)
0x003d 00061 (.\print.go:4) NOP
0x0040 00064 (.\print.go:4) CALL runtime.printstring(SB)
0x0045 00069 (.\print.go:4) MOVQ $1, (SP)
0x004d 00077 (.\print.go:4) CALL runtime.printint(SB)
0x0052 00082 (.\print.go:4) LEAQ go.string."hi"(SB), AX
0x0059 00089 (.\print.go:4) MOVQ AX, (SP)
0x005d 00093 (.\print.go:4) MOVQ $2, 8(SP)
0x0066 00102 (.\print.go:4) CALL runtime.printstring(SB)
0x006b 00107 (.\print.go:4) CALL runtime.printunlock(SB) // 解锁
```

print 执行过程中是对本 m 加了锁的,即使是 runtime.GOMAXPROCS(1),也能保证 print 先后的顺序:
https://github.com/golang/go/blob/master/src/runtime/print.go#L66
https://github.com/golang/go/blob/master/src/runtime/print.go#L76

而即使加了锁,依然会出现非固定的两两一组或者交替,说明这并不是进入 print 后造成的,所以即使是源码分析,也跟直接 print 还是 fmt 的 print 系列没关系
我前面说的 print,都是说 print 之前就可能被调度了,其实都是调度器决定的,而调度器并不能保证这些固定的顺序
2021-05-14 14:25:06 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 两个问题:

1. 分析楼主的问题,当然应该尽量用楼主相同的代码好些 :smile: :smile:

2. 你这里的例子循环次数只有 5,数量太少可能观察不到,修改下就来 10 秒的,你试试这个
package main

import (
"runtime"
"time"
)

func main() {
runtime.GOMAXPROCS(1)
ch := make(chan int)
go func() {
runtime.Gosched()
for i := 0; true; i++ {
ch <- 100
print("written", i, "\n")
}
}()
var ep = 100
go func() {
for {
ep = <-ch
print("received\n")
}
}()
time.Sleep(10 * time.Second)
}

然后再统计下( print 好像是直接 stderr 的,所以重定向下):
go run main.go 2>&1 | uniq -c | awk '$1!="2"'
或者 > x.log 日志文件你自己再搜下,应该就可以发现有不是连续两次的,我这里已经有只一次的日志产生
2021-05-14 13:15:54 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 一起学习研究,有新发现咱们继续讨论
2021-05-14 12:23:24 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@no1xsyzy 我找这个实际的日志例子是为了说 baiyi 的分析存在的问题,其实我第一次回复中的解释已经算比较清楚了,1.14 之后的抢占式,随时可能调度,所以在 chan send 和 recv 后面代码段之间的并发 print 的顺序是无法保证的,交替各一次和各连续两次都没法保证

我杠不动 baiyi 这孩子了,你帮我劝劝他 :joy::joy:。。。 :
2021-05-14 12:17:59 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
"这里只要 runtime.GOMAXPROCS 设置为 1,那么除了第一次和最后一次(如果存在的话)外,其他的输出绝对是连续两次的"
—— 这是你在 17 楼说的"绝对是两次",我举乱序的例子,是反驳你的绝对两次。同样的代码,现象已经证明你的解释是错的,你还要坚持你的解释,那我放弃跟你讨论这个问题

“楼主也没有问为什么在多次连续的操作中会有一次乱序”
—— 但是楼主问的是“为什么不是一个一个交替的形式”,我在以上好几个楼都解释过了 print 的原因,并且这个并不能保证固定的连续两次

“你的论证是什么?你看了 fmt.Printf 函数的源码,发现确实有能主动触发调度的操作吗?还是根据现象推断的?”
—— 你这么讲话的话,说明你根本没了解什么是抢占式调度、go 什么时候可能发生调度(我之前楼层也有提过一点),那我只能怀疑你看不懂我说的了,那就没必要再聊了,这个相关的资料一搜大把,去找资料先看一下吧。。。

从你回复的分析中能看的出,你算是个能钻研的娃,一般人不会去啃源码。但人年轻气盛的时候,可能聪明反被聪明误,因为觉得自己具备多数人不具备的源码阅读调试能力和钻研精神、并且在源码中窥探读懂了一些,所以更偏执于自己是正确的、可能会在对错上纠结、听不进去别人说什么,这个问题,我建议是冷静一下过几天你再来仔细研究下吧,我的回复已经足够详细了,如果你认为哪里有错误可以指出,我也会虚心继续研究

我也年轻过,但是接触得越多,越会明白自己还很菜,所以技术问题,心态平和些
2021-05-14 10:41:27 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
“我还是认为 chan 本身的特性所导致的,这个特性就是 chan 的等待队列可直接传值的操作。”
—— 最简单的问题,单从现象上说, #19 的日志,你可以试一下,这能说明 chan 本身特性的解释是不对的,明明都解释不了,就没必要继续坚持了吧 :joy:

“同时我认为你在 12 楼的解释将其认为是 printf 造成的调度我不认可,你也没有给出论证。”
—— 论证我已经解释得很清楚了,如果这都算没论证,那我无言以对了。或者你考虑下再仔细看看我上面几楼的回复,如果哪里不对,你也可以指出来、我再琢磨琢磨。。。
2021-05-13 18:17:04 +08:00
回复了 lesismal 创建的主题 分享创造 发布个 golang 高性能异步网络框架 nbio,单击百万不是梦!
@kksco 感谢支持!太爱 golang 了
2021-05-13 18:16:28 +08:00
回复了 lesismal 创建的主题 分享创造 发布个 golang 高性能异步网络框架 nbio,单击百万不是梦!
@foam 感谢支持!
2021-05-13 18:14:19 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 如果考虑到其他调度的话是超出讨论范围了,我只是思考了为什么会有连续两个输出而不是交替输出的问题。”
—— 这个现象本身并不是 chan op 的单句代码导致的,所以你只分析 chan 内部的肯定不足够。反而正是因为其他部分代码的调度导致的现象,所以考虑其他调度也不是超出范围

“其实在真正的使用中是要避免依赖这种 runtime 的执行顺序”
—— 对于多数人,“是否需要依赖以及如何避免依赖 rutime 调度”本身就是个难题,楼主和很多人的意图其实应该是想依赖 chan 做流控,但是对 golang 内存模型+happens-before 与调度场景下的代码执行顺序没弄太清楚所以才会疑惑。#20 例子中的想确保 1 和 2 的顺序这种场景用 chan 还是可以的
2021-05-13 17:35:55 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
send 的时候先检查 recvq,等待队列有 waiter 的话直接发给第一个 waiter
https://github.com/golang/go/blob/master/src/runtime/chan.go#L207
并标记 waiter 的那个 g 为可运行状态,顺着代码往下看就是了
https://github.com/golang/go/blob/master/src/runtime/chan.go#L320

这里需要着重说的一点是,标记可运行不是立刻就运行,而且就算立刻运行,也不能保证 chan op 之后的一段代码全部在单次调度运行中执行完,所以你调试 chan 内部的实现逻辑,其实解释不了这个现象,解释现象,我 #12 的应该说得差不多了

recv 的逻辑也类似,代码就不贴了
2021-05-13 16:53:53 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi golang 的内存模型,无缓冲的 chan,比如两个 goroutine 分别 send 、recv 之间,可以保证这两个 A chan op 前段代码先于 B chan op 后段代码执行,但不能保证 A 和 B op 后段代码的执行顺序,因为 chan op 之后的代码随时也可能被调度

比如

goroutine A:

some code... // 1
chan <- v
some code... // 3
some code... // 4

goroutine B:
<-chan
some code... // 2
some code... // 5
some code... // 6

这里能保证的是 1 先于 2/5/6 执行,但是不能保证 3 和 4,因为 3 和 4 执行之前就可能被调度了
2021-05-13 16:48:14 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 你多跑几次例子试试,至少我这里中间可以遇到这样的日志:

| <-r recv|10001
| <-r recv|10001
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
|write <- who|10000 ///////////// 不是连续的两次,也不是第一次和最后一次
| <-r recv|10001 ///////////// 不是连续的两次,也不是第一次和最后一次
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
2021-05-13 11:16:33 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 这样解释应该是不对的。
“然后在循环中再次的操作,才会阻塞,阻塞时自己也会在相应的等待队列中。所以每次的 send 或 recv 操作,都会执行两次,输出的形式也是连着输出”
—— 你都说了,再次的操作会阻塞,比如 A 的再次操作阻塞了,然后就暂时没走到 print,需要等 B 触发 A 的运行后才能 print,也就是说,A 的两次之间,只有一次 print,然后 B 触发了之后才能再次 print,这两次中间阻塞过、并不是连着输出。

你看 #11 的,通常一开始的时候日志是这样的,第一句他就不是连续的两次:
|write <- who|10000 // 不是连续的两次,只有一次
| <-r recv|10001
| <-r recv|10001
|write <- who|10000
|write <- who|10000

再举个例子,简单点,我开 4 个协程

runtime.GOMAXPROCS(1)
ch := make(chan int)
go count(ch, 10000)
go count(ch, 10001)
go count(ch, 10002)
go count(ch, 10003)

然后某段日志里:

| <-r recv|10003
| <-r recv|10003
| <-r recv|10001 // 不是连续的两次,只有一次
|write <- who|10002 // 不是连续的两次而是三次
|write <- who|10002 // 不是连续的两次而是三次
|write <- who|10002 // 不是连续的两次而是三次
| <-r recv|10001
| <-r recv|10001
|write <- who|10002
|write <- who|10002

runtime 的调度不可能是这样简单对 chan 稳定的两次然后就调度,即使是因为楼主代码的例子场景这两个协成比较均衡导致基本是出现两次,但这也并不是 runtime 提供的保证

看下我 #12 楼的解释
2021-05-12 14:08:22 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@no1xsyzy
"而且 chan 和 printf 都是原子的,golang 默认没有 buffer 。"
—— 跟 printf 有没有 buffer 也没关系,即使有 buffer,先调用 printf 的也是先入 buffer
2021-05-12 14:03:58 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@no1xsyzy 这个解释是错误的

“上面都不看 runtime.GOMAXPROCS(1) 的吗?”
—— 跟 runtime.GOMAXPROCS(1) 没关系,尝试下 runtime.GOMAXPROCS(大于 1),也一样会出现两两一组的日志

“一句话解释:因为是非抢占式调度。”
—— golang 好像是 1.2 版中开始引入比较初级的抢占式调度,然后好像是 1.14 做得更彻底,即使 for{} 也能释放调度权了

这个代码的 chan 虽然是无缓冲的,但只能保证 chan 的 send 和 recv 单句代码的原子性,记得那句话吗——不要通过共享内存来通信,而应该通过通信来共享内存。这句话主要是指用 chan 来保证内存的一致性,因为传统的方法用锁、锁在复杂的业务场景更烧脑并且一不小心容易死锁

用 chan 保证内存的一致性,进一步就可以做到一些业务逻辑的串行化,通常用 chan 也主要是用来做内存读写和逻辑的串行化从而保证一致性,但这并不是承诺多个协程同一个 chan 前后代码段的所有代码执行顺序

楼主代码中的实验是用 printf 打印,而 printf 本身就可能触发调度权的出让,所以其实现象不是 chan 直接导致的,而是由于 printf 时的出让顺序导致的

举个例子(对应代码注释中的 1 、2 、3 、4 ):

func count(r chan int, who int) {
for {
if who%2 == 0 {
r <- who // 1
fmt.Printf("|write <- who|%d\n", who) // 2
} else {
<-r // 3
fmt.Printf("| <-r recv|%d\n", who) // 4
}
}
}

1 执行后立刻执行了 2,打印了 write,出让
3 执行,4 执行前出让
又执行了一组 1 、2,再次打印了 write,出让
4 继续执行,打印了一个 recv,出让
1 执行,2 执行前出让调度
3 执行,4 执行,再次打印了一个 recv
...
依次类推,每次 printf 前都可能出让

顺便宣传下自己两个项目,欢迎来玩玩
https://v2ex.com/t/755862
2021-05-07 19:29:15 +08:00
回复了 jiangshanmeta 创建的主题 求职 刷了 1400 道力扣的开发,跪求广州前端岗位
做 web 前端有点可惜了
2021-05-02 11:24:26 +08:00
回复了 rv54ntjwfm3ug8 创建的主题 Go 编程语言 Go 如何根据请求头限制请求频率?
请求头只是个 key,所以请求头跟问题本身无关。只问怎么限流就行了

多节点就 redis 之类的:
https://github.com/go-redis/redis_rate

单节点就进程内,官方扩展包:
https://github.com/golang/time
或者其他第三方实现挺多的、自己手撸也不难
2021-04-07 12:05:20 +08:00
回复了 zkdfbb 创建的主题 Go 编程语言 map 的一个神奇的问题
@zkdfbb 基于标准库的知名框架都比较稳,功能和周边也都差不多,按 star 就 gin,按喜好就看自己了
2021-04-07 11:40:20 +08:00
回复了 zkdfbb 创建的主题 Go 编程语言 map 的一个神奇的问题
@makdon 63 楼回答的好
@Lpl 现在是互夸了

enjoy coding, have fun ~
1 ... 48  49  50  51  52  53  54  55  56  57 ... 63  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1342 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 20ms · UTC 17:28 · PVG 01:28 · LAX 09:28 · JFK 12:28
Developed with CodeLauncher
♥ Do have faith in what you're doing.