如何使用 pthread 和条件变量改进多线程应用程序中的实时行为?

发布于 2024-09-02 20:39:05 字数 630 浏览 2 评论 0原文

我有一个使用 pthread 的多线程应用程序。我有一个互斥锁()和条件变量()。有两个线程,一个线程正在为第二个线程(即工作线程)生成数据,该线程试图以实时方式处理生成的数据,以便尽可能接近固定时间段的流逝来处理一个卡盘。

这工作得很好,但是,偶尔当生产者线程释放工作线程正在等待的条件时,在工作线程获得控制并再次执行之前会出现长达几乎一整秒的延迟。

我知道这一点,因为在生产者释放工作人员正在等待的条件之前,如果需要处理另一个卡盘,它会为工作人员执行一个卡盘处理,然后在工作线程中接收到条件后,它也会立即执行此操作如果需要加工另一个卡盘,则加工一个卡盘。

在后面的例子中,我发现我多次延迟处理卡盘。我想消除这种效率损失,并尽我所能使卡盘的滴答声尽可能接近所需的频率。

我可以采取什么措施来减少生产者的释放条件与检测到该条件已释放以使工作人员恢复处理之间的延迟?例如,生产者调用某些东西来强制自己切换上下文是否有帮助?

最重要的是,工作人员每次要求生产者为自己创建工作时都必须等待,以便生产者可以在告诉工作人员它已准备好再次并行运行之前破坏工作人员的数据结构。生产者的独占访问时间应该很短,但在此期间,我还在检查生产者在拥有独占访问权限时代表工作人员完成的实时工作。不知何故,我再次重新并行运行会导致偶尔出现明显的延迟,这是我想避免的。请建议如何最好地实现这一点。

I have a multi-threaded application that is using pthreads. I have a mutex() lock and condition variables(). There are two threads, one thread is producing data for the second thread, a worker, which is trying to process the produced data in a real time fashion such that one chuck is processed as close to the elapsing of a fixed time period as possible.

This works pretty well, however, occasionally when the producer thread releases the condition upon which the worker is waiting, a delay of up to almost a whole second is seen before the worker thread gets control and executes again.

I know this because right before the producer releases the condition upon which the worker is waiting, it does a chuck of processing for the worker if it is time to process another chuck, then immediately upon receiving the condition in the worker thread, it also does a chuck of processing if it is time to process another chuck.

In this later case, I am seeing that I am late processing the chuck many times. I'd like to eliminate this lost efficiency and do what I can to keep the chucks ticking away as close to possible to the desired frequency.

Is there anything I can do to reduce the delay between the release condition from the producer and the detection that that condition is released such that the worker resumes processing? For example, would it help for the producer to call something to force itself to be context switched out?

Bottom line is the worker has to wait each time it asks the producer to create work for itself so that the producer can muck with the worker's data structures before telling the worker it is ready to run in parallel again. This period of exclusive access by the producer is meant to be short, but during this period, I am also checking for real-time work to be done by the producer on behalf of the worker while the producer has exclusive access. Somehow my hand off back to running in parallel again results in significant delay occasionally that I would like to avoid. Please suggest how this might be best accomplished.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

Bonjour°[大白 2024-09-09 20:39:05

我可以建议以下模式。通常可以使用相同的技术,例如在某些实时渲染器或类似的东西中预缓冲帧时。

首先,很明显,您在消息中描述的方法只有在两个线程始终加载相同(或几乎相同)的情况下才有效。如果没有,多线程实际上会对您的情况有利。

现在,让我们考虑一下最适合您的问题的线程模式。假设我们有一个 yielding 和一个 processing 线程。第一个准备要处理的数据块,第二个进行处理并将处理结果存储在某处(实际上并不重要)。

让这些线程协同工作的有效方法是适当的屈服机制。您的yielding线程应该只是将数据添加到某个共享缓冲区,并且实际上不应该关心该数据会发生什么。而且,您的缓冲区可以实现为一个简单的 FIFO 队列。这意味着您的 yielding 线程应该准备要处理的数据并对队列进行 PUSH 调用:

X = PREPARE_DATA()
BUFFER.LOCK()
BUFFER.PUSH(X)
BUFFER.UNLOCK()

现在是 processing 线程。它的行为应该这样描述(您可能应该在调用 EMPTY 之间添加一些人为延迟,例如 SLEEP(X)),

IF !EMPTY(BUFFER) PROCESS(BUFFER.TOP)

这里重要的时刻是您的处理线程应该做什么与处理过的数据。显而易见的方法是在处理数据后进行 POP 调用,但您可能想要一些更好的想法。无论如何,在我的变体中,这看起来像

// After data is processed
BUFFER.LOCK()
BUFFER.POP()
BUFFER.UNLOCK()

请注意,yieldingprocessing 线程中的锁定操作实际上不会影响您的性能,因为每个数据块只调用一次它们。


现在,有趣的部分。正如我在开头所写的,只有当线程在 CPU/资源使用方面表现得有些相同时,这种方法才会有效。有一种方法可以使这些线程解决方案有效,即使这个条件并不总是成立并且对某些其他运行时条件很重要。

这种方式意味着创建另一个称为控制器线程的线程。该线程仅比较每个线程用于处理一大块数据的时间并相应地平衡线程优先级。实际上,我们不必“比较时间”控制器线程可以简单地以如下方式工作:

IF BUFFER.SIZE() > T
   DECREASE_PRIORITY(YIELDING_THREAD)
   INCREASE_PRIORITY(PROCESSING_THREAD)

当然,您可以实现一些更好的启发式方法这里但是控制器线程的方法应该很清楚。

I could suggest the following pattern. Generally the same technique could be used, e.g. when prebuffering frames in some real-time renderers or something like that.

First, it's obvious that approach that you describe in your message would only be effective if both of your threads are loaded equally (or almost equally) all the time. If not, multi-threading would actually benefit in your situation.

Now, let's think about a thread pattern that would be optimal for your problem. Assume we have a yielding and a processing thread. First of them prepares chunks of data to process, the second makes processing and stores the processing result somewhere (not actually important).

The effective way to make these threads work together is the proper yielding mechanism. Your yielding thread should simply add data to some shared buffer and shouldn't actually care about what would happen with that data. And, well, your buffer could be implemented as a simple FIFO queue. This means that your yielding thread should prepare data to process and make a PUSH call to your queue:

X = PREPARE_DATA()
BUFFER.LOCK()
BUFFER.PUSH(X)
BUFFER.UNLOCK()

Now, the processing thread. It's behaviour should be described this way (you should probably add some artificial delay like SLEEP(X) between calls to EMPTY)

IF !EMPTY(BUFFER) PROCESS(BUFFER.TOP)

The important moment here is what should your processing thread do with processed data. The obvious approach means making a POP call after the data is processed, but you will probably want to come with some better idea. Anyway, in my variant this would look like

// After data is processed
BUFFER.LOCK()
BUFFER.POP()
BUFFER.UNLOCK()

Note that locking operations in yielding and processing threads shouldn't actually impact your performance because they are only called once per chunk of data.


Now, the interesting part. As I wrote at the beginning, this approach would only be effective if threads act somewhat the same in terms of CPU / Resource usage. There is a way to make these threading solution effective even if this condition is not constantly true and matters on some other runtime conditions.

This way means creating another thread that is called controller thread. This thread would merely compare the time that each thread uses to process one chunk of data and balance the thread priorities accordingly. Actually, we don't have to "compare the time", the controller thread could simply work the way like:

IF BUFFER.SIZE() > T
   DECREASE_PRIORITY(YIELDING_THREAD)
   INCREASE_PRIORITY(PROCESSING_THREAD)

Of course, you could implement some better heuristics here but the approach with controller thread should be clear.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文