微软这是对开发者友好的的软件厂商,在直接提供线程池使得开发者在需要使用线程池时无需自己编写(特别是调度的部分),用户无需自己创建和管理线程。微软默认用户使用线程池与不同的对象关联来满足四种需求:
1.以异步的方式调用一个函数。用户需要异步调用一个函数,主要将这个函数地址传给线程池,系统就会在合适的时间调用它。函数地址当然必须是接口定义好的函数指针。
//回调函数指针类型
VOID NTAPI ThreadFunc(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext
);
//将回调函数传递到线程池中
BOOL TrySubmitThreadpoolCallback(
PTP_SIMPLE_CALLBACK pfnCallback, //上边指定的回调函数
PVOID pvContext, //传递给回调函数的参数
PTP_CALLBACK_ENVIRON pche
);
TrySubmitThreadpoolCallback的主要工作就是创建一个(包含回调函数地址和参数的)线程池工作对象,并将工作对象加入到线程池中,由于用户环境的多样性函数TrySubmitThreadpoolCallback可能在创建ThreadpoolWork(TP_Work)时失败,故而导致整个函数不能完成工作而返回false。所以我们可以自己创建一个工作对象并自己添加到线程池中来完成这个过程,并处理失败的情况。使用如下函数:
//回调函数指针类型
VOID CALLBACK WorkCallback(
PTP_CALLBACK_INSTANCE Instance,
PVOID Context,
PTP_WORK Work //调用这个回调的工作对象
);
//创建工作对象
PTP_WORK CreateThreadpoolWork(
PTP_WORK_CALLBACK pfnWorkHandler, //上边这个类型的函数指针
PVOID pvContext, // 回调的参数
PTP_CALLBACK_ENVIRON pche
);
//把工作对象提交给线程池(这个函数不会失败)
VOID SubmitThreadpoolWork(PTP_WORK pWork);
对于同步问题我们可以调用函数等待或取消工作对象的调用:
VOID WaitForThreadpoolWorkCallbacks(
PTP_WORK pWork, //目标工作对象
BOOL bCancelPendingCallbacks //false,等到回调结束,true若回调在执行过程中等到它执行结束,否则标记为取消不会再被执行
);
//使用完工作对象之后要删除它
VOID CloseThreadpoolWork(PTP_WORK pwk);
和所有的wait系列函数一样,除非是在回调未执行的时候取消掉他,不然wait函数总是会阻塞当前线程直到对象被触发。
2.每隔一段时间调用一个函数。与异步调用一样用户通过创建一个定时器对象并传递给线程池,线程池根据定时器是否到达来做出是否调用回调函数的决定(而不是是否有空闲的线程)使用的接口也与异步调用类似:
//回调函数指针类型
VOID CALLBACK TimeoutCallback(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext,
PTP_TIMER pTimer //导致回调的定时器
);
//创建(线程池能够识别的)定时器
PTP_TIMER CreateThreadpoolTimer(
PTP_TIMER_CALLBACK pfnTimerCallback, //上边这个类型的函数指针
PVOID pvContext, // 回调的参数
PTP_CALLBACK_ENVIRON pcbe
);
//把定时器设置到线程池中
VOID SetThreadpoolTimer(
PTP_TIMER pTimer, //定时器对象
PFILETIME pftDueTime, //第一次执行回调的时间(基准),-1标识理解开始执行
DWORD msPeriod, //时间周期单位为微秒,0表示不再执行
DWORD msWindowLength //时间窗口,既执行完成之后不立即休眠等待一段时间(以避免线程sleep后在唤醒的代价)
);
//检查定时器对象是否被设置了(pftDueTime不为NULL)
BOOL IsThreadpoolTimerSet(PTP_TIMER pti);
//等待线程执行结果与WaitForThreadpoolWorkCallbacks一样
VOID WINAPI WaitForThreadpoolTimerCallbacks(
PTP_TIMER pti,
BOOL fCancelPendingCallbacks
);
//使用完之后删除
VOID WINAPI CloseThreadpoolTimer(
PTP_TIMER pti
);
3.当内核对象触发时调用一个函数。在线程池的线程中调用WaitForMultipleOBjecs(故不允许同一个内核对象多次被加入到list中)等待内核对象的触发,触发后调用回调函数,并解除等待对象与内核对象的关联(不再监视内核对象,若需要再次监视就需要再次调用SetThreadpoolWait)。
//回调函数指针类型
VOID CALLBACK WaitCallback(
PTP_CALLBACK_INSTANCE Instance,
PVOID Context,
PTP_WAIT Wait, //导致回调的等待对象
TP_WAIT_RESULT WaitResult //等待对象的等待结果
);
//创建线程池等待对象
PTP_WAIT WINAPI CreateThreadpoolWait(
PTP_WAIT_CALLBACK pfnwa, //上边这个类型的函数指针
PVOID pv, // 回调的参数
PTP_CALLBACK_ENVIRON pcbe
);
//把等待对象和内核对象句柄关联起来并设置到线程池中
VOID WINAPI SetThreadpoolWait(
PTP_WAIT pwa, //等待对象
HANDLE h, //要监视的内核句柄, 传入NULL时从线程池中删除等待对象
PFILETIME pftTimeout //等待时间
);
//等待线程执行结果与WaitForThreadpoolWorkCallbacks一样
VOID WINAPI WaitForThreadpoolWaitCallbacks(
PTP_WAIT pwa,
BOOL fCancelPendingCallbacks
);
//使用完之后删除
VOID WINAPI CloseThreadpoolWait(
PTP_WAIT pwa
);
回调函数的waitresult参数表示等待的结果:
4.当异步IO请求完成时调用一个函数。在异步IO中详细了解过,同样的使用方式与前集中差别不大:
//回调函数指针类型
VOID CALLBACK OverlappedCompletionRoutine(
PTP_CALLBACK_INSTANCE pInstance,
PVOID pvContext,
PVOID pOverlapped,
ULONG IoResult,
ULONG_PTR NumberOfBytesTransferred,
PTP_IO pIo
);
//创建完成端口
PTP_IO CreateThreadpoolIo(
HANDLE hDevice,
PTP_WIN32_IO_CALLBACK pfnIoCallback,
PVOID pvContext,
PTP_CALLBACK_ENVIRON pcbe
);
//把完成端口设置到线程池中
VOID StartThreadpoolIo(PTP_IO pio);
//等待完成端口结果与WaitForThreadpoolWorkCallbacks一样
VOID WaitForThreadpoolIoCallback(
PTP_IO pio,
BOOL bCancelPendingCallbacks
);
//取消完成端口
VOID CancelThreadpoolIo(PTP_IO pio);
//使用完之后删除
VOID CloseHandlepoolIo(PTP_IO pio);
对于各个回调接口中的pInstance在指定回调返回后的指定动作时使用,回调返回后可以释放一些锁:
虽然用到的不多,windows还是提供了自己定制线程池的方法,首先要创建线程池:
//创建新线程池
PTP_POOL CreateThreadpool(PVOID reserved);
//设置线程池中的最大线程和最小线程
void SetThreadpoolThreadMinimum(PTP_POOL pThreadpool,DWORD cthrdMin);
void SetThreadpoolThreadMaximum(PTP_POOL pThreadpool,DWORD cthrdMin);
为了让四种对象加入到我们自己所创建的线程池中还需要自定义的回调环境,作为Create这种变量时的参数PTP_CALLBACK_ENVIRON pcbe,此参数若为NULL则与默认线程池相关联,回调环境接口如下:
//初始化回调环境
VOID InitializeThreadpoolEnvironment(PTP_CALLBACK_ENVIRON pcbe);
//将回调环境与线程池进行关联
VOID SetThreadpoolCallbackpoo(PTP_CALLBACK_ENVIRON pche, PTP_POOL pThreadPool);
接下来就能想使用默认线程池一样的使用自定义的线程池,当时在线程池使用完成后需要自己清理(默认的生命周期与应用程序一样长,系统会清理),要创建一个清理组:
//创建一个清理组
PTP_CLEANUP_GROUP CreateThreadpoolCleanuGroup();
//将清理组和线程池相关联
VOID SetThreadpoolCallbackCleanupGroup(
PTP_CALLBACK_ENVIRON pcbe,
PTP_CLEANUP_GROUP ptpcg,
PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);
//SetThreadpoolCallbackCleanupGroup的回调
VOID CALLBACK CleanupGroupCancelback(
PVOID pvObjectContext,
PVOID pvCleanupContext);
当我们调用CloseThreadpoolWork,CloseThreadpoolTimer,CloseThreadpoolWait或是CloseThreadIo时,等于是隐式的将对应项从清理组中清理。但是结束线程池之前还是需要做一次清理才结束线程以防资源的泄漏:
//清理线程池
VOID CloseThreadpoolCleanupGroupMemebers(
PTP_CLEANUPGROUP ptpcg,
BOOL bCancelPendingCallbacks,
PVOID pvCleanupContext);
//释放清理组所占的资源
VOID WINAPI CloseThreadpoolCleanupGroup(PTP_CLEANUP_GROUP ptpcg);
//销毁回调环境
VOID DestroyThreadEnvironment(PTP_CALLBACK_ENVRION pcbe);
//销毁 所有线程都被终止所有项将被取消
VOID CloseThreadpool(PTP_POOL pThreadpool);
纤程我们只需要概念上理解就好了,一般也用不到。就是一个可执行的对象(函数对象?)自己包含了执行的状态(栈和寄存器)。被我们自己设计的逻辑所调度(switchto函数)。