公平临界区 (Linux)
在多线程 Linux 应用程序中,我对关键部分使用互斥体。除了公平性问题之外,这非常有效。可能会发生这样的情况:一个线程离开临界区并立即重新进入,不会给任何其他线程机会。例如,
while(true)
{
critsect.enter();
... do calculations ...
... maybe call a blocking operation so we sleep ...
critsect.leave();
}
很可能会阻止任何其他线程进入同一临界区。互斥是不公平的。
有没有办法做出公平的关键部分?我正在考虑添加一个队列,以便关键部分按照其“到达”的顺序执行。或者,如果其他线程正在等待,则至少有一个计数器可以在解锁后执行 pthread_yield() 。
针对这种需求有推荐的做法吗?
On a multi-threaded Linux application I use a mutex for critical sections. This works very well except for the fairness issue. It can happen that a thread leaving a critical section and re-entering right away does not give any other thread a chance. For example
while(true)
{
critsect.enter();
... do calculations ...
... maybe call a blocking operation so we sleep ...
critsect.leave();
}
might very likely stop any other thread to enter the same critical section. Mutexe are not fair.
Is there a solution to making a fair critical section? I was thinking of adding a queue so that critical sections are executed in the order of their 'arrival'. Alternatively at least a counter to maybe do a pthread_yield() after unlock if other threads are waiting.
Is there a recommended practice for this kind of requirement?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您可以在 pthreads 互斥体之上构建 FIFO“票证锁”,按照以下思路:
在这种方案下,当线程位于票证锁保护的关键部分内时,不会持有低级 pthreads 互斥体,从而允许其他线程加入队列。
You can build a FIFO "ticket lock" on top of pthreads mutexes, along these lines:
Under this kind of scheme, no low-level pthreads mutex is held while a thread is within the ticketlock protected critical section, allowing other threads to join the queue.
即使有一个相当好的关键部分,代码也可能会具有可怕的性能,因为如果关键部分被保留很长时间,线程通常会等待它。
因此,我建议您尝试重构代码,这样就不需要长时间锁定关键部分。要么完全使用不同的方法(通常建议通过消息队列传递对象,因为它很容易正确),要么至少在不持有锁的情况下对局部变量进行大部分计算,而不是仅使用锁来存储结果。如果持有锁的时间较短,线程等待它的时间就会减少,这通常会提高性能并使公平性不再成为问题。您还可以尝试增加锁定粒度(单独锁定较小的对象),这也会减少争用。
编辑:好吧,考虑一下,我相信 Linux 中的每个关键部分都是大致公平的。每当有睡眠者时,解锁操作就必须进入内核以告诉它唤醒它们。从内核返回期间,调度程序运行并选择具有最高优先级的进程。睡眠者在等待时优先级会上升,因此在某些时候它们的优先级会足够高,以至于释放会导致任务切换。
Even with a fair critical section, the code is probably going to have horrible performance, because if the critical section is being held for long periods of time, threads will be often waiting for it.
So I'd suggest you try to restructure the code so it does not need to lock critical sections over extended periods of time. Either by using different approach altogether (passing objects over message queue is often recommended, because it's easy to get right) or at least by doing most of the calculation on local variables without holding the lock and than only taking the lock to store the results. If the lock is held for shorter periods of time, the threads will spend less time waiting for it, which will generally improve performance and make fairness a non-issue. You can also try to increase lock granularity (lock smaller objects separately), which will also reduce the contention.
Edit: Ok, thinking about it, I believe every critical section in Linux is approximately fair. Whenever there are sleepers, the unlock operation has to enter kernel to tell it to wake them up. During return from kernel, scheduler runs and picks the process with highest priority. The sleepers rise in priority when waiting, so at some point they will be high enough that the release will cause a task swtich.
如果你的主张成立(我没有时间阅读,而且看起来你在发布问题之前已经研究过这一点),我建议
在关键部分之间明确让步。
If your claim holds true (I haven't got the time to read up, and it would appear as though you have researched this before posting the question), I suggest
to explicitely yield in between critical sections.
好的,这样怎么样:
Init.信号量计数为 1。在尝试获取 CS 并在完成后向其发出信号之前,让其他线程也等待该信号量。如果“计算”线程获得 sema,它就可以到达 CS 并锁定它。一旦进入锁内,但在长计算之前,sema 会发出信号,然后另一个线程可以到达 CS 但不能进入其中。当“计算”线程退出锁时,它无法循环并重新锁定它,因为 sema. count 为零,因此另一个线程获得锁。 “计算”线程必须等待 sema,直到进入的另一个线程完成其访问并向 sema 发出信号。
通过这种方式,另一个线程可以“保留”对数据的访问,即使它实际上还无法获取数据。
平均值,
马丁
OK, how about this:
Init. the semaphore count to 1. Have other threads wait on the semaphore as well before trying to get the CS and signal it when done. If the 'calculate' thread gets the sema, it can get to the CS and lock it. Once inside the lock, but before the long claculate, the sema is signaled and one other thread can then reach the CS but not get inside it. When the 'calculate' thread exits the lock, it cannot loop round and relock it because the sema. count is zero, so the other thread gets the lock. The 'calculate' thread has to wait on the sema until the other thread that got in has finished with its access and signals the sema.
In this way, another thread can 'reserve' access to the data even though it cannot actually get at it yet.
Rgds,
Martin
恕我直言,您可以在 Linux 上使用 FIFO SCHEDULER 并更改线程的优先级:
IMHO you can use a FIFO SCHEDULER on Linux and change de priority of threads: