#c #multithreading #design-patterns
#c #многопоточность #шаблоны проектирования
Вопрос:
У меня есть библиотека с IJobMaker
сущностью, которая создает определенное количество IJob
объектов для запуска в собственных потоках, управляемых пользователем. Для отслеживания прогресса каждого IJob
я внедряю шаблон observer с IProgressObserver
в каждом задании. Трудность возникает, когда я хочу сообщить об ОБЩЕМ прогрессе.
Идеальным для меня было бы иметь IProgressOverserver.ReportProgress(float jobProgress, float overallProgress
, который сообщает как о работе, так и об общем прогрессе. IJobMaker
может быть осведомлен о части каждого задания в общей работе и каким-то образом собирать отчеты каждого.
Возникают два основных вопроса:
-
Механизм синхронизации? Держать мьютекс внутри
IJobMaker
, например, может повредить производительность, посколькуIProgressOverserver.ReportProgress
вызывают большое и мьютекс может повлечь за собой переключение контекста, а что нет. InterlockedIncrement выглядит как хороший вариант, но поскольку такой функции для с плавающей запятой нет, я был бы вынужден сообщать о прогрессе с помощью целых приращений. (Я бы хотел держаться подальше от функций c 0x или Boost) -
Шаблон проектирования?
IJob
о прогрессе сообщается из его самых глубоких алгоритмов. Мне нужен каждый такой отчет как для связи с центральным объектом для вычисления общего прогресса, так и для вызоваIProgressObserver.ReportProgress
метода, который находится вIJob
.
Ответ №1:
Пара предложений по потоковой обработке:
- Не сообщайте о каждом малейшем прогрессе. Сообщать основному потоку только после достижения определенного заранее определенного уровня прогресса, или по истечении определенного заранее определенного количества времени, или по завершении подзадачи. Это может значительно сократить объем синхронизации.
- Если вы реализуете # 1, мьютекс может работать довольно хорошо.
- Если мьютекс окажется слишком дорогим, вы можете сообщить о прогрессе, используя атомарную целочисленную переменную: просто масштабируйте значения от «нет прогресса» до «все сделано» до
0
…INT_MAX
.
Что касается проектирования API, не должно быть слишком сложно придумать что-то разумное. Моим общим советом было бы не перепроектировать его.
Комментарии:
1. Я сообщаю как можно «грубо», и все же я считаю, что использование блокировки может плохо повлиять на производительность. Независимо от того, насколько далеки мои отчеты, я всегда могу масштабировать вещи достаточно, чтобы создать узкое место в блокировке. (имея большое количество потоков и
IJob
ов) Эта библиотека должна иметь возможность масштабирования до 10 ядер.
Ответ №2:
Прежде всего, довольно плохая практика использовать значения с плавающей точкой в таких случаях. Используйте целое число.
Есть еще одно предложение. Вы можете использовать сегментацию — синхронизировать только несколько потоков с помощью одного мьютекса / атома (один сегмент). А затем соберите общее количество по всем сегментам.
Кроме того, есть хорошее место, чтобы начать изучать высокопараллельные алгоритмы:http://www.1024cores.net/home/lock-free-algorithms
UDPATE Есть пример проблем с float
#include <iostream>
using namespace std;
int main() {
float f = 0;
for(int i=0; i<100000-98; i)
{
f = 0.00001;
}
cout << f << endl;
}
Итак, если у вас есть 100 заданий с 1000 шагами в каждом, вы получите результат 1.0 в 98 раньше, чем вы могли ожидать.
Комментарии:
1. можете ли вы пояснить, почему плохо использовать значения с плавающей точкой? кроме того, я не уверен, что понимаю, что вы подразумеваете под сегментацией. Как я могу синхронизировать только несколько потоков?
2. Значения с плавающей точкой предназначены для широкого диапазона значений. Я не думаю, что у вас есть диапазон прогресса между
1E-200
—1E200
. В вашей ситуации вы могли бы иметьAtomicInteger
и выполнять точные вычисления, но я никогда не слышал оAtomicFloat
. Да, вы могли бы сгруппировать несколько потоков по нескольким сегментам. Это позволяет синхронизировать меньшее количество потоков в одном мьютексе.3. но тогда мне пришлось бы синхронизировать сегменты, чтобы получить окончательный ответ. Возможно, это сработало бы, если бы я допустил некоторую задержку с момента выполнения некоторых
IJob
задач до тех пор, пока пользователь не получит общий отчет о ходе выполнения. Тем не менее, поскольку я хочу получить немедленный ответ, мне пришлось бы синхронизировать внутри сегментов и между сегментами, что кажется столь же вредным. что касается значений с плавающей точкой, я могу использовать диапазон от 0 до 1. целые числа также имеют широкий диапазон, но используется только поддиапазон.4. Хорошо, тогда вы правы. В этом случае лучшим решением является атомарное целое число, я полагаю, потому что оно не выполняет системные вызовы как мьютексы. Я не понимаю вас по поводу диапазона. Просто используйте диапазон целых чисел, например, от 0 до 1 миллиарда, это даст лучшую точность, чем тип с плавающим числом того же размера. В противном случае вы можете столкнуться с проблемой
0.01 ..hundred times.. 0.01 != 1