Дата 23.02.01 11:34 От кого Vlad Mogilevsky <vladosha@inbox.ru> Кому <kaev@yandex.ru>
Задача программы была получив имя каталога, поискать в нем все каталоги. Получив список этих каталогов войти внутрь каждого и выбрать оттуда все файлы с определенным расширением. Тупой перебор рекурсивной функцией в один поток дает время выполнения около 2 часов при 200 тыс каталогов. Исходя из этого было решено использовать многопоточный поиск внутри нижнего уровня. Программа контролирует максимальное количество одновременно запущенных потоков при помощи iCurrentRun и не дает запустить более 10. Задержка между попытками запуска задана в расчете на 0.1 секунду (равна 10*100=1 секунда). Для синхронизации нескольких потоков и гарантии, что новый поток получит входной параметр правильно используется m_Processed. Переменная m_AllExited сигнализирует о завершении последнего потока. Через strScanDir передается в поток имя каталога, в котором нужно искать. В поток через pParam передается ссылка на интерфейс, вызвавший поток. Через эту ссылку можно передать сообщение интерфейсу от потока.
Внутри потока при некой обработке я использовал критическую секцию, которая общалась с некоторой глобальной переменной. Чтобы от момента считывания переменной до ее обновления потоком, другой поток ее не изменил и стоит критическая секция.
Рекомендации: ограничение потоков необходимо для того, чтобы программа вообще смогла завершиться. При бесконечном количестве - она виснет напрочь. При маленьком (например 1 - выполняется долго). Оптимальное количество 5-10 потоков. Ставить 400-500 штук тоже не следует - больше чем может вытянуть диск, вы из него выжать не сможете. Мой пример после этого стал работать не 2 часа, а 30-40 минут. Загрузка процессора небольшая.
#define EXTENSION txt static CEvent m_Processed; //event semaphore static CEvent m_AllExited; //event semaphore static CString strScanDir; //global parameter: directory static long iCurrentRun; //current threads running static CString strExt; //extension of files to look for void CDialogApp::Recurse(LPCTSTR pstr) //name of root directory to search in { CString str, tmpStr; int iThreads, iThreadRetry; CFileFind finder; strExt=EXTENSION; iThreads=10; //threads to start iThreadRetry=10; //retry to start thread in 0.1 seconds // build a string with wildcards CString strWildcard(pstr); strWildcard += _T("\\*"); //just only directories // start working for files BOOL bWorking = finder.FindFile(strWildcard); while (bWorking) { bWorking = finder.FindNextFile(); if (finder.IsDots()) // skip . and .. files; otherwise, we'd recur infinitely! continue; // if it's a directory, search into it str = finder.GetFilePath(); if (finder.IsDirectory()) { //start thread for finding m_Processed.ResetEvent(); strScanDir=str; //parameter for search - directory name //don't make more threads? if(iCurrentRun>=iThreads) while(iCurrentRun>=iThreads) Sleep(iThreadRetry*100); CWinThread* myWinThread = AfxBeginThread(*ProcessScan, GetSafeHwnd()); WaitForSingleObject(m_Processed,INFINITE); } } finder.Close(); //stop looking for files } UINT ProcessScan(LPVOID pParam) { CString strDir; strDir=strScanDir; m_AllExited.ResetEvent(); //just entered thread m_Processed.SetEvent(); //process main thread //process directory InterlockedIncrement((long*)&iCurrentRun); //current thread running counter increment //PostMessage((HWND) pParam, WM_REFRESH_STATUS, 0,0); //You can post any message to the interface CCriticalSection m_Lock; //critical section object CFileFind findFile; if(strExt.Left(1)==".") strExt=strExt.Mid(2); //cut "." at left position, if exists strDir += _T("\\*."); //only "*.<extension>" strDir +=strExt; // start working for files BOOL bWorking = findFile.FindFile(strDir); while (bWorking) { bWorking = findFile.FindNextFile(); if (findFile.IsDots()) // skip . and .. files; otherwise, we'd recur infinitely! continue; if(!findFile.IsDirectory()) //this is a file { str=findFile.GetFileName(); //this's a file! //.... process it! m_Lock.Lock(); //block code until we calculate something // do something, while blocking code //You don't have to use CriticalSection! //I used it only because of unsafe calculations m_Lock.Unlock(); //unblock code } } InterlockedDecrement((long*)&iCurrentRun); if (iCurrentRun==0) m_AllExited.SetEvent(); //exited all threads? return 0; }
Шаг прислал Vlad Mogilevsky.