-
Notifications
You must be signed in to change notification settings - Fork 284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
linux内核级同步机制--futex #8
Comments
futex在真正将进程挂起之前会检查addr指向的地址的值是否等于val,如果不相等则会立即返回,由用户态继续trylock。否则将当期线程插入到一个队列中去,并挂起。 这一段有点没有懂,futex先检查addr指向的地址的值是否等于val,在到将当期线程插入到一个队列中去,并挂起。之间不是也有一个窗口么?如果检查addr指向的地址的值等于val,在放入队列并挂起之前有恰好有线程释放了锁,不是还是会有当前线程没有办法被唤醒的风险了么? |
@chen-shang 下午的源码分析中有解释
其实就是通过自旋锁将 |
那对于那个有空窗期的代码,我们也在try_lock和wait之间加自旋锁不就没有空窗期了吗,还有一个问题就是这个futex和mutex有什么区别吗...比较是两个不同的api |
是的,上面的空窗期,如果你能用一个volatile修饰的AtomicInteger当做标识符,那么就不存在空窗期了 @hanxuan123 |
谁来调用futex_wake方法呢?靠上层的线程通知notify吗? |
唤醒的顺序是什么?看过其它地方有种说法是按线程优先级顺序来唤醒的,等待队列按照优先级排序 |
futex的wait和wake过程,看起来和javaReentrantLock工作方式如出一辙,既然底层有了这样的实现了。jdk为什么还要再写一遍相同的过程,而且ReentrantLock在park和unpark的时候还会调用pthread中的pthread_cond_timewait最终还是通过futex系统调用。 |
不知道作者的文章是能否随意转载, 发现了 这篇文章 提醒下是否侵权 |
我觉得还是用户态和内核态的区别吧 |
volatile 不能保证原子性,两行代码不加锁 总会有空窗期的,这是我的理解 |
try_lock是循环的条件 在循环while前后加锁解锁没有意义,如果在while前后加锁解锁 trylock要么一次成功,要么永远不成功 |
那么对于不同的方式申请的锁,比如共享内存创建的锁变量,在每个进程的虚拟地址是不同的。而线程组中的锁变量虚拟地址相同,当进行哈希的时候是怎么区分二者的呢? |
所以说wait wake最底层还是用到了自旋锁,做最终资源的互斥 |
在关于同步的一点思考-下一文中,我们知道glibc的
pthread_cond_timedwait
底层是用linux futex机制实现的。更多文章见个人博客:https://github.com/farmerjohngit/myblog
理想的同步机制应该是没有锁冲突时在用户态利用原子指令就解决问题,而需要挂起等待时再使用内核提供的系统调用进行睡眠与唤醒。换句话说,在用户态的自旋失败时,能不能让进程挂起,由持有锁的线程释放锁时将其唤醒?
如果你没有较深入地考虑过这个问题,很可能想当然的认为类似于这样就行了(伪代码):
上述代码的问题是trylock和wait两个调用之间存在一个窗口:
如果一个线程trylock失败,在调用wait时持有锁的线程释放了锁,当前线程还是会调用wait进行等待,但之后就没有人再唤醒该线程了。
为了解决上述问题,linux内核引入了futex机制,futex主要包括等待和唤醒两个方法:
futex_wait
和futex_wake
,其定义如下futex在真正将进程挂起之前会检查addr指向的地址的值是否等于val,如果不相等则会立即返回,由用户态继续trylock。否则将当期线程插入到一个队列中去,并挂起。
在关于同步的一点思考-上文章中对futex的背景与基本原理有介绍,对futex不熟悉的人可以先看下。
本文将深入分析futex的实现,让读者对于锁的最底层实现方式有直观认识,再结合之前的两篇文章(关于同步的一点思考-上和关于同步的一点思考-下)能对操作系统的同步机制有个全面的理解。
下文中的进程一词包括常规进程与线程。
futex_wait
在看下面的源码分析前,先思考一个问题:如何确保挂起进程时,val的值是没有被其他进程修改过的?
代码在kernel/futex.c中
在将进程阻塞前会将当期进程插入到一个等待队列中,需要注意的是这里说的等待队列其实是一个类似Java HashMap的结构,全局唯一。
着重看
futex_wait_setup
和两个函数futex_wait_queue_me
函数
futex_wait_setup
中主要做了两件事,一是获得自旋锁,二是判断*uaddr是否为预期值。futex_wait_queue_me
中主要做几件事:如何保证条件与等待之间的原子性
在
futex_wait_setup
方法中会加自旋锁;在futex_wait_queue_me
中将状态设置为TASK_INTERRUPTIBLE
,调用queue_me
将当期线程插入到等待队列中,然后才释放自旋锁。也就是说检查uaddr的值的过程跟进程挂起的过程放在同一个临界区中。当释放自旋锁后,这时再更改addr地址的值已经没有关系了,因为当期进程已经加入到等待队列中,能被wake唤醒,不会出现本文开头提到的没人唤醒的问题。futex_wait小结
总结下
futex_wait
流程:TASK_INTERRUPTIBLE
futex_wake
futex_wake
futex_wake
流程如下:futex_hash_bucket
,即代码中的hbwake_futex
唤起等待的进程wake_futex
中将制定进程状态设置为TASK_RUNNING
并加入到系统调度列表中,同时将进程从futex的等待队列中移除掉,具体代码就不分析了,有兴趣的可以自行研究。End
Java中的ReentrantLock,Object.wait和Thread.sleep等等底层都是用futex进行线程同步,理解futex的实现能帮助你更好的理解与使用这些上层的同步机制。另外因篇幅与精力有限,涉及到进程调度的相关内容没有具体分析,不过并不妨碍理解文章内容,
The text was updated successfully, but these errors were encountered: