Windows Practice_文件搜索器(三)_线程池

线程池

由于上一节滥用多线程,导致扫描时间变长,并且结果也不正确,原因就在于开启的线程太多太多了,导致很多时候开启线程失败,并且我们那样做的方式也不对,因为一个文件夹中也就几十个文件,扫描一个文件夹也就是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的时候,所有的线程都会被激活,都会直接往下运行,也就是说一个线程池中一次可以启动多个线程;
自动方式当创建事件内核对象为自动方式的时候,一个线程池中一次只能启动多个线程。
当然,对于我们当前的情况下,设置手动和设置自动的效果都是一样的。但是我们在设计线程池的时候,一定要设置为手动方式,因为有些逻辑可能一下要同时开启多个线程。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值