Windows 檔案搜尋預設為單執行緒遞迴掃描,效率低且程式碼繁瑣 (需手動遞迴目錄樹)。本文提出 **多執行緒同步搜尋** 策略:發現子目錄即建立新執行緒處理,檔案在執行緒內即時比對,實現寬度優先並行掃描,代碼極簡且效能大幅提升。
FindFirstFile / FindNextFile:Win32 列舉目錄項目AfxBeginThread:MFC 執行緒建立CMutex / CEvent:同步物件保護共享資料
// 執行緒參數
struct THREADPARAM {
CString strPath; // 待掃描目錄
CString strFileName;// 目標檔名
};
THREADPARAM g_param;
// 同步物件
CMutex g_mutexThreadCount, g_mutexThreadParam, g_mutexPath;
CEvent g_event(FALSE, FALSE); // 手動重設, 初始無訊號
// 全域狀態
UINT g_uThreadCount = 0;
CStringArray g_strPathArray; // 找到的完整路徑
UINT GetFilePathThreadProc(LPVOID pParam)
{
if (!pParam) return 0;
THREADPARAM* p = (THREADPARAM*)pParam;
CString strPath = p->strPath;
CString strTarget = p->strFileName;
// 計數器 +1
g_mutexThreadCount.Lock(); g_uThreadCount++; g_mutexThreadCount.Unlock();
WIN32_FIND_DATA* pInfo = new WIN32_FIND_DATA;
HANDLE hFind = ::FindFirstFile(strPath + _T("\\*.*"), pInfo);
if (hFind == INVALID_HANDLE_VALUE) {
delete pInfo;
g_mutexThreadCount.Lock();
if (--g_uThreadCount == 0) g_event.SetEvent();
g_mutexThreadCount.Unlock();
return 0;
}
do {
// 跳過 . 和 ..
if (pInfo->cFileName[0] == '.') continue;
CString name = pInfo->cFileName;
if (pInfo->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// 子目錄 → 建立新執行緒遞迴掃描
g_mutexThreadParam.Lock();
g_param.strPath = strPath + _T("\\") + name;
g_param.strFileName = strTarget;
AfxBeginThread(GetFilePathThreadProc, &g_param, THREAD_PRIORITY_NORMAL);
g_mutexThreadParam.Unlock();
}
else {
// 檔案 → 比對名稱
if (name == strTarget) {
g_mutexPath.Lock();
g_strPathArray.Add(strPath + _T("\\") + name);
g_mutexPath.Unlock();
// 找到即可結束此執行緒 (若只找第一個)
g_mutexThreadCount.Lock();
if (--g_uThreadCount == 0) g_event.SetEvent();
g_mutexThreadCount.Unlock();
delete pInfo;
return 0;
}
}
} while (::FindNextFile(hFind, pInfo));
::FindClose(hFind);
delete pInfo;
g_mutexThreadCount.Lock();
if (--g_uThreadCount == 0) g_event.SetEvent();
g_mutexThreadCount.Unlock();
return 0;
}
void GetFilePath()
{
g_event.ResetEvent();
g_uThreadCount = 0;
g_strPathArray.RemoveAll();
g_param.strPath = m_strStartPath; // 使用者輸入起始目錄
g_param.strFileName = m_strTargetFile; // 使用者輸入檔名
AfxBeginThread(GetFilePathThreadProc, &g_param, THREAD_PRIORITY_NORMAL);
// 等待所有執行緒完成
::WaitForSingleObject(g_event.m_hObject, INFINITE);
if (g_strPathArray.GetSize() == 0)
AfxMessageBox(_T("未找到檔案"));
else
for (int i = 0; i < g_strPathArray.GetSize(); ++i)
AfxMessageBox(g_strPathArray[i]);
}
維護一個 CArray 記錄所有啟動的執行緒指標:
// 啟動時
CWinThread* pThread = AfxBeginThread(...);
g_threadArray.Add(pThread);
// 取消時
for (int i = 0; i < g_threadArray.GetSize(); ++i) {
DWORD exitCode;
if (::GetExitCodeThread(g_threadArray[i]->m_hThread, &exitCode) &&
exitCode == STILL_ACTIVE)
::TerminateThread(g_threadArray[i]->m_hThread, 0);
}
g_threadArray.RemoveAll();
g_event.SetEvent(); // 解除主執行緒等待
| 指標 | 單執行緒遞迴 | 多執行緒寬度優先 |
|---|---|---|
| 程式碼複雜度 | 高 (手動堆疊/遞迴) | 低 (系統自動排程) |
| CPU 利用率 | 單核心 | 多核心並行 (I/O 密集型顯著受益) |
| 記憶體開銷 | 低 | 較高 (每執行緒 1MB 堆疊 + 核心物件) |
| 深度目錄風險 | 堆疊溢位 | 無 (各目錄獨立執行緒) |
| 檔案系統壓力 | 循序讀取 | 並發隨機讀取 (SSD 受益大, HDD 可能降速) |
FindFirstFileEx(FindExInfoBasic, FIND_FIRST_EX_LARGE_FETCH) 非同步列舉,單執行緒即可高效處理百萬檔案。\\?\ 前綴支援 >260 字元路徑。編譯環境:VC++6.0 / Windows 2000 測試通過。現代專案建議配合 C++17 std::filesystem + 執行緒池重寫。