Skip to content

Commit cc602b1

Browse files
committed
Merge branch 'master' of github.com:cch123/golang-notes
2 parents e73f3b5 + 1d221f6 commit cc602b1

File tree

3 files changed

+90
-4
lines changed

3 files changed

+90
-4
lines changed

scheduler.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,7 +1618,7 @@ findrunnable 比较复杂,流程图先把 gc 相关的省略掉了:
16181618

16191619
```mermaid
16201620
graph TD
1621-
runqget --> A[gp == nil]
1621+
localrunqget --> A[gp == nil]
16221622
A --> |no|return
16231623
A --> |yes|globrunqget
16241624
globrunqget --> B[gp == nil]
@@ -1641,7 +1641,7 @@ H --> |yes|I[netpoll]
16411641
I --> J[gp == nil]
16421642
J --> |no| return
16431643
J --> |yes| stopm
1644-
stopm --> runqget
1644+
stopm --> localrunqget
16451645
```
16461646

16471647
```go

sync.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,9 @@ func (e *entry) tryExpungeLocked() (isExpunged bool) {
10041004
return p == expunged
10051005
}
10061006
```
1007-
1007+
- sync.Map 利用了读写分离的思路为读多写少或读写不同 key 的场景而设计,当违背这种设计初衷来使用 sync.Map 的时候性能或许达不到你的期待
1008+
- 可以参考下其他诸如散列思路减少锁开销的并发安全 [Map](https://github.com/orcaman/concurrent-map/
1009+
)
10081010
# 参考资料
10091011

10101012
http://www.weixianmanbu.com/article/736.html

syscall.md

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,96 @@ ok2:
175175
RET
176176
```
177177

178-
RawSyscall 和 Syscall 的区别也非常微小,就只是在进入 Syscall 和退出的时候没有通知 runtime,这样 runtime 理论上是没有办法通过调度把这个 g 的 m 的 p 调度走的,所以如果用户代码使用了 RawSyscall 来做一些阻塞的系统调用,是有可能阻塞其它的 g 的,下面是官方开发的原话:
178+
golang 旧版本中 RawSyscall 和 Syscall 的区别也非常微小,就只是在进入 Syscall 和退出的时候没有通知 runtime,这样 runtime 理论上是没有办法通过调度把这个 g 的 m 的 p 调度走的,所以如果用户代码使用了 RawSyscall 来做一些阻塞的系统调用,是有可能阻塞其它的 g 的,下面是官方开发的原话:
179179

180180
> Yes, if you call RawSyscall you may block other goroutines from running. The system monitor may start them up after a while, but I think there are cases where it won't. I would say that Go programs should always call Syscall. RawSyscall exists to make it slightly more efficient to call system calls that never block, such as getpid. But it's really an internal mechanism.
181181

182182
RawSyscall 只是为了在执行那些一定不会阻塞的系统调用时,能节省两次对 runtime 的函数调用消耗。
183183

184+
#### 新版本抢占式调度中的 RawSyscall 和 Syscall
185+
由于 `RawSyscall` 相较于 `Syscall` 缺少了 `runtime·entersyscall(SB)` 以及 `runtime·exitsyscall(SB)` 的调用,当 `g` 执行的是阻塞性质的系统调用的时候,当前 `g` 会维持 `running` 状态,runtime 系统监控在进行全局调度的时候一旦发现运行超过 10ms 的 `g` 就会执行抢占操作(1.14.3 版本, linux_amd64 下为例),通过发送信号量给 `g` 对应的线程,而由于线程在初始化的时候进行了信号量的监听以及设置了相应的 `sa_flags` 参数,虽然包含诸如`SA_RESTART`参数会让系统调用在信号中断后自动恢复,但是不是对所有系统调用都会有效,这将会导致在收到信号量的时候对正在阻塞的系统调用产生中断,这种行为往往会给使用者带来意料之外的情况,以下通过一个简单的阻塞性系统调用案例来演示(futex):
186+
```go
187+
// futex
188+
package main
189+
190+
import (
191+
"fmt"
192+
"syscall"
193+
"time"
194+
"unsafe"
195+
)
196+
197+
const (
198+
SYS_futex = 202
199+
_FUTEX_PRIVATE_FLAG = 128
200+
_FUTEX_WAIT = 0
201+
_FUTEX_WAKE = 1
202+
_FUTEX_WAIT_PRIVATE = _FUTEX_WAIT | _FUTEX_PRIVATE_FLAG
203+
_FUTEX_WAKE_PRIVATE = _FUTEX_WAKE | _FUTEX_PRIVATE_FLAG
204+
)
205+
206+
func main() {
207+
var futexVar uint32 = 1
208+
for i := 0; i < 3; i++ {
209+
go func() { // 启动 3 个线程进行阻塞系统调用
210+
fmt.Println("sleep start")
211+
type timespec struct {
212+
tv_sec int64
213+
tv_nsec int64
214+
}
215+
/*
216+
* 设置 10 秒唤醒 futex
217+
* 通过调节该参数大小可以测试:
218+
* 当执行时间小于 runtime 调度器的判定时间的时候,阻塞调用可正常进行
219+
*/
220+
var ns int64 = 1e9 * 10
221+
ts := timespec{
222+
tv_sec: ns / 1e9,
223+
tv_nsec: ns % 1e9,
224+
}
225+
// var ts timespec
226+
// ts.setNsec(ns)
227+
fmt.Println(syscall.RawSyscall6(
228+
SYS_futex, // trap AX 202
229+
uintptr(unsafe.Pointer(&futexVar)), // a1 DI 1
230+
uintptr(_FUTEX_WAIT), // a2 SI 0
231+
1, // a3 DX
232+
// futex 超时参数,经测试,在设置为 NULL 的情况下(永不超时)
233+
// 再起一个线程(下方注释部分代码)修改 futexVar 的值可以发现另一个系统调用异常,感兴趣的读者可自行测试验证
234+
uintptr(unsafe.Pointer(&ts)), // a4 R10
235+
0, // a5 R8
236+
0), // a6 R9
237+
)
238+
fmt.Println("sleep end")
239+
}()
240+
}
241+
// go func () {
242+
// select {
243+
// case <-time.After(time.Second):
244+
// futexVar = 2
245+
// }
246+
// }
247+
for {
248+
select {
249+
case <-time.After(time.Hour):
250+
}
251+
}
252+
}
253+
254+
```
255+
执行结果:
256+
```shell
257+
sleep start
258+
sleep start
259+
sleep start
260+
18446744073709551615 0 interrupted system call # 线程收到 SIGURG ,系统调用被信号中断
261+
sleep end
262+
18446744073709551615 0 interrupted system call
263+
sleep end
264+
18446744073709551615 0 interrupted system call
265+
sleep end
266+
```
267+
综上,在使用 `RawSyscall` 的时候需要自行根据 golang 版本确定是否符合你的预期
184268
## vdso
185269

186270
vdso 可以认为是一种特殊的调用,在使用时,没有本文开头的用户态到内核态的切换,引用一段参考资料:

0 commit comments

Comments
 (0)