手把手教你实现一个完美的线程池 第三集 c++高阶开发

手把手教你实现一个完美的线程池 第三集 c++高阶开发,第1张

手把手教你实现一个完美的线程池(可商用)第二集 c++高阶开发_杀神李的博客-CSDN博客

承接上一集 我们已经完全实现了线程类的设计与开发 现在开始线程池的开发

一般多线程的程序,线程会有多个。但是一些任务不会一直占用线 程运行。 一旦任务结束,线程也会随之退出。 而有需要的时候,又会重新创建起来。 对于一般的程序来说,这没有什么问题,而且非常好。 可以保证线程资源的随用随取。 但是对于高性能服务器来说,这可能会是一个大隐患。 因为线程反复创建和销毁,都需要 *** 作全局堆,但是这玩意会产生 锁。 虽然不会卡死,但是如果很多线程同时创建,那么创建的瞬间会变 得很卡。 一旦大量用户连接上来,业务一忙,这可能导致服务器出现峰值卡 死、崩溃或者无响应。 正是因为这样,服务器往往会在启动阶段就准备好线程,避免线程 数量的大幅度波动。当空闲的时候,这些线程会处于休眠状态。 当大量任务到来的时候,这些任务会排队进入线程。 使得服务器整体的性能是可控的。 不会因为任务多,线程就多,导致各个线程之间进行竞争。 同时也不会因为线程批量的创建和销毁,导致内存、性能上的波 动。 另外线程数量过多,对系统也不是好事。太多CPU资源可能会占 满,使得同服务器的其他任务出现故障。为了均衡程序对服务器资源的使用,控制高峰时期服务器CPU的占 用率,那么一开始就准备合理的线程,成为了必要。 这些线程,自然就需要一个类来管理,方便使用者投递任务,同时 管理空闲线程,调度任务等等。 这样,我对线程池的需求基本就出来了:

1. 需要一个启动接口,指明线程池的尺寸。

2. 需要一个任务接口,用于向线程池投递任务。

3. 需要一个关闭接口,用于结束线程池。

虽然有了这个基础需求,但是一些问题我们需要考虑一下。 就是这个任务如何投递,如何分配? 投递任务的很可能不是线程池内的函数。 那么多线程环境下如何高效投递数据?如何保证数据跨线程传递的 线程安全问题? 首先我们来看看线程安全问题。 什么情况下会出现线程安全问题呢? 一个数据,如果各个线程都有访问和修改权力,那么它就会有线程安 全隐患。 如果一个数据只有一个线程能够访问,那么它就没有所谓的线程安 全问题了。 但是如果不能跨线程读写数据,那各个线程之间怎么进行数据交互 呢? 这个时候一种机制就可以起到作用。 那就是完成端口映射(Input/Output Completion Port,IOCP)。 我们先来回顾一下完成端口映射的基本模型: 

通过这个模型,我们可以看到,PostQueuedCompletionStatus 方法是可以跨线程投递数据到数据处理线程的。 利用好这一点,我们就可以通过这个接口向数据处理线程发送数据 处理请求! 不过首先我们要了解一下三个关键的API函数: 第一个是创建IOCP。 

HANDLE
WINAPI
CreateIoCompletionPort(
    HANDLE FileHandle,//首次创建使用
INVALID_HANDLE_VALUE
    HANDLE ExistingCompletionPort,//首次创建使用NULL
    ULONG_PTR CompletionKey,//首次使用可以使用NULL
    DWORD NumberOfConcurrentThreads//使用本IOCP可能
的线程数量
   );

这个接口可以反复调用。 第一次调用是创建句柄,需要指定线程数量。本次会返回IOCP句 柄。 第二次调用,一般要指定ExistingCompletionPort参数,来自第 一次调用的返回值。第二次无需指定线程数量,指定了也会被忽 略。 第二次调用怎么使用,我们会在后面异步通信的时候进一步学习。 

 第二个接口是获取完成端口状态。

BOOL
WINAPI
GetQueuedCompletionStatus(
    _In_ HANDLE CompletionPort,
    _Out_ LPDWORD lpNumberOfBytesTransferred,
    _Out_ PULONG_PTR lpCompletionKey,
    _Out_ LPOVERLAPPED* lpOverlapped,
    _In_ DWORD dwMilliseconds
   );

 第一个参数来自CreateIoCompletionPort函数的返回值。 第二、三、四个参数是外部传入的值。只需要填入本地变量去接这 些参数就行。 第五个参数指定等待的时间。如果填入INFINITE,则表示无限等 待。 第三个接口是投递完成状态。 

BOOL
WINAPI
PostQueuedCompletionStatus(
    _In_ HANDLE CompletionPort,
    _In_ DWORD dwNumberOfBytesTransferred,
    _In_ ULONG_PTR dwCompletionKey,
    _In_opt_ LPOVERLAPPED lpOverlapped
   );

第一个参数来自CreateIoCompletionPort函数的返回值。 第二、三个参数是用户指定的任意值。 第四个参数是一个异步参数。 注意这个参数比较特殊。任何从OVERLAPPED 继承的类型,或者 第一个属性为OVERLAPPED 的类型,都可以通过 CONTAINING_RECORD来获取原始类型的地址。 比如: 

class CIoData {
 public:
 WSAOVERLAPPED Overlapped;
 ……
};

那么可以通过 

CIoData* pIoData = CONTAINING_RECORD(lpOverlapped,
CIoData, Overlapped);

来从参数lpOverlapped获取到其对应的CIoData对象地址。 这里的Overlapped是CIoData的第一个属性。 而对应的投递动作则是: 

CIoData* pIoData = new CIoData();
PostQueuedCompletionStatus(hIOCP,dwNumberOfBytesTr
ansferred,dwCompletionKey,&pIoData->Overlapped);

最后异步参数传递的是pIoData的成员的地址,但是最终我们可以 通过CONTAINING_RECORD宏来获得pIoData的地址。 然后来 *** 作pIoData。 

期待第四集吧 兄弟们 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1331137.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存