Многопоточность: ещё нюансы (28.05.2026). Печать
2026 - Май
28.05.2026 11:11
Save & Share
Обобщая все материалы по многопоточности: первый, второй, третий, четвёртый, пятый. Результат: переписана однопоточная программа расчёта простых чисел на Visual Basic v.6.0, став многопоточной на Borland C++ Builder v.6.0 (размещена вместе с исходниками). Последний работает с потоками хорошо (прирост скорости составил 3 с лишним раза вместо теоретических 11 - тоже неплохо).

Но остался один баг: родительский поток тормозит дочерние, когда в нём while (true) крутится, - не удавалось выйти на загрузку процессора >50%. Нужно было его добить. В итоге, баг добил меня.


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

Взяв полученные многопоточные исходники, были повторно подтверждены результаты:
- без использования Application->ProcessMessages() в родительском потоке - дочерние потоки не хотят запускаться, отрабатывая только 1 раз;
- родительский поток, по сути, является паразитом-подготовителем порций данных для дочерних потоков - и тупо жрёт 23.3% от ЦП из 50% при кручении while (true);
- при слабых логических процессорах (реальные потоки ЦП с низкой частотой) и их большом количестве (23 в частности) - ЦП проваливается в значения типа 8%.

Понимание причин:
- были написаны дополнительные проверки времён выполнения определённых участков кода (замедляют работу). Код создания и запуска родительских потоков (а также подготовки новой порции информации для них) - оказался самым тормознутым местом, сжираемым минимум 100мкс на порцию данных;
- был написан механизм визуального слежения за потоками (сильно замедляют работу из-за необходимости Refresh для элементов интерфейса). Стало видно, что при работе с числами больше квадриллиона: большинство потоков закончило работу и простаивают (признав число непростым), а 1-3 потока проверяют числа, не выбывшие после деления на 3-значные числа (спасибо, кэп, - логично). При этом, 3 потока могут работать долго (думаешь, что 3 числа простых) - а потом 2, внезапно, отваливаются на секунду позже третьего - из трёх чисел простым признано только одно;
- при работе же с числами меньше квадриллиона и с 23 дочерними потоками - стало понятно: родительский поток тупо не успевает раздавать порцию данных дочерним потокам - они отрабатывают задолго до того, как родитель сообщит часть порции данных 23-му потоку.

Единственный способ избежать таких задержек - отказаться от порций данных и тупо скармливать следующее число сразу же освободившемуся дочернему потоку. Тогда простые числа не будут отсортированы. Но попытки отсортировать 17млн чисел в std::sort или самописными функциями не увенчались успехом: огромное время выполнения. Потом вспомнился опыт 10-летней давности - сортировка Шелла всё равно тратит 10мин на 16млн чисел. И даже если эту задачу решить многопоточно (раскидать числа по N массивам по разрядам, критериям <10млн, <1млн, <100к, ..., - отсортировать - собрать в 1 большой массив обратно по очереди) - несколько минут на сортировку точно уйдёт. Будет прирост незначительным, раза в 2 (первому потоку достанутся 7млн 8-разрядных чисел, последнему достанется 9 1-разрядных - и возникнет та же ситуация: все потоки отработали, а первый будет один отдуваться). Значит, критерием принадлежности массиву для сортировки является не разрядность числа, а порция 17млн/кол-во потоков. Или значение старших битов, используя количество потоков, кратное 8. Но сколько такое раскидывание займёт времени - неизвестно.

Исходники v.1.2 будут выложены вечером. Но они содержат только новые диагностические механизмы. Многопоточная сортировка Шелла, изменение алгоритма работы с потоками - глобальная переделка всей программы, и тут дело не только в лени и потраченном времени. Неоптимальная многопоточность для задачи простых чисел - индикатор разности скорости работы разных языков программирования. Если текущие исходники поместить в Qt v.5.3 под Windows - могут получиться интересные результаты. Но это - уже сами делайте: потому что мне тоже лень.

А посыл получился интересным. Программист, разбирающийся и успешно реализующий многопоточность-параллелизм на практике, - не должен получать зарплату обычного программиста (нет возможности выполнить работу частично: либо всё работает идеально, либо все труды идут по влагалищу - и если бы кто-то использовал мою программу для квадриллионных чисел - он бы мне *** на лбу нарисовал). А рынок труда - показывает обратное. Параллелизм - это не просто глобальное изменение подхода разработки исходников относительно однопоточных (с жёстким контролем и огромной вероятностью скрытых ошибок), но и необходимость дополнительных знаний о скорости работы кучи функций, да ещё и в разных условиях (иначе идеального результата не добиться).
Обновлено ( 28.05.2026 19:16 )