请问一些操作系统基础,线程能够进入等待/阻塞状态的底层原理是什么?

2021-03-26 11:26:19 +08:00
 LeeReamond

如题,学习 Go 的过程中认识到管道这种进程间通信方式,感觉确实是比直接共享内存更合适的线程间同步模型。

学习过程中产生一些问题,涉及到语言如何和操作系统相关联

1 、管道通常有一个阻塞读取功能,比如消费者调用 recv()后进入阻塞,然后由生产者向管道内丢入一个数据,消费者就可以解除阻塞并获取数据,相当于被唤醒,这在语言层面上是如何实现的呢,我们在业务代码中似乎没有办法显式地让一个线程进入阻塞,然后再用另一个线程去 trigger 这个线程。

2 、这种操作非常类似 socket 表现的性状,但我觉得怎么也不会用 socket 实现管道吧,我觉得对于统一进程内间的线程同步,socket 是一种非常昂贵的通信手段。

3 、我们是否有办法显示地管理线程,比如子线程主动进入 block,而后主线程主动唤醒子线程,这种管理是否是可以实现的。类似地,还有主动地关闭一个线程的需求,不过据我所知线程本身似乎是并未设计在外部干预下关闭的这么一个功能。

4 、类似地,java 语言中还有一个 wait 的状态,与 block 不同,wait 通常是指等待锁,而不是等待 IO,其他语言可以释放锁并唤醒一个 wait 线程,这与 block 又有什么区别呢? block 和 wait 的概念是 java 的概念还是操作系统的概念?

问的比较乱,基础不扎实,有了解的朋友请帮忙回复一下,谢谢大家。

2514 次点击
所在节点    问与答
28 条回复
ydongd
2021-03-26 17:24:57 +08:00
go 中的堵塞不是操作系统上的概念,只是表现结果相似。有自己的 GPM 调度模型,它的堵塞是将当前协程让出运行的系统线程,将自己加入到等待队列,不再执行调度,直到事件到来,条件满足再加入到运行队列等待调度。这里的等待队列示情况而定,比如 channel,就是加入到 channel 的等待队列,接受到数据后将其放入 go 运行的全局队列。实际上 go 中的所有涉及到系统调用的堵塞,都是封装过的,socket 在读数据没有数据时,将 go 程放到 go 实现的 netpoller 中等待,直到数据过来再加入到可运行队列。
irytu
2021-03-26 20:14:58 +08:00
应该是让出 CPU 给其他任务,等待的线程 /进程会被有需要的时候 wake up,比如 IO 中断
sosilver
2021-03-26 21:49:01 +08:00
交出运行权(等待),反正要么主动要么被打断。针对 os 抽象的就是通过系统调用被塞入队列,比如你提的 rust channel 的 recv,内部调用 thread::park ( park 更下面通过 libc 等调某个系统调用实现)。轻量些的协程、async/await 等虽然不陷入系统,但是本质还是在某些调用点转移出去(比如.await )。只不过一类由 os 调度,一类由 runtime 调度。
被打断的情况当然就是中断了。
akira
2021-03-26 22:06:09 +08:00
管道 本质上就是文件来的吧,socket 也是一种文件吧
busymilk
2021-03-27 10:53:42 +08:00
cpu 不调你的线程,不就行了……
Claar
2021-03-27 15:04:11 +08:00
1.要探讨真正的阻塞不应该看 go,因为 go 的阻塞本质应该是非阻塞,只是调度器挂起了任务;大概的过程是调度器发现有个任务想要阻塞等待一个 io,所以调度器直接把任务挂起了,并开始执行下一个任务,在开发者看来和等待没什么区别。
2.看 linux 的源码可以看到真正的阻塞的代码
nevin47
2021-03-27 15:41:28 +08:00
@learningman #17
@Jooooooooo #18
我想表达有问题的是:“所有的等待本质都是轮询”这个论述,不过昨天确实留了个言就跑了 sorry....

我对 Go 不是特别熟悉,所以只能用 Linux 内核来举点例子。例如低精度的定时器,是通过 Jiffies 作为单位来的,背后确实就是一个 timer service 去轮询 n 级 timer 链表,然后定时触发回调。这种属于通过 调度器+轮询 来实现的等待(实际上例如低精度的 sleep 库函数这种的,都是类似)

但是对于 hrtimer,或者某些要做硬实时的地方,我们往往喜欢使用 CPU 的 timer intterrupt,不同的体系结构有略微不同的实现,但是原理都差不多,要靠一个小的模块,例如 TSC/HPET,我没记错的 ARM/X86 都叫这个名字

以上是我凭记忆写的,不一定十分。更多细节可以参考下《 ULK 》和 ARM/X86 芯片手册的 timer 子章节
lcdtyph
2021-03-28 00:44:57 +08:00
@nevin47
tsc 是 x86 平台的,是内置在 cpu 里东西,精度极高可以达到纳秒级,而且访问延迟低,在 intel 搞出来 invariant tsc 之后现代 linux 在 x86 平台会默认选择这个作为 clocksource

hpet 是内置在主板芯片组中的,最开始是 intel 和微软弄出来的,但是精度基本都在最低要求的百纳秒级,而且因为不在 cpu 里面所以访问延迟很高

arm 上类似 tsc 的东西是某个 pmc 寄存器的值,但是它没有 invariant 的特性所以不能被用作 clocksource,而是需要板载一个时钟模块,然后内核做 mmio 来操作这个外部定时器

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

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

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

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

© 2021 V2EX