我本来是以为Rust的channel中,sender写了数据后,是通过pthread_cond_signal()来唤醒receiver的。
但刚才写了个示例程序,然后用 strace 执行,并没有发现任何的系统调用,receiver就被唤醒了。
那是用什么方法唤醒的? 多线程间的唤醒,是可以不经过系统调用的吗?
1
共 10 条评论, 1 页
我本来是以为Rust的channel中,sender写了数据后,是通过pthread_cond_signal()来唤醒receiver的。
但刚才写了个示例程序,然后用 strace 执行,并没有发现任何的系统调用,receiver就被唤醒了。
那是用什么方法唤醒的? 多线程间的唤醒,是可以不经过系统调用的吗?
评论区
写评论是的,并且pthread_cond_signal在常用的glibc库里头也是使用futex实现的,于是性能跟Rust的消息渠道应该不分上下。
--
👇
WuBingzheng: 跟我最开始想的一样,通过 r_waiting 来控制是否需要调用 pthread_cond_signal来唤醒receiver。
看有很多star,应该不错。我使用一下。
另外,看了下 发送消息的代码,跟我最开始想的一样,通过 r_waiting 来控制是否需要调用 pthread_cond_signal来唤醒receiver。
--
👇
hax10: 你可以看看chan
--
👇
WuBingzheng:
顺便再问下,你知道C语言有这种成熟的channel库吗?
--
我明白你的意思。你是从具体实现的角度来解释的。
我刚才说的 ”因为他知道receiver后续肯定会执行recv()的“ 是从业务逻辑角度理解的。大概就类似于 https://stackoverflow.com/questions/5536759/condition-variable-why-calling-pthread-cond-signal-before-calling-pthread-co 这个问题。
多谢解惑。
--
👇
Pikachu: > 如果此时receiver还没有执行recv(),那么sender都不会调用syscall,我猜应该是因为他知道receiver后续肯定会执行recv()的,届时肯定会检查channel的,于是就不需要syscall来唤醒。
这个理解是错的。它的实际行为是这样的:
你可以看看chan
--
👇
WuBingzheng:
顺便再问下,你知道C语言有这种成熟的channel库吗?
--
这个理解是错的。它的实际行为是这样的:
你所看到的recv调用前,sender不会调用syscall,其原因是receiver所在的线程还没有被加入selectors,而不是因为“后续肯定会recv”。
按照你的思路,又改了几次代码然后用strace检查,发现确实比我想象的高级很多。
比如哪怕sender在发送第一条消息的时候,如果此时receiver还没有执行recv(),那么sender都不会调用syscall,我猜应该是因为他知道receiver后续肯定会执行recv()的,届时肯定会检查channel的,于是就不需要syscall来唤醒。
之前一直听说futex很复杂,不建议直接学习。果然如此。
顺便再问下,你知道C语言有这种成熟的channel库吗?
--
👇
Pikachu: 感觉上不太有必要。
unpark的内部实现是这样的:首先对一个atomic变量做swap,如果swap出来的值是PARKED的话,才会调用syscall。也就是说,你想要的“省略不必要的syscall”,标准库已经帮你做掉了。
std::sys_common::thread_parking::futex
当然如果你想进一步省去atomic带来的cache line相关的开销的话,那就超出我的知识范围了。你可以去看一下Rust atomic and locks有没有相关的内容。
感觉上不太有必要。
unpark的内部实现是这样的:首先对一个atomic变量做swap,如果swap出来的值是PARKED的话,才会调用syscall。也就是说,你想要的“省略不必要的syscall”,标准库已经帮你做掉了。
std::sys_common::thread_parking::futex
当然如果你想进一步省去atomic带来的cache line相关的开销的话,那就超出我的知识范围了。你可以去看一下Rust atomic and locks有没有相关的内容。
多谢回复。
我查这个问题的本意是源于另外一个想法:对于channel的场景,很多时候sender会持续发送多条消息。如果每发送一条就要调用一次syscall的话,是不是有些浪费,这里有没有优化空间?
比如,向一个空channel(即之前所有消息都被消费了)里发消息,肯定是需要syscall来通知对方的。但是,当发送一条消息后,如果检查channel里还有消息没有被消费,那说明之前已经调用过syscall,并且receiver还没消费或者正在消费中。而receiver消费一般都是一个loop一直读channel,读到空为止(而不是被syscall通知了几次就读几次(我猜的))。那后面的这个消息,是不是就不用调用syscall了。因为receiver的loop肯定可以读到最新的这个消息。
这只是一个大概的思路,里面应该有一些边界条件会有数据竞争,但应该也是可以克服的。
这个思路可行吗?
--
👇
Pikachu: 这个时候读源码一定是最容易的。
首先,channel唤醒thread用的是Thread::unpark。在多数系统上,unpark是基于futex实现的。std::sys_common::thread_parking::futex
再进一步看futex的实现,基本就追溯到了syscall。std::sys::unix::futex
我不太确定为什么你用strace看不到syscall,但源码里确实用了syscall。要不你再检查一下你的参数有没有问题,或者gdb单步调试一下试试看?
知道了,是我的问题。。。。
strace默认不监控子线程的系统调用,要加-f参数显式指定才行。 加了-f后发现还是有系统调用的。那跟预期相符了。
这个时候读源码一定是最容易的。
首先,channel唤醒thread用的是Thread::unpark。在多数系统上,unpark是基于futex实现的。std::sys_common::thread_parking::futex
再进一步看futex的实现,基本就追溯到了syscall。std::sys::unix::futex
我不太确定为什么你用strace看不到syscall,但源码里确实用了syscall。要不你再检查一下你的参数有没有问题,或者gdb单步调试一下试试看?