Как я могу улучшить свое поведение в реальном времени в многопоточном приложении, используя pthreads и условные переменные? - PullRequest
0 голосов
/ 24 мая 2010

У меня есть многопоточное приложение, которое использует pthreads. У меня есть блокировка mutex () и условные переменные (). Существует два потока, один поток создает данные для второго потока, рабочего, который пытается обработать полученные данные в режиме реального времени так, чтобы один обработчик обрабатывался как можно ближе к истечению фиксированного периода времени, насколько это возможно.

Это работает довольно хорошо, однако, иногда, когда поток производителя освобождает условие, при котором рабочий ожидает, задержка почти до целой секунды видна, прежде чем рабочий поток получает управление и выполняется снова.

Я знаю это, потому что непосредственно перед тем, как производитель выпускает условие, при котором рабочий ожидает, он выполняет обработку для рабочего, если пришло время обработать другой, затем сразу после получения условия в рабочем потоке, он также выполняет обработку обработки, если пришло время обработать другую обработку.

В этом более позднем случае я вижу, что опаздываю многократно обрабатывать патрон. Я хотел бы устранить эту потерянную эффективность и сделать все возможное, чтобы патроны тикали как можно ближе к желаемой частоте.

Могу ли я что-нибудь сделать, чтобы уменьшить задержку между условием освобождения от производителя и обнаружением, что это условие выпущено так, что работник возобновляет обработку? Например, поможет ли продюсер вызвать что-то, чтобы заставить себя переключиться в контекст?

Суть в том, что работнику приходится ждать каждый раз, когда он просит продюсера создать работу для себя, чтобы продюсер мог перебирать структуры данных работника, прежде чем сообщить работнику, что он снова готов к параллельной работе. Этот период монопольного доступа производителя должен быть коротким, но в течение этого периода я также проверяю работу в режиме реального времени, которую продюсер должен выполнять от имени работника, в то время как у производителя есть эксклюзивный доступ. Почему-то моя рука снова начинает работать параллельно, что иногда приводит к значительной задержке, которой я бы хотел избежать. Пожалуйста, предложите, как это может быть лучше всего достигнуто.

1 Ответ

2 голосов
/ 24 мая 2010

Я мог бы предложить следующую схему. Как правило, можно использовать тот же метод, например, когда пребуферизует кадры в некоторых рендерерах в реальном времени или что-то в этом роде.

Во-первых, очевидно, что подход, который вы описываете в своем сообщении, будет эффективным , если оба ваших потока будут загружены одинаково (или почти одинаково) все время. Если нет, многопоточность действительно выиграет в вашей ситуации.

Теперь давайте подумаем о схеме потока, которая была бы оптимальной для вашей задачи. Предположим, у нас есть поток yielding и processing. Первый из них подготавливает куски данных к обработке, второй производит обработку и сохраняет результат обработки где-то (не очень важно).

Эффективный способ заставить эти нити работать вместе - это правильный механизм отдачи. Ваш поток yielding должен просто добавлять данные в некоторый общий буфер и не должен заботиться о том, что произойдет с этими данными. И, ну, ваш буфер может быть реализован в виде простой очереди FIFO. Это означает, что ваш поток yielding должен подготовить данные для обработки и сделать PUSH вызов вашей очереди:

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

Теперь поток processing. Это поведение должно быть описано следующим образом (вам, вероятно, следует добавить некоторую искусственную задержку, например SLEEP(X) между вызовами EMPTY)

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

Важным моментом здесь является то, что ваш поток обработки должен делать с обработанными данными. Очевидный подход означает выполнение POP вызова после обработки данных, но вы, вероятно, захотите придумать более удачную идею. Во всяком случае, в моем варианте это будет выглядеть как

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

Обратите внимание, что операции блокировки в потоках yielding и processing на самом деле не должны влиять на вашу производительность, поскольку они вызываются только один раз для каждого фрагмента данных.


Теперь интересная часть. Как я писал в начале, этот подход будет эффективен только в том случае, если потоки будут работать примерно одинаково с точки зрения использования процессора / ресурсов. Существует способ сделать это решение для многопоточности эффективным, даже если это условие не всегда верно и имеет значение для некоторых других условий времени выполнения.

Этот способ означает создание другого потока, который называется controller thread. Этот поток просто сравнил бы время, которое каждый поток использует для обработки одного куска данных, и соответствующим образом сбалансировал приоритеты потока. На самом деле нам не нужно «сравнивать время» , поток controller может просто работать так:

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

Конечно, вы могли бы реализовать некоторые лучшие эвристики здесь, но подход с потоком controller должен быть ясным.

...