Windows Practice_文件搜索器(三)_线程池
来源:互联网 发布:淘宝 武士刀 编辑:程序博客网 时间:2024/06/11 04:00
线程池
由于上一节滥用多线程,导致扫描时间变长,并且结果也不正确,原因就在于开启的线程太多太多了,导致很多时候开启线程失败,并且我们那样做的方式也不对,因为一个文件夹中也就几十个文件,扫描一个文件夹也就是1um,而开启一个线程就需要2us,关闭一个线程也需要2us。一个C盘有好几万个文件夹,也就是说我们需要开启好几万个线程,天哪!!!这简直就是一个天文数字!!!这样一来,开启新线程反而降低了效率,所以我们不能频繁的开启、关闭线程。那么不这样做,那怎么做呢?这就是我们要说的线程池。
我们在程序扫描运行之前就开启一定数量的线程,在扫描完成之后再把这些线程关闭,这样就大大节省了线程创建和销毁的时间。
需要注意的是:并不是说前面那种创建线程的方式就一定不好,只不过不适用于我们目前的文件扫描,如果需要一个文件夹中有几十万个文件,每个文件夹的扫描需要很长的时间(相对于线程的创建和销毁时间),这样就适合遇到一个文件夹就创建一个线程的方式来扫描了。
对于线程池,我们创建一个多大的线程池呢?一般来说,线程池中线程的数量一般是CPU个数的两倍。比如CPU的个数是8个,那么一般就创建一个含有16个线程的线程池。
代码实现
#include <string>#include <stdio.h>#include <process.h>#include <Windows.h>#include <vector>HANDLE g_hExitEvent, g_hPushDirEvent;CRITICAL_SECTION g_cs;long g_lFindFileNumber = 0, g_lSearchFileNumber = 0, g_lSearchDirectoryNumber = 0, g_lWaitThreadNum = 0;DWORD g_dwThreadNum = 0;std::vector<std::wstring> g_vecDirNames;std::wstring MakeStandardDir(std::wstring wstrDir){ if (wstrDir.back() != L'\\') { wstrDir += L'\\'; } return wstrDir;}unsigned int __stdcall ThreadFunc(void *lParam){ std::wstring *pWstrSerach = static_cast<std::wstring *>(lParam); std::wstring wstrDirName; BOOL bRunnable = TRUE; while (true) { EnterCriticalSection(&g_cs); if (g_vecDirNames.empty()) { bRunnable = FALSE; } else { wstrDirName = g_vecDirNames.back(); g_vecDirNames.pop_back(); } LeaveCriticalSection(&g_cs); if (!bRunnable) { ResetEvent(g_hPushDirEvent); InterlockedAdd(&g_lWaitThreadNum, 1); if (g_lWaitThreadNum == g_dwThreadNum) SetEvent(g_hExitEvent); WaitForSingleObject(g_hPushDirEvent, INFINITE); InterlockedAdd(&g_lWaitThreadNum, -1); bRunnable = TRUE; continue; } // 扫描 WIN32_FIND_DATA find_data = { 0 }; HANDLE hFile = FindFirstFileW((MakeStandardDir(wstrDirName) + L"*.*").c_str(), &find_data); if (INVALID_HANDLE_VALUE != hFile) { do { if (wcscmp(find_data.cFileName, L".") == 0) { continue; } if (wcscmp(find_data.cFileName, L"..") == 0) { continue; } if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { EnterCriticalSection(&g_cs); g_vecDirNames.push_back(MakeStandardDir(wstrDirName) + find_data.cFileName); InterlockedAdd(&g_lSearchDirectoryNumber, 1); SetEvent(g_hPushDirEvent); LeaveCriticalSection(&g_cs); } else { if (wcsstr(find_data.cFileName, pWstrSerach->c_str()) != nullptr) { printf("%ls\r\n", (MakeStandardDir(wstrDirName) + find_data.cFileName).c_str()); InterlockedAdd(&g_lFindFileNumber, 1); } InterlockedAdd(&g_lSearchFileNumber, 1); } } while (FindNextFileW(hFile, &find_data)); if (!FindClose(hFile)) printf("error no:%d\r\n", errno); } } return 0;} int main(){ InitializeCriticalSection(&g_cs); g_hExitEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); g_hPushDirEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); do { if (NULL == g_hExitEvent) { printf("创建 g_hExitEvent 失败\r\n"); break; } if (NULL == g_hPushDirEvent) { printf("创建 g_hPushDirEvent 失败\r\n"); break; } std::vector<HANDLE> vecHandles; SYSTEM_INFO system_info = { 0 }; GetSystemInfo(&system_info); g_dwThreadNum = system_info.dwNumberOfProcessors * 2; std::wstring wstrSearch = L"ntdll"; g_vecDirNames.push_back(L"C:"); DWORD dwPrevTime = GetTickCount(); for (DWORD i = 0; i < g_dwThreadNum; ++i) { HANDLE handle = (HANDLE)_beginthreadex(nullptr, 0, ThreadFunc, &wstrSearch, 0, nullptr); if (NULL == handle) --i; else vecHandles.push_back(handle); } WaitForSingleObject(g_hExitEvent, INFINITE); DWORD dwTimeUse = (GetTickCount() - dwPrevTime) / 1000; for (auto handle : vecHandles) { //_endthreadex((unsigned)handle); CloseHandle(handle); } // msdn的解释 // https://msdn.microsoft.com/zh-cn/library/kdzttdcb.aspx printf("共找到 %lu 个 ntdll 相关的文件!\r\n", g_lFindFileNumber); printf("共查找了 %lu 个文件夹, %lu 个文件!\r\n", g_lSearchDirectoryNumber, g_lSearchFileNumber); printf("用时 %lu 秒\r\n", dwTimeUse); } while (false); if (NULL != g_hExitEvent) { CloseHandle(g_hExitEvent); } if (NULL != g_hPushDirEvent) { CloseHandle(g_hPushDirEvent); } DeleteCriticalSection(&g_cs); system("pause"); return 0;}
扫描结果如下:
仅仅用了7秒钟,较之于递归的37秒,已经提高了很大的效率。这个程序没有问题,有几点需要我们注意:
- g_hExitEvent,主要是用来判断程序合适退出的事件内核对象;
- g_hPushDirEvent,主要是用来判断是否新加入了一个文件夹;
- g_lWaitThreadNum,等待线程的个数,这是个很重要的变量,通过它才能正确的判断文件是否扫描完成。
其它的就没有什么特别需要注意了。
回顾时间内核对象的手动和自动方式:
手动方式当创建事件内核对象为手动方式的时候,在SetEvent的时候,所有的线程都会被激活,都会直接往下运行,也就是说一个线程池中一次可以启动多个线程;
自动方式当创建事件内核对象为自动方式的时候,一个线程池中一次只能启动多个线程。
当然,对于我们当前的情况下,设置手动和设置自动的效果都是一样的。但是我们在设计线程池的时候,一定要设置为手动方式,因为有些逻辑可能一下要同时开启多个线程。
- Windows Practice_文件搜索器(三)_线程池
- Windows Practice_文件搜索器(二)_多线程调试
- Windows Practice_文件_文件分割器(三)
- Windows Practice_文件搜索器(四)_封装文件扫描器
- Windows Practice_文件_文件分割器(一)
- Windows Practice_文件_文件分割器(二)
- Windows Practice_文件搜索器(一)递归
- Windows Practice_文件_内存映射(一)
- Windows Practice_文件_文件基础操作
- Windows Practice_文件_注册表操作
- Windows Practice_闹钟(三)_作业讲解和GDI概述
- Windows Practice_闹钟(一)_简易记事本
- Windows Practice_闹钟(二)_简易记事本
- Windows Practice_闹钟(六)_控件对象
- Windows Practice_内存映射_加载BMP
- Windows Practice_闹钟(四)_GDI对象概述
- Windows Practice_闹钟(五)_GDI绘制工具
- Java线程_(三)线程池
- html dom创建表格
- Python入门8_方法,属性,迭代器
- BZOJ1691: [Usaco2007 Dec]挑剔的美食家
- TabLayout+ViewPager使用
- Hibernate入门之ORM概述
- Windows Practice_文件搜索器(三)_线程池
- 77. Combinations
- 如何调整linux窗口大小-安装VMWare Tools
- [网络流24题] No3_最小路径覆盖问题
- 分享我的第一次Selenium自动化测试框架开发过程
- 我的博客园定制样式
- 万兆网络UDP速率传输性能测试(发送端)
- Java 内部类
- Sherlock and his girlfriend (Codeforces-776B)