Windows 线程池

  • 说起底层的线程操作一般都不会陌生,Windows 提供了 CreateThread 函数来创建线程,为了同步线程的操作,Windows 提供了事件内核对象、互斥量内核对象、关键段、原子操作等等方式,但总体来说操作线程难度还是非常大的,需要考虑到诸多的问题,比如死锁等,能不能建立一种机制即提高性能又方便操作线程呢,正因为有这个需求 Windows 提出了线程池的概念,目前线程池可以做以下四个定制的工作,分别是:(1) 以异步方式调用一个函数 (2) 每隔一段时间调用一个函数 (3) 在内核对象触发时调用一个函数 (4) 在异步 I/O 请求时调用一个函数

0x01 以异步方式调用一个函数

  • 异步方式调用一个函数是所有线程池中最简单的一个操作

  • 来看看执行这个操作需要哪些函数:

    (1)PTP_WORK CreateThreadpoolWork (2)SubmitThreadpoolWork (3)WaitForThreadpoolWorkCallbacks (4)CloseThreadpoolWork (5)VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work);
  • 值得注意的是 WaitForThreadpoolWorkCallbacks 函数,想象以下当某一个时刻有 2 个线程正在执行操作,而 3 个线程等待,如果我想等待这 5 个线程都执行完操作,那么向 WaitForThreadpoolWorkCallbacks 的第二个函数中传入 FALSE 即可,如果只等待 2 个线程完成的话传入 TRUE 即可。例子如下:
#include <process.h>
#include <Windows.h>
#include <iostream>
using namespace std; DWORD ThreadPoolTest1(VOID);
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work); int main(int argc, char **argv)
{
ThreadPoolTest1();
return 0;
} DWORD ThreadPoolTest1(VOID)
{
// 创建一个线程池工作项,第三个参数用于定制线程池
PTP_WORK ThreadPool = CreateThreadpoolWork(WorkCallback, NULL, NULL); // 提交 4 次线程池请求,也就是运行 4 次线程池函数
SubmitThreadpoolWork(ThreadPool);
SubmitThreadpoolWork(ThreadPool);
SubmitThreadpoolWork(ThreadPool);
SubmitThreadpoolWork(ThreadPool); // TRUE 为只能取消尚未运行的任务,FALSE 是等待所有的任务完成
WaitForThreadpoolWorkCallbacks(ThreadPool, FALSE); // 销毁线程池工作项
CloseThreadpoolWork(ThreadPool);
return TRUE;
} // 线程池函数原型
VOID CALLBACK WorkCallback(PTP_CALLBACK_INSTANCE Instance, PVOID Context, PTP_WORK Work)
{
// 模拟工作 600 毫秒
cout << "WorkCallback" << endl;
Sleep(600);
}

0x02 每隔一段时间调用一个函数

  • 每个一段时间调用一个函数,稍微复杂一点

  • 看看需要调用哪些函数:

    (1)VOID CALLBACK TimeoutCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PTP_TIMER Work) (2)PTP_TIMER CreateThreadpoolTimer() (3)IsThreadpoolTimerSet() (4)SetThreadpoolTimer() (5)WaitForThreadpoolTimerCallbacks() (6)CloseThreadpoolTimer()
  • 例子如下:
#include <process.h>
#include <Windows.h>
#include <iostream>
using namespace std; DWORD ThreadPoolTest2(VOID);
VOID CALLBACK TimeoutCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PTP_TIMER Work); // 控制线程变量
LONG Statistics; int main(int argc, char **argv)
{
ThreadPoolTest2();
return 0;
} DWORD ThreadPoolTest2(VOID)
{
// 创建一个定时器线程池对象
PTP_TIMER ThreadPoolTimer = CreateThreadpoolTimer(TimeoutCallback, NULL, NULL); // 判断线程池的定时器是否已经被设置过,其实定时器可以多次被设置
if (IsThreadpoolTimerSet(ThreadPoolTimer))
{
cout << "该定时器已经被设置" << endl;
return FALSE;
} // 设置定时器的时间
LARGE_INTEGER time; time.QuadPart = -1; // -1 比较特殊表示立即执行,负数表示相对时间,正数表示绝对时间
FILETIME filetime; filetime.dwLowDateTime = time.LowPart; filetime.dwHighDateTime = time.HighPart;
SetThreadpoolTimer(ThreadPoolTimer, &filetime, 1000, 0); // 等待线程池中的线程操作完成
while (Statistics < 5); Sleep(100);
WaitForThreadpoolTimerCallbacks(ThreadPoolTimer, FALSE); // 销毁定时器线程池对象
CloseThreadpoolTimer(ThreadPoolTimer);
return TRUE;
} // 定时器线程池线程原型
VOID CALLBACK TimeoutCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PTP_TIMER Work)
{
cout << "[*] TimeoutCallback" << endl;
InterlockedExchangeAdd(&Statistics, 1);
}
  • 值得注意的是 WaitForThreadpoolTimerCallbacks 并不具有等待全部的线程池线程操作完成,所以我利用原子操作等待 Statistics 变为 5;SetThreadpoolTimer 的最后一个参数用于将多个计时器分为一组,这个参数用的比较少。运行结果如下:

0x03 在内核对象触发时调用一个函数

  • 什么是内核对象,内核对象就是内核的一个对象,可以被用于完成线程的同步问题。所有的内核对象都有两种状态,一个是触发态一个是非触发态。所以完成这样一个需求的线程池首先需要设置一个非触发态的内核对象,并且和这个线程池相互绑定,等到这个内核对象变为触发状态时该线程池就会调用一个函数来处理接下来的问题

  • 来看看完成这个定制的线程池需要哪些函数:

    (1) CreateThreadpoolWait():创建一个等待内核对象的线程池对象 (2) SetThreadpoolWait():将线程池函数和内核对象绑定 (3) WaitForThreadpoolWaitCallbacks():等待线程池函数操作完成 (4) CloseThreadpoolWait():销毁等待内核对象的线程池对象 (5) VOID CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult):线程池函数原型
  • 例子如下:
#include <process.h>
#include <Windows.h>
#include <iostream>
using namespace std; DWORD ThreadPoolTest3(VOID);
VOID CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult); // 用于控制线程的计数
LONG ThreadCount; int main(int argc, char **argv)
{
ThreadPoolTest3();
return 0;
} DWORD ThreadPoolTest3(VOID)
{
PTP_WAIT ThreadPool[5]; HANDLE Event; // 创建一个事件内核对象
Event = CreateEvent(NULL, TRUE, FALSE, NULL); for (size_t i = 0; i < 5; i++)
{
// 创建一个等待内核对象为触发状态的线程池,并且将线程池等待对象和事件内核对象相互绑定
ThreadPool[i] = CreateThreadpoolWait(WaitCallback, NULL, NULL);
SetThreadpoolWait(ThreadPool[i], Event, 0);
} // 将内核对象变为触发状态
SetEvent(Event); // 等待操作完成
while (ThreadCount < 5) Sleep(0); // 关闭等待内核对象触发的工作对象
for (size_t i = 0; i < 5; i++)
{
CloseThreadpoolWait(ThreadPool[i]);
}
return TRUE;
} // 完成当内核对象触发线程池的线程函数原型
VOID CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE pInstance, PVOID Context, PTP_WAIT Wait, TP_WAIT_RESULT WaitResult)
{
// SetThreadpoolWait(ThreadWait, Event, NULL);
cout << "WaitCallback" << endl;
InterlockedExchangeAdd(&ThreadCount, 1);
}

0x04 在异步 I/O 请求时调用一个函数

  • 在异步 I/O 完成时调用一个函数,这个实现起来较为复杂

  • 来看看使用这个线程池需要哪些函数

    (1) CreateThreadpoolIo:创建一个等待 I/O 完成的线程池对象 (2) StartThreadpoolIo:完成端口关联,且需要在 ReadFile 和 WriteFile 函数之前调用 (3) WaitForThreadpoolIoCallbacks:等待线程池中的线程操作完成 (4) CloseThreadpoolIo:销毁线程池对象 (5) VOID CALLBACK OverlappedCompletionRoutine(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PVOID pOverlapped, ULONG IoResult, ULONG_PTR NumberOfBytesTransferred, PTP_IO pIo):线程池对象函数原型
  • 例子如下:
#include <process.h>
#include <Windows.h>
#include <iostream>
using namespace std; DWORD ThreadPoolTest4(VOID);
VOID CALLBACK OverlappedCompletionRoutine(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PVOID pOverlapped, ULONG IoResult, ULONG_PTR NumberOfBytesTransferred, PTP_IO pIo); HANDLE file;
DWORD Size;
PBYTE BufferFile; int main(int argc, char **argv)
{
ThreadPoolTest4();
return 0;
} DWORD ThreadPoolTest4(VOID)
{
// D盘 post.txt 打开文件
DWORD LastError;
file = CreateFile(TEXT("D:\\post.txt"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
LastError = GetLastError(); // 获取文件的大小
LARGE_INTEGER FileSize = { 0 };
GetFileSizeEx(file, &FileSize);
Size = FileSize.LowPart; // 创建一个等待 I/O 完成的线程池对象
PTP_IO ThreadIO = CreateThreadpoolIo(file, OverlappedCompletionRoutine, NULL, NULL);
StartThreadpoolIo(ThreadIO); // 用于 ReadFile 读取文件数据的缓冲区
BufferFile = (PBYTE)malloc(Size);
OVERLAPPED overlapped = { 0 }; overlapped.Offset = 0;
ReadFile(file, BufferFile, Size, NULL, &overlapped); // 等待线程池对象
WaitForThreadpoolIoCallbacks(ThreadIO, FALSE); // 销毁等待 I/O 完成的线程池对象
CloseThreadpoolIo(ThreadIO);
return TRUE;
} // 等待 I/O 完成的线程池对象线程原型
VOID CALLBACK OverlappedCompletionRoutine(PTP_CALLBACK_INSTANCE pInstance, PVOID pvContext, PVOID pOverlapped, ULONG IoResult, ULONG_PTR NumberOfBytesTransferred, PTP_IO pIo)
{
cout << "文件内容" << BufferFile << endl;
}
  • 打印结果如下:

利用 Windows 线程池定制的 4 种方式完成任务(Windows 核心编程)的更多相关文章

  1. 第11章 Windows线程池(1)_传统的Windows线程池

    第11章 Windows线程池 11.1 传统的Windows线程池及API (1)线程池中的几种底层线程 ①可变数量的长任务线程:WT_EXECUTELONGFUNCTION ②Timer线程:调用 ...

  2. (并发编程)进程池线程池--提交任务2种方式+(异步回调)、协程--yield关键字 greenlet ,gevent模块

    一:进程池与线程池(同步,异步+回调函数)先造个池子,然后放任务为什么要用“池”:池子使用来限制并发的任务数目,限制我们的计算机在一个自己可承受的范围内去并发地执行任务池子内什么时候装进程:并发的任务 ...

  3. java中线程池创建的几种方式

    java中创建线程池的方式一般有两种: 通过Executors工厂方法创建 通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize ...

  4. windows线程池四种情形(win核心读书笔记)

    windows线程池四种情形(win核心读书笔记) Mircosoft从Windows2000引入线程池API,并在Vista后对线程池重新构架,引入新的线程池API.以下所有线程池函数均适用于Vis ...

  5. 第11章 Windows线程池(2)_Win2008及以上的新线程池

    11.2 Win2008以上的新线程池 (1)传统线程池的优缺点: ①传统Windows线程池调用简单,使用方便(有时只需调用一个API即可) ②这种简单也带来负面问题,如接口过于简单,无法更多去控制 ...

  6. 《Windows核心编程系列》十一谈谈Windows线程池

    Windows线程池 上一篇博文我们介绍了IO完成端口.得知IO完成端口可以非常智能的分派线程.但是IO完成端口仅对等待它的线程进行分派,创建和销毁线程的工作仍然需要我们自己来做. 我们自己也可以创建 ...

  7. Windows核心编程:第11章 Windows线程池

    Github https://github.com/gongluck/Windows-Core-Program.git //第11章 Windows线程池.cpp: 定义应用程序的入口点. // #i ...

  8. 你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池

    你创建线程池最好分为两种线程池,io密集型线程池,或者cpu密集型线程池. 否则,如果只用一个线程池的话,不管是iO密集的线程,或者cpu消耗大的都放在同一个线程池的话,会发生线程池被撑满的情况

  9. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

随机推荐

  1. pandas函数高级

    一.处理丢失数据 有两种丢失数据: None np.nan(NaN) 1. None None是Python自带的,其类型为python object.因此,None不能参与到任何计算中. #查看No ...

  2. FreeBSD——艺术、科学、哲学概论

    FreeBSD--艺术.科学.哲学概论→→→→→概论: 信息都有一定的时效性.那么现在是什么时间?现在已经发布了 FreeBSD 12.2,距离下一个版本 13.0 还有约 4 个月. 『约定』 使用 ...

  3. .Net Core 2.1 升级3.1 问题整理

    随着技术的不断拓展更新,我们所使用的技术也在不断地升级优化,项目的框架也在不断地升级,本次讲解 .net core 2.1  升级到3.1所需要注意的事项: 当项目框架升级后,所有的Nuget引用也会 ...

  4. github个人主页 阿里云域名的绑定

    域名解析 我在阿里云上买了一个新域名:gaolu.name,我已经在GitHub Pages上建立了自己的博客:http://gaolu1215.github.io.现在我希望将gaolu.name映 ...

  5. Flutter教程- Dart语言规范-知识点整理

    Flutter教程- Dart语言知识点整理 Dart语言简介 Dart语言介绍 ① 注释的方式 ② 变量的声明 ③ 字符串的声明和使用 ④ 集合变量的声明 ⑤ 数字的处理 ⑥ 循环的格式 ⑦ 抛异常 ...

  6. 攻防世界 reverse Replace

    Replace 湖湘杯2018 查壳upx,手动脱壳,修复IAT,去掉重定向便可以运行. ida查看,流程清晰.关键函数check_E51090. int __cdecl main(int argc, ...

  7. winnt.h

    winnt.h https://docs.microsoft.com/en-us/windows/win32/api/winnt/ /*++ BUILD Version: 0091 Increment ...

  8. PTA 冒泡排序

    6-4 冒泡排序 (10 分)   编程实现冒泡排序函数.void bubbleSort(int arr[], int n);.其中arr存放待排序的数据,n为数组长度(1≤n≤1000). 函数接口 ...

  9. 学《跟我一起写Makefile》笔记发博词

    目录 笔记发博词 参考 笔记发博词 本系列笔记主要记录学了<跟我一起写Makefile>后的一些笔记 由于<跟我一起写Makefile>已经写得很详细了,所以我只是提取其中重要 ...

  10. helm3.1安装及结合ceph rbd 部署harbor

    [root@bs-k8s-ceph ~]# ceph -s cluster: id: 11880418-1a9a-4b55-a353-4b141e2199d8 health: HEALTH_WARN ...