Конференция "Основная" » Снова многопоточность
 
  • Denchik (17.08.17 16:54) [0]
    Доброго времени суток! Уважаемы знатоки, ищу изящное решение вопроса.

    Задача: максимально быстро просчитать md5 списка фалов. Идея, раскидывать список файлов на потоки, чтобы каждый обсчитывал свой кусок списка. Количество потоков = количеству ядер CPU в системе.

    Создание и старт и ожидание потоков:


    const
     MAXFILESIZE=50*1024*1024; // 50mb
    var
     ThrCnt: integer;
     FilesSzPerThread,
     CurThrFilesSz,
     TotalFilesSz,
     TotAllThrFilesSz,
     CurAllThrFilesSz: int64;

     ThrList: array of TThreadMultiMd5;
     AFileHashesItem: TFileHashesItem;
     i, j: integer;
     rWait: cardinal;
     ThrHandles: array[0..MAXIMUM_WAIT_OBJECTS - 1] of THandle;
     MalwProgressData: TMalwProgressData;
    begin
     ThrCnt:=CPUCount; // опеределяю сколько ядер

     TotalFilesSz:=FFileHashesList.GetTotalFilesSize(MAXFILESIZE);
     FilesSzPerThread:=Round(TotalFilesSz/ThrCnt); // Считаю какой размер файлов будет приходиться на каждый поток
     SetLength(ThrList, ThrCnt);

     for i := 0 to ThrCnt-1 do
     begin
       ThrList[i]:=TThreadMultiMd5.Create(True, FRHashFileProcPtr);
       ThrHandles[i]:=ThrList[i].Handle;

       // тут набиваю списки потоков именами файлов для которых нужно посчитать md5

       {$IFDEF LOGMODE}WrLog(Self.ClassName+' - CalcFilesMd5MultiThread, Thread #'+IntToStr(i)+' initialized, scheduled files size: '+GetHumanFileSize(CurThrFilesSz)+', total thread files: '+IntToStr(ThrList[i].FileHashesList.Count));{$ENDIF}

       ThrList[i].Start;
     end;

     {$IFDEF LOGMODE}WrLog(Self.ClassName+' - CalcFilesMd5MultiThread, Threads started');{$ENDIF}

     // жду когда потоки завершат свою работу, по пути обновляю юзеру прогресс бар
     repeat
       rWait:=WaitForMultipleObjects(ThrCnt, @ThrHandles, True, 300); // 300ms, scan progress refreshing interval

       // тут собственно обновление самого прогресса

     until (rWait<>WAIT_TIMEOUT) and (rWait <> (WAIT_OBJECT_0 + ThrCnt));




    Поток который занимается непосредственно подсчётом md5:


    .....

    procedure TThreadMultiMd5.Execute;
    begin
     {$IFDEF LOGMODE}WrLog(Self.ClassName+' - Starting thread with handle: '+IntToStr(Handle));{$ENDIF}
     // обсчитываю md5
     ...



    Запускаю всю эту лавочку и вижу в логах, что на момент когда дёргается функция WaitForMultipleObjects() ещё не все потоки успели запустится, а которые успели запустится уже и успели отработать (случай когда мало файлов и много ядер CPU). WaitForMultipleObjects выскакивает не дождавшись тех потоков которые не успели запуститься. Попробовал костыль в виде Sleep(2000); перед циклом ожидания завершения потоков. Всё в порядке. Но это костыль + в задаче критична каждая секунда ожидания.

    Собственно вопрос, каким образом грамотно понять что все потоки запустились и можно переходить к циклу с WaitForMultipleObjects ?

    С многопоточностью дел имел мало и первое что приходит в голову, в каждом потоке организовать какой нибудь доп. мьютекс, который будет заниматься в OnCreate каждого нового потока и освобождаться в OnExecute. Далее, в том месте где я запускаю потоки, ждать пока все эти мьютексы всех потоков не освободятся и уже потом запускать цикл с WaitForMultipleObjects.

    Но сдаётся мне, что моя идея с доп. мьютексами попахивает каким то мазохизмом и есть более простой способ решения проблемы)
  • rrrrrrr © (17.08.17 16:58) [1]
    зачем тебе вообще WaitForMultipleObjects?
  • rrrrrrr © (17.08.17 17:00) [2]
    и тем более мьютексы зачем тебе
  • rrrrrrr © (17.08.17 17:05) [3]
    создаем поток.
    в конструктор передаем имя файла и хендл формы откуда все это происходит.
    наращиваем счетчик запущенных.

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

    в форме запуска по пришедшему сообщению уменьшаем счетчик запущенных, вынимаем полученный хеш и имя файла.

    и все.
    никого ни ждать и пасти не надо.
  • Denchik (17.08.17 17:08) [4]
    > зачем тебе вообще WaitForMultipleObjects?

    Хочу дождаться пока потоки завершат работу, забрать из них результат работы и грохнуть их. А какие ещё варианты есть кроме OnTerminate и ручного определения что все потоки завершили работу? Вроде для этого WaitForMultipleObjects и предназначен?
  • Denchik (17.08.17 17:10) [5]
    > в конструктор передаем имя файла и хендл формы откуда все это происходит.

    Каждый поток обрабатывает не один и список файлов. Запускаю я эти потоки не из события формы, а с другого уже созданного мною потока. Кроме просчёта md5 там ещё пачка операций выполняется. Не стал загромождать топик лишним кодом.
  • Denchik (17.08.17 17:13) [6]
    Если совсем коротко, есть основной поток (потомок TThread, тоже созданный мной), который должен запустить ещё несколько потоков, дождаться их выполнения, забрать из этих потоков данные и продолжить выполнение других задач.
  • rrrrrrr © (17.08.17 17:17) [7]
    Каждый поток обрабатывает не один и список файлов.

    Ну и смысл. делай чтобы основной обрабатывал не один а несколько файлов.
    Те же будут только в профиль.
  • rrrrrrr © (17.08.17 17:18) [8]
    дождаться их выполнения,

    Зачем?

    забрать из этих потоков данные
    Зачем. Эти потоки могут сами отдать данные.
  • rrrrrrr © (17.08.17 17:21) [9]
    вот файл в сто байт.
    а рядом в терабайт.

    хеш первого будет готов не успеешь кнопку отпустить
    а хеш второго чуть опосля.

    смысл ждать первому второго если первый уже все сделал?
  • rrrrrrr © (17.08.17 17:34) [10]
    Идея, раскидывать список файлов на потоки, чтобы каждый обсчитывал свой кусок списка. Количество потоков = количеству ядер CPU в системе.

    идея конечно супер.
    жалко только, что в ней нет ничего про пропускную способность дисковой подсистемы.
  • Denchik (17.08.17 18:04) [11]
    > Ну и смысл. делай чтобы основной обрабатывал не один а несколько файлов.

    С этого я начинал. Скорость оставляет желать лучшего.

    > Те же будут только в профиль.

    Это не так, проверено. Delphi не настолько умён чтобы уметь параллелить свои задачи без ведома программера. Один мой поток на моём 4х ядерном CPU занимает максимум 25% ресурсов, т.е. максимум одно ядро. 4мя же потоками можно забить его на все 100%. При этом приоритет потока (хоть tpTimeCritical ему поставь) никак не влияет на распределние нагрузки между ядрами. Опять же через свои же шишки пришёл к тому, что приоритет имеет вес если например запустить 2 потока на одноядерном CPU. Тогда поток с большим приоритетом получит больше процессорного времени.

    > Эти потоки могут сами отдать данные.

    А каким образом не пойму? То что выдумал я, сделал класс, наследник TList, который заполняю списком фалов перед Thread.Start. Владелец этого класса сам поток. После завершения вытягиваю заветные просчитанные md5 из свойства потока. В процессе работы поток работает со своими изолированными данными которые находятся внутри самого потока. Выбрал этот путь опять же чтобы облегчить себе жизнь отсутствием всяких синхронизаций, критических секций и прочих трудностей которые возникают при совместном доступе разных потоков к общим данным.

    > смысл ждать первому второго если первый уже все сделал?

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

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

    Об этом тоже подумал. Тестил я на максимальном количестве ядер 8, обычном простеньком SATA (тупой бэкапный винт) и проблем с диском не было. Всё упирается именно в ресурсы CPU. Т.е. даже 8 ядер i7 не успевают с такой скоростью читать данные с диска и обсчитывать их, чтобы диск захлебнулся.
  • Denchik (17.08.17 18:06) [12]
    Незаметно ушли от основного вопроса..
  • rrrrrrr © (17.08.17 18:07) [13]
    вот допустим в списке 100 файлов.
    запустить сто IO операций чтения (и пусть дерутся между собой за шину sata) - идея бестолковая.

    значит одновременно должно работать скажем 10 - 20 потоков.
    идем дальше.

    если потоки одноразовые, и есть WaitForMultipleObjects то мы запустим вторую порцию только после самого последнего (так как уже начавшемуся WaitForMultipleObjects не подсунуть новые хендлы)

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

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

    Всё упирается именно в ресурсы CPU.

    Все упирается как раз в диск.
    Так как мд5 считается быстрее, чем файл читается с диска.
  • rrrrrrr © (17.08.17 18:10) [14]
    Задача поставлена так, что пока не будут обсчитаны все файлы, я не могу двигаться дальше.

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

    но при этом ты не будешь висеть в зачем-то вызванном WaitForMultipleObjects, а будешь волен делать все что хочешь.
  • rrrrrrr © (17.08.17 18:22) [15]
    > Эти потоки могут сами отдать данные.

    А каким образом не пойму?


    Элементарным

    SendMessage (or PostMessage) на хендл окна из которого все создавалось.
    /* первый синхронный второй асинхронный */

    для передачи результата : WParam + LParam
    которые могут быть указателями на какие угодно структуры с расчитанными потоком данные.
    Или они могут быть простым пэчаром на строку хеша приведенным к L/W Param
    Да чем угодно они могут быть
  • Denchik (17.08.17 19:00) [16]
    > значит одновременно должно работать скажем 10 - 20 потоков.

    Я не говорил что их будет 10-20. Такое количество без всяких сомнений упрётся в возможности диска.

    > расчет хеша файла - задача самостоятельная, от других потоков не зависящая. в синронизации не нуждающаяся.

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

    > зачем нам мьютексы, даже если мы знаем такое слово?

    Давай пож не будем выяснять кто о чём слышал. С мьютексами работал много в RTOS и очень ясно себе представляю как с этим работать. Счётчик - костыль, мьютекс (а точнее семафор, если речь идёт именно о параллельных вычислениях) - это есть тот самый счётчик только потокоориентированный. Но никто не запрещает использовать тупо переменную счётчик, также как и шуруп забивать молотком никто не запрещает.

    > Так как мд5 считается быстрее, чем файл читается с диска.

    Это проверял собственноручно. i7-4790k, простой SATA диск, запуск в 8 потоков, тестовая куча файлов просчиталась за 9 секунд. Тоже самое в один поток - 68 секунд. Далее, ноут i5-2410m, родной SATA, один поток - 84 сек, 2 потока 47 сек. Далее были ещё более слабые Core2Duo, Celeron. Результатов нет под рукой, но ситуация не сильно отличалась.

    > SendMessage (or PostMessage) на хендл окна из которого все создавалось.

    Вопрос зачем это всё? Я могу просто забрать из потока обычным доступом к его свойству.
  • rrrrrr © (17.08.17 19:32) [17]
    В другом случае есть вероятность, что один и тот же поток полезет к одному элемента массива.</>

    вероятность есть, но равняется ровно нулю.
    если делать как тебе говорят.

    Тоже самое в один поток - 68 секунд.
    попу с пальцем не путай.
    я говорю, что файл будет считываться с диска дольше, чем будет считаться его мд5.
    написано было по-русски.

    Вопрос зачем это всё? Я могу просто забрать из потока обычным доступом к его свойству.

    забрать-то ты можешь.
    только ты не знаешь когда это уже можно делать.

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

    удачи
  • rrrrrr © (17.08.17 19:36) [18]
    Счётчик - костыль, мьютекс (а точнее семафор, если речь идёт именно о параллельных вычислениях) - это есть тот самый счётчик только потокоориентированный.

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

    к тому же зачем синхронизировать то, что в синхронизации не нуждается.
    а расчет хэша одного файла как раз и не нуждается в синхронизации с расчетами других файлов.
  • rrrrrr © (17.08.17 19:48) [19]
    Вопрос зачем это всё? Я могу просто забрать из потока обычным доступом к его свойству.

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

    а то, что более ресурсоемко - оно еще и по затратам процессорного времени не даром тебе дается.

    ищу изящное решение вопроса.

    ищи ищи.
  • Denchik (17.08.17 19:49) [20]
    > rrrrrr

    бесполезный разговор, удачи
  • rrrrrr © (17.08.17 19:57) [21]
    // Считаю какой размер файлов будет приходиться на каждый поток

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

    при этом пофик сколько десятков терабайт в этом файле.
    он никогда целиком в память не грузится.
  • Denchik (17.08.17 20:28) [22]
    Ещё раз хочу пожелать вам удачи с вашими выводами, и прошу остановить не связное обсуждение вырванных фраз из постановки задачи.
  • rrrrrr © (17.08.17 20:35) [23]
    в общем на правах борьбы со скукой и в рамках ретроспективы этого форума.

    за 18 лет наблюдений
    каждый (каждый!) новичок (а ты он и есть) задающий здесь вопросы про многопоточность выглядит точно так же как и предыдущий такой же новичок, ушедший обиженным.
    вы словно однояйцевые близнецы из одного инкубатора.

    и так как эти наблюдения длятся не менее 18 лет, то причина этого феномена однажды была найдена.

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

    формально и абстрактно он конечно знает для чего все это надо
    но зачем это ему надо в его конкретном случае он не знает.
    "раз у меня потоки, я должен пользоваться этими словами"

    отсюда и неприятие советов со стороны тех, кто эти потоки применяет с умом, не создавая проблем на ровном месте, (изящно и элегантно как ты и хочешь)
  • rrrrrr © (17.08.17 20:48) [24]
    вот тебе элегантная схема

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

    в модуле формы пишем обработчик для WM_USER + XXX
    в нем принимаем результаты, уменьшаем счетчик.
    если есть еще файлы, генерим еще один поток взамен завершившегося
    если счетчик обнулился, и файлов больше нет, начинаем использовать результаты расчета (двигаемся дальше)

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

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

    все!
    никаких дурацких прикидок на расходы по памяти.
    никаких проблем.
  • Denchik (17.08.17 22:26) [25]
    > rrrrrr

    Тошнит от таких думающих, что умнее всех, пытающихся самоутвердиться. Просто расскажу всё что хоть немного знаю. На вопрос ответа нет, ничего страшного, щас чё нибудь не относящегося к вопросу навыдумываю. Ещё раз повторяю, читай вопрос, и его пояснения, а не придумывай сам на что отвечать.

    Если кратко, чтобы ты понял, что ты видно перепутал мою задачу с соседней веткой:
    1. У меня нет формы в постановке моей задачи
    2. У меня уже есть работающий алгоритм, у которого есть (точнее уже было) одна непонятная деталь
    3. Мне нужно просто грамотно грамотно запустить и дождаться завершения потоков.

    Ну раз уж ты решил советов наляпать мне, давай я разберу твой чудо алгоритм? А ты не просил?? Ты совсем про другое спрашивал?? Но ничего страшного, я всё равно поумничаю:

    1. > шлепаем по списку, генерим потоки

    Почему бы и нет, генерить пачками новые потоки, это же совсем бесплатная операция в плане ресурсов

    2. > в модуле формы пишем обработчик для WM_USER + XXX

    Формы нет в принципе, есть поток, который порождает другие потоки, а посему твой SendMessage не подходит. Если опять есть возражения, кури первую строку:
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx

    Для взаимодействия потоков, есть свои механизмы, их не дураки придумали. Если не веришь, опять курить тут:
    http://docwiki.embarcadero.com/RADStudio/Tokyo/en/How_To_Build_Multithreaded_Applications

    Покажи хоть одно упоминание о твоём любимом SendMessage.

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

    Память не будем чистить, её много, чёрт с ней. А даже если захотим, то никак не можем узнать получило ли окно наше сообщение, если используем PostMessage. Ок, не проблема, используем SendMessage, просто подождём пока окно не обработает наше сообщение. И ничего что мы тут теряем время и становимся зависимыми от потока в котором крутится оконная процедура.

    6. > поток универсален и самодостаточен.

    Если не считать, что он не может жить без главной формы, которая должна принимать твои сообщения.

    Это если очень поверхностно.. Дальше жаль времени, разбирать поток твоих противоречий.

    Мьютексы, семафоры, критические секции, выдумали ничего не знающие люди по твоему? Естественно, есть же глобальные переменные с счётчиками, всякие SendMessage, а это всё бред никому не нужный.
    Если ты усомнился в моих знаниях по этому поводу, я твоёго мнения не спрашивал.

    Судя по твоим легкомысленным высказываниям по счётчикам, использования SendMessage без синхронизации, новичок здесь ты, а не я. Обид у меня нет, просто не хочу именно с тобой это обсуждать.
  • rrrrrr © (17.08.17 22:38) [26]
    Для взаимодействия потоков, есть свои механизмы, их не дураки придумали. Если не веришь, опять курить тут:

    этим потокам (конкретно этим) нафик не сдалось взаимодействовать друг с другом.

    Память не будем чистить, её много, чёрт с ней.
    память чистить не будем пока работает расчет.
    и надо этой памяти 4 килобайта. или два. или мегабайт. сколько тебе нравится.
    для расчета хэша всего твоего винта (всех твоих винтов) нужно именно столько памяти не больше. со смертью потока она освободится. все твои драгоценные четыре килобайта. или два. или байт если ты вдруг решишь что такой буфер тебе нужен.

    Если не считать, что он не может жить без главной формы, которая должна принимать твои сообщения.


    форма не обязательна. сообщения можно слать не только окнам, но и потокам.
    а как минимум один у тебя есть.
    если это консоль, то там все равно есть AllocateHwnd которая сделает окно.

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

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

    тяжелая судьба ждуна. сочуствую.
  • rrrrrr © (17.08.17 22:41) [27]
    Судя по твоим легкомысленным высказываниям по счётчикам, использования SendMessage без синхронизации,

    Еще раз специально для больших специалистов по ртос и мутексам-шмутексам.

    сендмессадж - он синхронный.
    обработка его происходит в одном и том же потоке.
    последовательно.
    никакой синхронизации-шаманизации при этом не требуется от слова совсем.
    сколько бы потоков-шматокоф у тебя там не зареспавнено было.
  • rrrrrr © (17.08.17 22:45) [28]
    Мьютексы, семафоры, критические секции, выдумали ничего не знающие люди по твоему?

    умные люди придумали мьютексы и остальные объекты синхронизации.
    действительно умные.

    но потом пришли ослы, не понимающие когда и где они реально нужны и обязаны применяться и стали лепить их там где их нафик сто лет не надо применять.

    а потом они же жалуются

    что на момент когда дёргается функция WaitForMultipleObjects() ещё не все потоки успели запустится, а которые успели запустится уже и успели отработать (случай когда мало файлов и много ядер CPU). WaitForMultipleObjects выскакивает не дождавшись тех потоков которые не успели запуститься. Попробовал костыль в виде Sleep(2000); перед циклом ожидания завершения потоков. Всё в порядке. Но это костыль + в задаче критична каждая секунда ожидания.

  • rrrrrr © (17.08.17 22:46) [29]
    так что судьба твоя - лепить костыли из слипов.
    и ждать пока поумнеешь.
  • rrrrrr © (17.08.17 23:04) [30]
    Обид у меня нет, просто не хочу именно с тобой это обсуждать.

    ага. ты просто немного потерпи.
    возможно где-то рядом километровая очередь выстроилась чтобы в стопитсотый раз рассказать стопитсотому ламеру решившему в потоки научиться.
  • Denchik (17.08.17 23:52) [31]
    Иди с миром добрый человек
  • Юрий Зотов © (17.08.17 23:53) [32]
    Особо не вникал, но посмотрите PostThreadMessage.

    1. В главном потоке есть счетчик рабочих потоков.

    2. Главный поток запускает рабочий поток (передавая ему свой хэндл) и увеличивает счетчик.

    3. Главный поток ждет сообщения (WaitMessage).

    4. Завершившись, рабочий поток передает главному потоку PostThreadMessage и данные (в параметрах).

    5. Главный поток принимает даные и накапливает их.

    6. Главный поток декрементирует счетчик. Если счетчик обнулился, то все данные собраны, а если нет - то снова WaitMessage.

    Приблизительно такая схема. Наверняка всплывут какие-то детали, но в целом где-то так.
  • rrrrrr © (17.08.17 23:56) [33]
    не не не. какие такие постсредмессадж?
    или вы не в курсе как работает почта россии?

    уи нид мор костылей.
  • rrrrrr © (17.08.17 23:58) [34]
    интересно только нафига эти жертвы с постсредмессадж, и жданием.
    если оно там один фиг прогресс-бар юзеру рисует, а стало быть тулза интерактивная.
  • Denchik (18.08.17 00:10) [35]
    > Юрий Зотов

    PostThreadMessage не попадался в поисках решения, спасибо, почитаю. Судя по вашему предложению, все таки надо наверно выделить времени и поковыряться в реализации TThread.
  • rrrrrr © (18.08.17 00:12) [36]
    ой, а как же сакральное жданьё?

    неужели здесь таки нафик не сдался вэйтформултиплайобджект?

    ой ой ой........
    и ни одного мутекса шмутекса.....
  • rrrrrr © (18.08.17 00:16) [37]
    ой, неужели юрий зотов тоже довольствуется галимым счетчиком который не защищен ни семафорами ни даже критическими секциями?!

    он что не в курсе, что умные люди (а они не дураки!) придумали столько умных объектов синхронизации?

    ой, неужели до стопитсотого ослика все же дошло, что лучше сразу слушать, а не упираться копытами в свои костыли?
  • Denchik (18.08.17 00:18) [38]
    > rrrrrr

    Да иди ты уже ради бога с миром выскочка.
  • rrrrrr © (18.08.17 00:22) [39]
    не надо благодарности.
    если чо, то я еще до того как ответить первый раз
    наперед уже знал каким будет обсуждение в этой ветке.

    бо было это уже стопитсот раз.
  • Юрий Зотов © (18.08.17 07:55) [40]
    > Denchik   (18.08.17 00:10) [35]

    > ... поковыряться в реализации TThread.


    При такой схеме главному потоку нужен цикл обработки сообщений, который придется писать самому (раз в главном потоке нет окон, то и нет готового цикла). Написать такой цикл несложно, но в какое место TThread его вставить?

    (Поручик, молчать!)

    То есть, если главный поток не является первичным потоком всей программы, а порожден от TThread, то возникает эта проблема. Поэтому главный поток (а заодно, пожалуй, и рабочие потоки) я написал бы без TThread, на чистом WinAPI. На "Королевстве" есть статься "Сплэш - показываем красиво", а в ней есть пример потока с циклом выборки сообщений.
  • Юрий Зотов © (18.08.17 07:58) [41]
    И еще - нужно ли ограничивать число рабочих потоков числом ядер? Пусть ими рулит система, она сама разберется.
  • Юрий Зотов © (18.08.17 08:09) [42]
  • Дмитрий Белькевич © (18.08.17 09:22) [43]

    > Задача: максимально быстро просчитать md5 списка фалов.


    Вопрос: можно ли сделать задачу в один поток просто циклом? Не важно, в основном потоке или в дополнительном. Если да - то есть замечательные либы распараллеливания почти любого кода. Я у себя достаточно часто использую. Если код оптимизированный под потоки (либо не использует общих ресурсов, либо защищает их) то распараллелить получается сразу несколькими строчками лекговесного кода. Ничего не надо создавать, разрушать, ожидать.
    Мне видится, что это вполне возможно в данной ситуации: создали список файлов, многопоточно их обработали, пошли дальше разбираться.
    Ускорение на чтении + парсинге файлов с помощью библиотеки может получится в 6-7 раз на 8ми ядрах, у меня как раз так.
  • rrrrrrr © (18.08.17 09:24) [44]
    бутылочное горлышко здесь - не перемалывание байтов в памяти при расчете хеша.
    а добывание этих байтов с диска.

    так что все волшебные распараллеливатели почти любого кода здесь сдуются
  • sniknik © (18.08.17 10:24) [45]
    > Вопрос: можно ли сделать задачу в один поток просто циклом?
    если да, то рекомендую так и оставить в одном потоке... смысл(ускорение) потоков есть только там где есть ожидания в расчетах, если же, как тут, весь расчет выполняется практически в фоне от чтения данных... - нафиг потоки.

    4 ядра фигня, у меня 16 и все одно выгоды от распараллеливания подобного нет, вот если бы было 16 дисков по одному на каждый файл, и пусть даже всего 1 ядро, вот тут уже можно было поспорить/побороться, а так нет.
  • Юрий Зотов © (18.08.17 13:23) [46]
    Но загрузка CPU  на 25% откуда-то берется же. А это немало.
  • sniknik © (18.08.17 14:43) [47]
    то что показывает диспетчер задач вовсе не обязательно нагрузка... не полезные действия. в конце концов даже пустой цикл типа
     i:= 1;
     while i < 10000000000000 do
       i:= i + 1;
    грузит систему полностью, а вставь туда sleep
     i:= 1;
     while i < 10000000000000 do begin
       i:= i + 1;
       sleep(1);
     end;
    и нет нагрузки. как так? ;)

    кстати то, что есть WaitForMultipleObjects и при этом грузится все ядро на 100% это больше показатель кривого кода... т.к. ожидание выполнений потоков  оно типа как sleep, в смысле должно распределять нагрузку, а другие потоки/процессы должны как то "смягчать" вот это вот забивание ядра работой. (один делает на 100%, другой,третий...десятый на 0%, в среднем диспетчер должен показать меньше). ИМХО конечно.
  • Юрий Зотов © (18.08.17 15:31) [48]
    > sniknik ©   (18.08.17 14:43) [47]

    То, что sleep снижает показания - нормально. Это же СРЕДНЯЯ загрузка за интервал времени.

    Но я не об этом. Если бы тормоза были ТОЛЬКО из-за файловых операций, то CPU так не грузился бы. А он грузится - значит, тормоза есть еще из-за чего-то. И если это "что-то" вынести в потоки, нагрузив CPU на всю катушку, то выигрыш в итоговом времени должен быть. Конечно, из рабочих потоков надо убрать все Wait'ы.
  • Denchik (18.08.17 15:37) [49]
    > Юрий Зотов

    > То есть, если главный поток не является первичным потоком всей программы, а порожден от TThread, то возникает эта проблема. Поэтому главный поток (а заодно, пожалуй, и рабочие потоки) я написал бы без TThread, на чистом WinAPI

    Так и есть, главный поток не является первичным потоком программы. Сдаётся мне если лезть в потоки на чистом WinApi, то там вообще утопия будет по времени)..

    А в чём сложности сделать самому цикл обработки сообщений? Может какие-то подводные камни? Или может вообще есть под рукой какой-то простенький пример (лучше тысячи слов)?

    > И еще - нужно ли ограничивать число рабочих потоков числом ядер?

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

    Вот ещё вспомнил, народ где-то рекомендовал вообще привязывать потоки к ядрам через SetThreadAffinityMask, т.к. винда вроде не сильно грамотно умеет этим управлять. Сам не проверял.. Не знаю насколько оправдано это.
    Кста, писали вроде в винде если правой кнопкой тыкнуть на процессе в диспетчере задач, выбрать Задать сходство, поставить галки на против только одно ЦП, то как раз для процесса и применяется SetThreadAffinityMask.

    > Вопрос: можно ли сделать задачу в один поток просто циклом? Не важно, в основном потоке или в дополнительном.

    Сама задача очень простая, просто считать md5 для фалйов из подготовленного списка. Поэтому вполне можно вернуться к старому коду, который делал это всё в одном потоке, но с низкой производительностью. Вопрос только что дальше с этим делать)?

    > есть замечательные либы распараллеливания почти любого кода.

    А что за либы такие, подскажите пожалуйста? Может пример опять же под рукой какой-то есть?

    > sniknik

    Не совсем понял, имеется ввиду задача именно расчёта md5? Если это копирование файлов, тут никаких сомнений, что наращивать надо не количество ядер, а количество дисков, а вот с md5 ситация немного другая.

    > Но загрузка CPU  на 25% откуда-то берется же.

    25% в моём случае это дно ядро занимается этой задачей, поэтому 25% (всего 4 ядра). Я бы давно остановился, если бы наращивание ядер не дало бы прироста по времени обсчёта. Но оно стало, грубо говоря в 3 раза меньше, на 4х ядерном CPU. Есть подозрение в 3 раза меньше а не в 4 стало, из-за не эффективности кода.. На stackoverflow этот вопрос поднимался не раз, и при распараллеливании, народ получает прирост примерно пропорциональный количеству ядер.

    > кстати то, что есть WaitForMultipleObjects и при этом грузится все ядро на 100% это больше показатель кривого кода...

    Не исключаю этого.. Когда приступал к задаче, почитал доки форумы и пришёл к выводу WaitForMultipleObjects это то что нужно и это прямая замена своим выдумкам из счётчиков, по которым решаем, что все потоки завершили работу. Но судя по результату это не так) Может кто-то на пальцах расскажет, какое в принципе ему применение и для каких задач используется WaitForMultipleObjects?
  • Denchik (18.08.17 15:38) [50]
    > Если бы тормоза были ТОЛЬКО из-за файловых операций, то CPU так не грузился бы.

    Полностью с этим согласен. Даже результаты тестов где-то выше приводил
  • Юрий Зотов © (18.08.17 16:12) [51]
    > если лезть в потоки на чистом WinApi,
    >  то там вообще утопия будет по времени).


    Не зная, что имелось в виду под словом "утопия", но ничего страшного точно не будет. В конечном счете, дельфишный TThread все равно работает  через WinAPI.

    > А в чём сложности сделать самому цикл обработки сообщений?

    Уже говорилось. СДЕЛАТЬ цикл несложно, но в какое место TThread его ВСТАВЛЯТЬ?

    > есть под рукой какой-то простенький пример

    См. ссылку выше. Выкиньте из файла SplashDLL.dpr все, что относится к окну - получите готовый (и даже уже работающий) скелет.
  • Юрий Зотов © (18.08.17 16:27) [52]
    > для каких задач используется WaitForMultipleObjects?

    Для любых, где нужно дождаться, пока не просигналят все объекты ядра (или таймаута). Например, запускаем кучу программ (из своей) и надо дождаться их завершения.

    Для Вашей задачи это не подходит, потому что главный поток должен принять данные, а в спящем состоянии он этого сделать не может. Правда, есть вариант - создать буфер данных и пусть рабочие потоки пишут в этот буфер. Тогда WaitForMultipleObjects Вам подойдет.
  • rrrrrrr © (18.08.17 16:47) [53]
    он подойдет если у нас четыре файла, и четыре потока.

    или если файлов больше чем потоков,
    но нам пофик сколько времени потратится на все файлы.
  • Styx © (18.08.17 17:04) [54]
    Я бы посоветовал повнимательней отнестись к реализации самого хеширования. Нормальная производительность md5 - порядка сотен мегабайт в секунду, так что должно бы упираться в hdd, а не в cpu. У Вас какая производительность получается - в MiB/s/core?
  • Denchik (18.08.17 20:29) [55]
    Подразобрался с примером. Начал переделывать. Упёрся в проблему.

    У меня есть экземпляр TThread, назовём его Т1. Т1 в свою очередь может запускаться из: формы VCL, консольного приложения.

    Есть класс, наследник TObject, назовём его О1, который и предназначен для обсчёта md5 и он же создаёт потоки для параллельного просчёта.

    Экземпляр O1, создаётся либо в T1.Execute, либо его создаёт моя же виндовая служба.

    Главное условие, чтобы класс O1 был полностью самостоятельным и не зависел от класса из которого он создаётся.

    В идеале порождённые параллельные рабочие потоки, должны слать сообщения о том, что закончили обсчёт очередной партии фалов, в экземпляр O1. А вот как это организовать нет понимания. Ещё раз повторюсь, лезть в код Т1 нельзя, т.к. класс используется не только в одном юните в котором описан Т1. А плодить одинаковый код в нескольких юнитах вообще не айс..

    Вопрос, как правильно организовать приём сообщений от потока в классе О1? Создавать ещё один поток, целью которого будет исключительно приём сообщений от потоков, которые непосредственно считают md5?

    > У Вас какая производительность получается - в MiB/s/core?

    Цифры по размерам файлов не записал.. Если времени хватит переделать, то что начал, обязательно протестирую ещё раз. О результатах отпишусь.
  • Leonid Troyanovsky © (18.08.17 21:20) [56]

    > Denchik   (18.08.17 20:29) [55]

    > Вопрос, как правильно организовать приём сообщений от потока
    > в классе О1? Создавать ещё один поток, целью которого будет
    > исключительно приём сообщений от потоков,

    Пуркуа не па?

    Оный поток-монитор может  почти все время спать, ожидая приема
    QueueUserAPC. Ну, и  проверять условие завершения задачи.

    Вот пример постмортальной обработки данных потоков:

    unit thexproc;

    interface

    uses
     windows, classes;

    type
     TThreadExitProc = procedure (AData: DWord);

     TWorkThread = class(TThread) {поток монитора}
     private
       FStopEvent: THandle;
       FMemList: TList;
       procedure CheckList;
     public
       procedure Execute; override;
     end;

     TThreadExitProcs = class {объект регистрации процедур завершения}
     private
       FWorkThread: TWorkThread;
     public
       procedure RegThreadExitProc(AData: DWord; AProc: TThreadExitProc);
       constructor Create; virtual;
       destructor Destroy; override;
     end;

    var
      Timeout: DWord = 30000;

    implementation

    type
     TMem = packed record
       Handle: THandle;
       Data: DWord;
       Proc: TThreadExitProc;
       List: TList;
     end;
     PMem = ^TMem;

    procedure AddToList(param: PMem); stdcall;
    begin
     param.List.Add(param);
    end;

    procedure TWorkThread.CheckList;
    var
     i: Longint;
     exCode : DWord;
     pm: PMem;
    begin
     for i := FMemList.Count-1 downto 0 do
       begin
         pm := FMemList[i];
         GetExitCodeThread(pm.Handle, exCode);
         if exCode <> STILL_ACTIVE then
           begin
             CloseHandle(pm.Handle);
             ///////////////////
             pm.Proc(pm.Data);//
             ///////////////////
             Dispose(pm);
             FMemList.Delete(i);
           end;
      end;
    end;

    procedure TWorkThread.Execute;
    begin
     repeat
       ReturnValue := WaitForSingleObjectEx(FStopEvent, Timeout, True);
       CheckList;
     until (ReturnValue <> WAIT_TIMEOUT) and
           (ReturnValue <> WAIT_IO_COMPLETION);
    end;

    procedure TThreadExitProcs.RegThreadExitProc;
    var
     vmem : PMem;
    begin
     Assert(Assigned(AProc));
     New(vmem);
     vmem.Data := AData;
     vmem.Proc := AProc;
     vmem.List := FWorkThread.FMemList;
     DuplicateHandle( GetCurrentProcess,
                      GetCurrentThread,
                      GetCurrentProcess,
                      @vmem.Handle,
                      0,
                      False,
                      DUPLICATE_SAME_ACCESS);
     QueueUserAPC(@AddToList, FWorkThread.Handle, DWord(vmem));
    end;

    constructor TThreadExitProcs.Create;
    begin
     inherited;
     FWorkThread := TWorkThread.Create(True);
     FWorkThread.FStopEvent := CreateEvent(nil, False, False, nil);
     FWorkThread.FMemList := TList.Create;
     FWorkThread.Resume;
    end;

    destructor TThreadExitProcs.Destroy;
    var
     hse: THandle;
    begin
     hse := FWorkThread.FStopEvent;
     SetEvent(hse);
     FWorkThread.WaitFor;
     FWorkThread.FMemList.Free;
     FWorkThread.Free;
     CloseHandle(hse);
    end;

    end.


    --
    Regards, LVT.
  • Leonid Troyanovsky © (18.08.17 21:29) [57]
    Да, ну и пример

    type
     TForm1 = class(TForm)
       Button2: TButton;
       Button1: TButton;
       Button3: TButton;
       procedure Button2Click(Sender: TObject);
       procedure Button1Click(Sender: TObject);
       procedure Button3Click(Sender: TObject);
     private
       { Private declarations }
     public
       { Public declarations }
     end;

    var
     Form1: TForm1;

    implementation

    {$R *.dfm}

    uses
     thexproc;

    var
     teps: TThreadExitProcs;

    type
     TMyThread2 = class(TThread)
       procedure Execute; override;
     end;

    procedure AsyncDispose(AData: DWord);
    begin
     OutputDebugString(PChar(Format('%8.8x-', [AData])));
     Dispose(Pointer(AData));
    end;

    procedure TMyThread2.Execute;
    var
     pData: PDWord;
    begin
     New(pData);
     OutputDebugString(PChar(Format('%p+', [pData])));
     teps.RegThreadExitProc(DWord(pData), AsyncDispose);
     pData^ := Random(10000);
     Sleep (pData^);
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    begin
     teps := TThreadExitProcs.Create;
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
     TMyThread2.Create(False).FreeOnTerminate := True;
    end;

    procedure TForm1.Button3Click(Sender: TObject);
    begin
     teps.Free;
    end;

    --
    Regards, LVT.
  • Denchik (19.08.17 00:09) [58]
    > Leonid Troyanovsky

    Очень сложно понять с моим багажом знаний, но спасибо за пример!

    Сделал черновой вариант по совету Юрия Зотова. Прошу прокомментировать уважаемых гуру возможные проблемы кода:


    unit uThreadMultiMd5Ex;

    interface

    {$I Sources\defines.inc}

    uses Classes, SysUtils, Windows,  Dialogs, Messages, uCleanerTypes, uTypes;

    const
     THREAD_READY=WM_USER+20;
     MD5_READY=WM_USER+21;
     MD5_REQUIRED=WM_USER+22;

    type
     TToWorkerData=record
       AFullFileName: string;
     end;
     PToWorkerData=^TToWorkerData;

     TFromWorkerData=record
       AFullFileName,
       AFileMd5: string;
       ThreadId: TThreadId;
     end;
     PFromWorkerData=^TFromWorkerData;

     TThreadMultiMd5Ex = class(TThread)
     private
       FRHashFileProc: Trhash_file;
       FFileHashesList: TFileHashesList;

       procedure SetFileHashesList(AValue: TFileHashesList);
     protected
       procedure Execute; override;
     public
       constructor Create(CreateSuspended: Boolean; ARHashFileProc: Trhash_file);
       destructor Destroy; override;

       property FileHashesList: TFileHashesList read FFileHashesList write SetFileHashesList;
     end;

     TThreadMultiMd5Worker = class(TThread)
     private
       FParThreadId: TThreadId;
       FRHashFileProc: Trhash_file;
     protected
       procedure Execute; override;
     public
       constructor Create(CreateSuspended: Boolean; AParThreadId: TThreadId; ARHashFileProc: Trhash_file);
       destructor Destroy; override;
     end;

    implementation

    uses uCoreCommon, uCoreUtils;

    //------------------------------------------------------------------------------

    constructor TThreadMultiMd5Ex.Create(CreateSuspended: Boolean; ARHashFileProc: Trhash_file);
    begin
     inherited Create(CreateSuspended);
     FRHashFileProc:=ARHashFileProc;
     FFileHashesList:=TFileHashesList.Create;
    end;

    //------------------------------------------------------------------------------

    destructor TThreadMultiMd5Ex.Destroy;
    begin
     FreeAndNil(FFileHashesList);
    end;

    //------------------------------------------------------------------------------

    procedure TThreadMultiMd5Ex.SetFileHashesList(AValue: TFileHashesList);
    begin
     if Assigned(AValue) then
       FFileHashesList.Assign(AValue);
    end;

    //------------------------------------------------------------------------------

    procedure TThreadMultiMd5Ex.Execute;
    var
     Fn, Md5Str, m: string;
     LParamBuf, WParamBuf: TCopyDataStruct;
     ThrList: array of TThreadMultiMd5Worker;
     ThrCnt, i, j: integer;
     ToWorkerData: PToWorkerData;
     FromWorkerData: PFromWorkerData;
     Msg: TMsg;
     AFileHashesItem: TFileHashesItem;
    begin
     ThrCnt:=4;
     SetLength(ThrList, ThrCnt);

     for i := 0 to ThrCnt-1 do
     begin
       ThrList[i]:=TThreadMultiMd5Worker.Create(True, ThreadId, FRHashFileProc);
       ThrList[i].Start;
     end;

     j := 0;
     while j < FFileHashesList.Count do
     begin

       while not Terminated do
       begin
         if PeekMessage(Msg, 0, THREAD_READY, MD5_READY, PM_REMOVE) then
         begin
           FromWorkerData:=PFromWorkerData(Msg.lParam);
           if not Assigned(FromWorkerData) then
             WrLog('ALARMA!!! MD5_READY Msg.lParam=nil')
           else
           begin
             case Msg.message of
               MD5_READY:
                 begin
                   WrLog('MD5_READY Catched with file name: '+FromWorkerData^.AFullFileName+', md5: '+FromWorkerData^.AFileMd5+' from thread id: '+IntToStr(FromWorkerData^.ThreadId));
                   AFileHashesItem:=FFileHashesList.ItemByFullFileName(FromWorkerData^.AFullFileNam e);
                   if AFileHashesItem<>nil then
                     AFileHashesItem.FileMd5:=FromWorkerData^.AFileMd5;
                 end;
               THREAD_READY:
                 begin
                   WrLog('THREAD_READY Catched');
                 end;
             end;

             New(ToWorkerData);
             ToWorkerData^.AFullFileName:=FFileHashesList[j].FilePath+FFileHashesList[j].FileName;
             PostThreadMessage(FromWorkerData^.ThreadId, MD5_REQUIRED, 0, LParam(ToWorkerData));
             Dispose(FromWorkerData);

             Inc(j);
             Break;
           end;
         end;
       end;
     end;

    end;

    //------------------------------------------------------------------------------

    constructor TThreadMultiMd5Worker.Create(CreateSuspended: Boolean; AParThreadId: TThreadId; ARHashFileProc: Trhash_file);
    begin
     inherited Create(CreateSuspended);
     FRHashFileProc:=ARHashFileProc;
     FParThreadId:=AParThreadId;
    end;

    //------------------------------------------------------------------------------

    destructor TThreadMultiMd5Worker.Destroy;
    begin
     //
    end;

    //------------------------------------------------------------------------------

    procedure TThreadMultiMd5Worker.Execute;
    var
     Msg: TMsg;
     FromWorkerData: PFromWorkerData;
     ToWorkerData: PToWorkerData;
     Fn: string;
    begin
     New(FromWorkerData);
     FromWorkerData^.ThreadId:=ThreadId;
     FromWorkerData^.AFullFileName:='';
     FromWorkerData^.AFileMd5:='';

     PostThreadMessage(FParThreadID, THREAD_READY, 0, LParam(FromWorkerData));

     while not Terminated do
     begin
       if PeekMessage(Msg, 0, MD5_REQUIRED, MD5_REQUIRED, PM_REMOVE) then
       begin
         ToWorkerData:=PToWorkerData(Msg.LParam);
         if not Assigned(ToWorkerData) then
           WrLog('ALARMA!!! MD5_REQUIRED Msg.lParam=nil')
         else
         begin
           WrLog('MD5_REQUIRED Catched with file name: '+ToWorkerData^.AFullFileName);
           Fn:=ToWorkerData^.AFullFileName;
           Dispose(ToWorkerData);

           New(FromWorkerData);
           FromWorkerData^.ThreadId:=ThreadId;
           FromWorkerData^.AFullFileName:=Fn;
           FromWorkerData^.AFileMd5:=GetFileHashEx(FRHashFileProc, Fn);

           PostThreadMessage(FParThreadID, MD5_READY, 0, LParam(FromWorkerData));
         end;
       end;
     end;

    end;

    //------------------------------------------------------------------------------

    end.


    Алгоритм получился такой:
    1. Запускаем главный поток TThreadMultiMd5Ex, предварительно определив для него OnTerminate
    2. TThreadMultiMd5Ex.Execute запускает 4 рабочих потока
    3. Ждёт пока любой из рабочих потоков пришлёт THREAD_READY и свой ThreadId
    4. Как только THREAD_READY или MD5_READY получен, TThreadMultiMd5Ex выбирает из FFilesListHashes очередной элемент, шлёт рабочему потоку через PostThreadMessage путь к очередному файлу для которого считаем md5. Если был получен MD5_READY, а не просто THREAD_READY, тогда сохраняет результат просчёта
    5. Так до тех пор пока не кончится список FFilesListHashes

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

    Код сыровать, ещё не сделал проверки успешности PostThreadMessage, так же проверки если по какой-то причине пришли MD5_READY или MD5_REQUIRED, а данных в lParam нет (хотя с трудом могу представить в каком случае это может произойти).
  • rrrrrr © (19.08.17 09:44) [59]
    о, оказалось и вцл форма там вполне может быть если не капризы проходят.

    итого. универсальный случай для консоли и гуи.
    делается класс хоть от tobject, хоть от tcomponent

    методы : addFile() addFiles()
    свойство : maxThreads, threadsStillRunning
    события : onHashCalced(FileName, HashVal)
                   onAllDone()

    внутри : AllocateHwnd  которое будет принимать WM_USER + XXX

    при добавлении списка генерим нужный лимит потоков, остатки ставим в очередь
    при добавлении файла или ставим в очередь или запускаем поток в зависмости от текущего значения свойств

    в onHashCalced получаем очередной результат, обрабатываем оставшуюся очередь

    если это консоль, то крутим цикл со слипом а если гуи то все и так понятно.

    в итоге у нас библиотечный компонент, которым пользоваться может не только автор и не вдаваясь в потроха чего там кого ждет
  • rrrrrr © (19.08.17 09:47) [60]
    но есть несколько недостатков.....

    1. все очень быстро напишется
    2. подискутировать и порассусоливать будет не о чем дольше трех минут
    3. портянки кода, вышеприведенные станут какими-то коротенькими
  • Leonid Troyanovsky © (19.08.17 10:03) [61]

    > Denchik   (19.08.17 00:09) [58]

    Внимательно не смотрел, но смутила асинхронная посылка указателя
    с последующим освобождением памяти (например,  строки 132, 133).

    Безопасно можно слать <=8 байт (wparam, lparam).

    --
    Regards, LVT.
  • Leonid Troyanovsky © (19.08.17 10:19) [62]

    > rrrrrr ©   (19.08.17 09:44) [59]

    > итого. универсальный случай для консоли и гуи.

    Делать в консоли гуишные потоки не очень-то кошерно.

    Да и, во-ще, если и делать нечто полезное, то начать надо с пула потоков, IMHO.

    --
    Regards, LVT.
  • Leonid Troyanovsky © (19.08.17 10:58) [63]

    > rrrrrr ©   (19.08.17 09:47) [60]

    > 2. подискутировать и порассусоливать будет не о чем дольше
    > трех минут

    У мну есть 3 минуты, готов порассусоливать на такую тему:
    диспетчер и рабочие потоки обмениваются данными через асинхронные сообщения, а решение о загрузке потоков диспетчер принимает
    по статистике занятости потока  (GetThreadTimes?).

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

    --
    Regards, LVT.
  • rrrrrr © (19.08.17 11:19) [64]
    Делать в консоли гуишные потоки не очень-то кошерно.

    во первых что такое гуишные потоки? во вторых где они у меня там?
    в третьих с какого года и каким фз win32 консоли запрещено пользоваться гуи?

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


    так все же просто.
    однажды давно я тянул формы 101 и 102 с цб коих было миллион.
    и сначала потоки были одноразовыми.
    потом я подумал а зачем это собственно?
    в результате они перестали умирать выполнив порцию работы.

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

    сверху был диспетчер, который еще считал, дает ли увеличение количества потоков прирост производительности показателю "получено форм в секунду"

    и адаптивно выбирал всю полосу прокси которая доступна рабочему месту.

    дома где я один и оптика можно было иметь линейный рост производительности где-то до нескольких сотен потоков.

    а на работе было все уже печальнее. 10-15 потоков и все.
    дальнейший рост просто тратил ресурсы не увеличивая скорость обработки
  • Leonid Troyanovsky © (19.08.17 16:15) [65]

    > rrrrrr ©   (19.08.17 11:19) [64]

    > во первых что такое гуишные потоки? во вторых где они у
    > меня там?
    IsGUIThread function. Вызов Post(Thread)Message делает их таковыми.

    > в третьих с какого года и каким фз win32 консоли запрещено
    > пользоваться гуи?
    Никем не запрещено. Просто не люблю котлеты с мухами.

    > то есть стал сендмессадж с результатом наверх. и по результатам
    > сенда поток либо получал новое задание, либо умирал когда

    У ТС речь идет о ловле блох (на фоне дисковых операций или, скажем,
    скорости канала). Тут и SendMessage чемпион, но до всеобщей пользы,
    IMHO, далековато.

    --
    Regards, LVT.
  • rrrrrr © (19.08.17 16:25) [66]
    знаешь, все эти капризы конечно очень элегантны и гламурны.

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

    мне как бы не до этого.
    мне хорошо, когда на единицу времени уменьшается количество людей,
    которые продолжают ждать от меня чего-то.
  • rrrrrr © (19.08.17 16:28) [67]
    плюс три дня трендеть на тему плёвой задачки уровня школьного кружка погромирования - это как бы роскошь а не средство движения вперед.
    тоже имхо конечно
  • Eraser © (22.08.17 05:36) [68]

    > Denchik   (17.08.17 16:54) 

    автор, тебе тут правильно несколько раз намекнули. все упирается в hdd/ssd.
    даже два потока приведут к значительной деградации скорость, по сравнению с одним. почитай где-нибудь как hdd устроен. с SSD конечно легче, но не намного, ну 2-3 потока максимум, и то, тестировать надо.
 
Конференция "Основная" » Снова многопоточность
Есть новые Нет новых   [118571   +9][b:0.001][p:0.005]