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的时候,所有的线程都会被激活,都会直接往下运行,也就是说一个线程池中一次可以启动多个线程;
自动方式当创建事件内核对象为自动方式的时候,一个线程池中一次只能启动多个线程。
当然,对于我们当前的情况下,设置手动和设置自动的效果都是一样的。但是我们在设计线程池的时候,一定要设置为手动方式,因为有些逻辑可能一下要同时开启多个线程。

原创粉丝点击