• 有没有上城客在越南的踪迹? 2019-10-14
  • 本人几天前就吃了几个粽子了。 2019-10-14
  • 德媒:起好中文名,洋品牌入华第一步 2019-10-10
  • 紫光阁中共中央国家机关工作委员会 2019-10-10
  • 鹅肝做成冰淇淋 玉米做成小螃蟹?这位意大利厨神太6了 2019-10-05
  • 库恩:2017,站在历史长河中看中国这一年 2019-09-19
  • 【昆明天气】最新昆明今天天气,实时提供昆明气温、空气质量、24小时天气预报、生活指数查询 2019-09-12
  • 拆迁背后玩猫腻造成国家巨额经济损失 南昌这名官员“栽”了 2019-09-12
  • 竹编:缝隙里的乡愁文章中国国家地理网 2019-08-29
  • 习近平:真抓实干埋头苦干万众一心 夺取脱贫攻坚战全面胜利 2019-08-25
  • 回复@笑傲江湖V:咱那么多帖子一个赞都没有,又是咋回事呢? 2019-08-25
  • 新能源汽车产业升级将呈三大变化 2019-08-19
  • 次仁卓玛一家的端午节 2019-08-16
  • 回复@看着就想笑,不要以你现在满脑子的资产阶级的思想去度量共产主义社会人们的思想。呵呵! 2019-08-14
  • 聚焦两会:五年间习近平去的第一个“团组”是哪些? 2019-08-14
  • 浙江省快乐12走势图表

     找回密码
     立即注册

    QQ登录

    只需一步,快速开始

    查看: 450|回复: 0
    打印 上一主题 下一主题

    浙江11选5前三直选遗漏: VC++线程池的使用

    [复制链接]
    跳转到指定楼层
    楼主
    发表于 2019-5-26 13:42:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    在一般的设计中,当需要一个线程时,就创建一个,但是当线程过多时可能会影响系统的整体效率,这个性能的下降主要体现在:当线程过多时在线程间来回切换需要花费时间,而频繁的创建和销毁线程也需要花费额外的机器指令,同时在某些时候极少数线程可能就可以处理大量,比如http服务器可能只需要几个线程就可以处理用户发出的http请求,毕竟相对于用户需要长时间来阅读网页来说,CPU只是找到对应位置的页面返回即可。在这种情况下为每个用户连接创建一个线程长时间等待再次处理用户请求肯定是不划算的。为了解决这种问题,提出了线程池的概念,线程池中保存一定数量的 线程,当需要时,由线程池中的某一个线程来调用对应的处理函数。通过控制线程数量从而减少了CPU的线程切换,而且用完的线程还到线程池而不是销毁,下一次再用时直接从池中取,在某种程度上减少了线程创建与销毁的消耗,从而提高效率
    在Windows上,使用线程池十分简单,它将线程池做为一个整体,当需要使用池中的线程时,只需要定义对应的回调函数,然后调用API将回调函数进行提交,系统自带的线程池就会自动执行对应的回调函数。从而实现任务的执行,这种方式相对于传统的VC线程来说,程序员不再需要关注线程的创建与销毁,以及线程的调度问题,这些统一由系统完成,只需要将精力集中到逻辑处理的回调函数中来,这样将程序员从繁杂的线程控制中解放出来。同时Windows中线程池一般具有动态调整线程数量的自主行为,它会根据线程中执行任务的工作量来自动调整线程数,即不让大量线程处于闲置状态,也不会因为线程过少而有大量任务处于等待状态。
    在windows上主要有四种线程池
    1. 普通线程池
    2. 同步对象等待线程池
    3. 定时器回调线程池
    4. 完成端口回调线程池
    这些线程池最大的特点是需要提供一个由线程池中线程调用的回调函数,当条件满足时回调函数就会被线程池中的对应线程进行调用。从设计的角度来说,这样的设计大大简化了应用程序考虑多线程设计时的难度,此时只需要考虑回调函数中的处理逻辑和被调用的条件即可,而不必考虑线程的创建销毁等等问题(一些设计还可以绕开繁琐的同步处理)。需要注意的就是一般不要在这些回调函数中设计处理类似UI消息循环那样的循环,即不要长久占用线程池中的线程。
    下面来依次说明各种线程池的使用:
    普通线程池
    普通线程池在使用时主要是调用QueueUserWorkItem函数将回调函数加入线程池队列,线程池中一旦有空闲的线程就会调用这个回调,函数原型如下:
    [C++] 纯文本查看 复制代码
    BOOL WINAPI QueueUserWorkItem(
      __in      LPTHREAD_START_ROUTINE Function,
      __in_opt  PVOID Context,
      __in      ULONG Flags
    );
    第一个参数是一个回调函数地址,函数原型与线程函数原型相同,所以在设计时可以考虑使用宏开关来指定这个回调函数作为线程函数还是作为线程池的回调函数
    第二个参数是传给回调函数的参数指针
    第三个参数是一个标志值,它的主要值及其含义如下:
    标志
    含义

    WT_EXECUTEDEFAULT
    线程池的默认标志

    WT_EXECUTEINIOTHREAD
    以IO可警告状态运行线程回调函数

    WT_EXECUTEINPERSISTENTTHREAD
    该线程将一直运行而不会终止

    WT_EXECUTELONGFUNCTION
    执行一个运行时间较长的任务(这会使系统考虑是否在线程池中创建新的线程)

    WT_TRANSFER_IMPERSONATION
    以当前的访问字串运行线程并调用回调函数
    下面是一个具体的例子:
    [C++] 纯文本查看 复制代码
    void CALLBACK ThreadProc(LPVOID lpParam);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        int nWaitTime;
        while (TRUE)
        {
            printf("请输入线程等待事件:");
            scanf_s("%d", &nWaitTime);
            printf("\n");
            if (0 == nWaitTime)
            {
                break;
            }
            //将任务放入到队列中进行排队
            QueueUserWorkItem((LPTHREAD_START_ROUTINE)ThreadProc, &nWaitTime, WT_EXECUTELONGFUNCTION);
        }
    
        //结束主线程
        printf("主线程[%04x]\n", GetCurrentThreadId());
        return 0;
    }
    
    void CALLBACK ThreadProc(LPVOID lpParam)
    {
        int nWaitTime = *(int*)lpParam;
    
        printf("线程[%04x]将等待%ds\n", GetCurrentThreadId(), nWaitTime);
        Sleep(nWaitTime * 1000);
        printf("线程[%04x]执行完毕\n", GetCurrentThreadId());
    }
    这段代码上我们加入了WT_EXECUTELONGFUNCTION标识,其实在计算机中,只要达到毫秒级的,这个时候已经达到了系统进行线程切换的时间粒度,这个时候它就是一个需要长时间执行的任务
    定时器回调线程池
    定时器回调主要经过下面几步:
    1. 调用CreateTimerQueue:创建定时器回调的队列
    2. 调用CreateTimerQueueTimer创建一个指定时间周期的计时器对象,并指定对应的回调函数及参数
    之后当指定的时间片到达,就会将对应的回调历程放入到队列中,一旦线程池中有空闲的线程就执行它
    另外可以调用对应的函数对其进行相关的操作:
    1. 可以调用ChangeTimerQueueTimer修改一个已有的计时器对象的计时周期
    2. 调用DeleteTimerQueueTimer删除一个计时器对象
    3. 调用DeleteTimerQueue删除这样一个线程池对象,在删除这个线程池的时候它上面绑定的回调也会被删除,所以在编码时可以直接删除线程池对象而不用调用DeleteTimerQueueTimer删除每一个绑定的计时器对象。但是为了编码的完整性,最好加上删除计时器对象的操作
    下面是一个使用的具体例子
    [C++] 纯文本查看 复制代码
    VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE hTimeQueue = CreateTimerQueue();
        HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    
        HANDLE hTimer;
        CreateTimerQueueTimer(&hTimer, hTimeQueue, (WAITORTIMERCALLBACK)TimerCallback, &hEvent, 10000, 0, WT_EXECUTEDEFAULT);
    
        //等待定时器历程被调用
        WaitForSingleObject(hEvent, INFINITE);
    
        //关闭事件对象
        CloseHandle(hEvent);
        //删除定时器与定时器线程池的绑定
        DeleteTimerQueueTimer(hTimeQueue, hTimer, NULL);
    
        //删除定时器线程池
        DeleteTimerQueue(hTimeQueue);
    
        return 0;
    }
    
    VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
    {
        HANDLE hEvent = *(HANDLE*)lpParameter;
        if (TimerOrWaitFired)
        {
            printf("定时器回调历程[%04x]被执行\n", GetCurrentThreadId());
        }
    
        SetEvent(hEvent);
    }
    上述的代码中我们定义了一个同步事件对象,这个事件对象将在定时器历程中设置为有信号,这样方便我们在主线程中等待计时器历程执行完成
    同步对象等待线程池
    使用同步对象等待线程池只需要调用函数RegisterWaitForSingalObject,将一个同步对象绑定,当这个同步对象变为有信号或者等待的时间到达时,会调用对应的回调历程。该函数原型如下:
    [C++] 纯文本查看 复制代码
    BOOL WINAPI RegisterWaitForSingleObject(
      __out     PHANDLE phNewWaitObject,
      __in      HANDLE hObject,
      __in      WAITORTIMERCALLBACK Callback,
      __in_opt  PVOID Context,
      __in      ULONG dwMilliseconds,
      __in      ULONG dwFlags
    );
    第一个参数是一个输出参数,返回一个等待对象的句柄,我们可以将其看做这个线程池的句柄
    第二个参数是一个同步对象
    第三个参数是对应的回调函数
    第四个参数是传入到回调函数中的参数指针
    第五个参数是等待的时间
    第六个参数是一个标志与函数QueueUserWorkItem中的标识含义相同
    对应回调函数的原型如下:
    [C++] 纯文本查看 复制代码
    VOID CALLBACK WaitOrTimerCallback(
      __in  PVOID lpParameter,
      __in  BOOLEAN TimerOrWaitFired
    );
    当同步对象变为有信号或者等待的时间到达时都会调用这个回调,它的第二个参数就表示它所等待的对象是否为有信号。
    下面是一个使用的例子.
    [C++] 纯文本查看 复制代码
    void WaitEventCallBackProc(PVOID lpParameter, BOOLEAN TimerOrWaitFired);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE hWait;
        HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
        //注册等待同步对象的线程池
        RegisterWaitForSingleObject(&hWait, hEvent, (WAITORTIMERCALLBACK)WaitEventCallBackProc, NULL, 5000, WT_EXECUTELONGFUNCTION);
        for(int i = 0; i < 5; i++)
        {
            SetEvent(hEvent);
            Sleep(5000);
        }
    
        UnregisterWaitEx(hWait, hEvent);
        CloseHandle(hEvent);
        CloseHandle(hWait);
        return 0;
    }
    
    void WaitEventCallBackProc(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
    {
        if (TimerOrWaitFired)
        {
            printf("线程[%04x]等到事件对象\n");
        }else
        {
            printf("线程[%04x]等待事件对象超时\n");
        }
    }
    完成端口线程池
    在前面讲述文件操作的博文中,讲解了在文件中完成端口的使用,其实完成端口本质上就是一个线程池,或者说,windows上自带的线程池是使用完成端口的基础之上编写的。所以在这,完成端口线程池的使用将比IO完成端口来的简单
    通过调用BindIoCompletionCallback函数来将一个IO对象句柄与对应的完成历程绑定,这样在对应的IO操作完成后,对应的历程将会被丢到线程池中准备执行
    相比于前面的文件中的完成端口,这个完成端口线程池要简单许多,文件的完成端口需要自己创建完成多个线程,创建完成端口,并且将线程与完成端口绑定。另外还需要在线程中调用相应的等待函数等待IO操作完成,而线程池则不需要这些操作,我只需要准备一个完成历程,然后调用BindIoCompletionCallback,这样一旦历程被调用,就可以肯定IO操作一定完成了。这样我们只需要将主要精力集中在完成历程的编写中
    函数BindIoCompletionCallback的原型如下:
    [C++] 纯文本查看 复制代码
    BOOL WINAPI BindIoCompletionCallback(
      __in  HANDLE FileHandle,
      __in  LPOVERLAPPED_COMPLETION_ROUTINE Function,
      __in  ULONG Flags
    );
    第一个参数是一个对应IO操作的句柄
    第二个参数是对应的完成历程函数指针
    第三个参数是一个标志,与之前的标识相同
    完成历程的函数原型如下:
    [C++] 纯文本查看 复制代码
    VOID CALLBACK FileIOCompletionRoutine(
      __in  DWORD dwErrorCode,
      __in  DWORD dwNumberOfBytesTransfered,
      __in  LPOVERLAPPED lpOverlapped
    );
    第一个参数是一个错误码,当IO操作发生错误时可以通过这个参数获取当前错误原因
    第二个参数是当前IO操作操作的字节数
    第三个参数是一个OVERLAPPED结构
    这函数的使用与之前文件完成端口中完成历程一样
    下面我们将之前文件完成端口的例子进行改写,如下:
    [C++] 纯文本查看 复制代码
    typedef struct tagIOCP_OVERLAPPED
    {
        OVERLAPPED Overlapped;
        HANDLE hFile; //操作的文件句柄
        DWORD dwDataLen; //当前操作数据的长度
        LPVOID pData; //操作数据的指针
        DWORD dwWrittenLen; //写入文件中的数据长度
    }IOCP_OVERLAPPED, *LPIOCP_OVERLAPPED;
    
    #define MAX_WRITE_THREAD 20 //写线程总数
    #define EVERY_THREAD_WRITTEN 100 //每个线程写入信息数
    
    LARGE_INTEGER g_FilePointer; //全局的文件指针
    void GetAppPath(LPTSTR lpAppPath)
    {
        TCHAR szExePath[MAX_PATH] = _T("");
        GetModuleFileName(NULL, szExePath, MAX_PATH);
        size_t nPathLen = 0;
        StringCchLength(szExePath, MAX_PATH, &nPathLen);
        for (int i = nPathLen; i > 0; i--)
        {
            if (szExePath[i] == _T('\\'))
            {
                szExePath[i + 1] = _T('\0');
                break;
            }
        }
    
        StringCchCopy(lpAppPath, MAX_PATH, szExePath);
    }
    
    VOID CALLBACK WriteThread(LPVOID lpParam);
    VOID CALLBACK FileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TCHAR szAppPath[MAX_PATH] = _T("");
        GetAppPath(szAppPath);
        StringCchCat(szAppPath, MAX_PATH, _T("IocpLog.txt"));
        HANDLE hFile = CreateFile(szAppPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE)
        {
            return 0;
        }
    
        //绑定IO完成端口
        BindIoCompletionCallback(hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)FileIOCompletionRoutine, 0);
        //往日志文件中写入Unicode前缀
        LPIOCP_OVERLAPPED pIocpOverlapped = (LPIOCP_OVERLAPPED)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IOCP_OVERLAPPED));
        pIocpOverlapped->dwDataLen = sizeof(WORD);
        pIocpOverlapped->hFile = hFile;
        WORD dwUnicode = MAKEWORD(0xff, 0xfe); //构造Unicode前缀
        pIocpOverlapped->pData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WORD));
        CopyMemory(pIocpOverlapped->pData, &dwUnicode, sizeof(WORD));
        //偏移文件指针
        pIocpOverlapped->Overlapped.Offset = g_FilePointer.LowPart;
        pIocpOverlapped->Overlapped.OffsetHigh = g_FilePointer.HighPart;
        g_FilePointer.QuadPart += pIocpOverlapped->dwDataLen;
    
        //写文件
        WriteFile(hFile, pIocpOverlapped->pData, pIocpOverlapped->dwDataLen, &pIocpOverlapped->dwWrittenLen, &pIocpOverlapped->Overlapped);
    
        //创建线程进行写日志操作
        HANDLE hWrittenThreads[MAX_WRITE_THREAD];
        for (int i = 0; i < MAX_WRITE_THREAD; i++)
        {
            hWrittenThreads[i] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WriteThread, &hFile, 0, NULL);
        }
    
        //等待所有写线程执行完成
        WaitForMultipleObjects(MAX_WRITE_THREAD, hWrittenThreads, TRUE, INFINITE);
        for (int i = 0; i < MAX_WRITE_THREAD; i++)
        {
            CloseHandle(hWrittenThreads[i]);
        }
    
        CloseHandle(hFile);
        return 0;
    }
    
    VOID CALLBACK FileIOCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
    {
        LPIOCP_OVERLAPPED pIOCPOverlapped = (LPIOCP_OVERLAPPED)lpOverlapped;
    
        //释放对应的内存空间
        printf("线程[%04x]得到IO完成通知,写入长度%d\n", GetCurrentThreadId(), pIOCPOverlapped->dwDataLen);
        if (pIOCPOverlapped->pData != NULL)
        {
            HeapFree(GetProcessHeap(), 0, pIOCPOverlapped->pData);
        }
    
        if (NULL != pIOCPOverlapped)
        {
            HeapFree(GetProcessHeap(), 0, pIOCPOverlapped);
            pIOCPOverlapped = NULL;
        }
    }
    
    VOID CALLBACK WriteThread(LPVOID lpParam)
    {
        TCHAR szBuf[255] = _T("线程[%04x]模拟写入一条日志记录\r\n");
        TCHAR szWrittenBuf[255] = _T("");
        StringCchPrintf(szWrittenBuf, 255, szBuf, GetCurrentThreadId());
    
        for (int i = 0; i < EVERY_THREAD_WRITTEN; i++)
        {
            LPIOCP_OVERLAPPED lpIocpOverlapped = (LPIOCP_OVERLAPPED)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(IOCP_OVERLAPPED));
            size_t dwBufLen = 0;
            StringCchLength(szWrittenBuf, 255, &dwBufLen);
            lpIocpOverlapped->dwDataLen = dwBufLen * sizeof(TCHAR);
            lpIocpOverlapped->pData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (dwBufLen + 1) * sizeof(TCHAR));
            CopyMemory(lpIocpOverlapped->pData, szWrittenBuf, dwBufLen * sizeof(TCHAR));
    
            lpIocpOverlapped->hFile = *(HANDLE*)lpParam;
            //同步文件指针
            *((LONGLONG*)&(lpIocpOverlapped->Overlapped.Pointer)) = InterlockedCompareExchange64(&g_FilePointer.QuadPart, g_FilePointer.QuadPart + lpIocpOverlapped->dwDataLen, g_FilePointer.QuadPart);
    
            //写文件
            WriteFile(lpIocpOverlapped->hFile, lpIocpOverlapped->pData, lpIocpOverlapped->dwDataLen, &lpIocpOverlapped->dwWrittenLen, &lpIocpOverlapped->Overlapped);
        }
    }


    C VC C++ MFC 汇编 函数 脚本 辅助 多开 注入 内存 插件 破解 基址 窗口 大漠 绑定 编程 交流 论坛 实例 源码
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    VC中文网 - 豫ICP备14012807号|小黑屋|联系客服|金币冲值|浙江省快乐12走势图表

    GMT+8, 2019-10-16 01:14 , Processed in 0.140625 second(s), 24 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 浙江省快乐12走势图表 返回列表
  • 有没有上城客在越南的踪迹? 2019-10-14
  • 本人几天前就吃了几个粽子了。 2019-10-14
  • 德媒:起好中文名,洋品牌入华第一步 2019-10-10
  • 紫光阁中共中央国家机关工作委员会 2019-10-10
  • 鹅肝做成冰淇淋 玉米做成小螃蟹?这位意大利厨神太6了 2019-10-05
  • 库恩:2017,站在历史长河中看中国这一年 2019-09-19
  • 【昆明天气】最新昆明今天天气,实时提供昆明气温、空气质量、24小时天气预报、生活指数查询 2019-09-12
  • 拆迁背后玩猫腻造成国家巨额经济损失 南昌这名官员“栽”了 2019-09-12
  • 竹编:缝隙里的乡愁文章中国国家地理网 2019-08-29
  • 习近平:真抓实干埋头苦干万众一心 夺取脱贫攻坚战全面胜利 2019-08-25
  • 回复@笑傲江湖V:咱那么多帖子一个赞都没有,又是咋回事呢? 2019-08-25
  • 新能源汽车产业升级将呈三大变化 2019-08-19
  • 次仁卓玛一家的端午节 2019-08-16
  • 回复@看着就想笑,不要以你现在满脑子的资产阶级的思想去度量共产主义社会人们的思想。呵呵! 2019-08-14
  • 聚焦两会:五年间习近平去的第一个“团组”是哪些? 2019-08-14
  • 2元彩票七星彩走势图 11选五彩票一定牛 快3开奖青海 21庄家胜率 五星巴西 有什么赚钱快的门路 比照娱乐 彩票开奖直播大厅 齐鲁福彩 贵州快三今天开奖结果查询结果 澳洲幸运10是什么颜色 六合图库总站即时开奘 真人龙虎斗app 德州扑克大小顺序 排球比分显