Window线程池实现和解析、#pragma和构造函数参数列表基础知识

原创 2012年03月24日 11:14:07

先上实现代码:(转自:http://blog.csdn.net/pjchen/article/details/170606

#ifndef _ThreadPool_H_
#define _ThreadPool_H_
#pragma warning(disable: 4530)
#pragma warning(disable: 4786)
#include <cassert>
#include <vector>
#include <queue>
#include <windows.h>
using namespace std;
class ThreadJob //工作基类
{
public:
	//供线程池调用的虚函数
	virtual void DoJob(void *pPara) = 0;
};
class ThreadPool
{
protected:
	//用户对象结构
	struct CallProcPara
	{
		ThreadJob* _pObj;//用户对象
		void *_pPara;    //用户参数
		CallProcPara(ThreadJob* p, void *pPara) : _pObj(p), _pPara(pPara) { };  //构造函数初始化列表
	};
	//用户函数结构
	struct JobItem
	{
		void (*_pFunc)(void *);//函数
		void *_pPara;          //参数
		JobItem(void (*pFunc)(void *) = NULL, void *pPara = NULL) : _pFunc(pFunc), _pPara(pPara) { };
	};
	//线程池中的线程结构
	struct ThreadItem
	{
		HANDLE _Handle; //线程句柄
		ThreadPool *_pThis; //线程池的指针
		DWORD _dwLastBeginTime; //最后一次运行开始时间
		DWORD _dwCount; //运行次数
		bool _fIsRunning;
		ThreadItem(ThreadPool *pthis) : _pThis(pthis), _Handle(NULL), _dwLastBeginTime(0), _dwCount(0), _fIsRunning(false) { };
		~ThreadItem()
		{
			if(_Handle)
			{
				CloseHandle(_Handle);
				_Handle = NULL;
			}
		}
	};
	//调用用户对象虚函数(用于在Threadpool行扩展线程对象)
	static void CallProc(void *pPara)
	{
		CallProcPara *cp = static_cast<CallProcPara *>(pPara);
		assert(cp);
		if(cp)
		{
			cp->_pObj->DoJob(cp->_pPara);
			delete cp;
		}
	}
	std::queue<JobItem *> _JobQueue; //用户工作队列
	std::vector<ThreadItem *> _ThreadVector; //线程数据数组
	CRITICAL_SECTION _csThreadVector, _csWorkQueue; //工作队列临界, 线程数据数组临界
	HANDLE _EventEnd, _EventComplete, _SemaphoreCall, _SemaphoreDel;//结束通知, 完成事件, 工作信号, 删除线程信号
	long _lThreadNum, _lRunningNum; //线程数, 运行的线程数

public:
	//dwNum 线程池规模
	ThreadPool(DWORD dwNum = 4) : _lThreadNum(0), _lRunningNum(0)
	{
		InitializeCriticalSection(&_csThreadVector);
		InitializeCriticalSection(&_csWorkQueue);
		_EventComplete = CreateEvent(0, false, false, NULL);//自动设置 初始为无信号
		_EventEnd = CreateEvent(0, true, false, NULL);      //手动设置 初始为无信号
		_SemaphoreCall = CreateSemaphore(0, 0, 0x7FFFFFFF, NULL); //初始为0 最大值为0x7FFFFFFF
		_SemaphoreDel = CreateSemaphore(0, 0, 0x7FFFFFFF, NULL);  //初始为0 最大值为0x7FFFFFFF
		assert(_SemaphoreCall != INVALID_HANDLE_VALUE); //使用断言来确定这些句柄都创建成功了
		assert(_EventComplete != INVALID_HANDLE_VALUE);
		assert(_EventEnd != INVALID_HANDLE_VALUE);
		assert(_SemaphoreDel != INVALID_HANDLE_VALUE);
		AdjustSize(dwNum <= 0 ? 4 : dwNum);
	}
	~ThreadPool()
	{
		DeleteCriticalSection(&_csWorkQueue);
		CloseHandle(_EventEnd);
		CloseHandle(_EventComplete);
		CloseHandle(_SemaphoreCall);
		CloseHandle(_SemaphoreDel);
		vector<ThreadItem*>::iterator iter;
		for(iter = _ThreadVector.begin(); iter != _ThreadVector.end(); iter++)
		{
			if(*iter)
				delete *iter;
		}
		DeleteCriticalSection(&_csThreadVector);
	}
	//调整线程池规模
	int AdjustSize(int iNum)
	{
		if(iNum > 0)
		{
			ThreadItem *pNew;
			EnterCriticalSection(&_csThreadVector);
			for(int _i=0; _i<iNum; _i++)
			{
				_ThreadVector.push_back(pNew = new ThreadItem(this));
				assert(pNew);
				pNew->_Handle = CreateThread(NULL, 0, DefaultJobProc, pNew, 0, NULL);//工作函数为DefaultJobProc,传入参数为pNew
				// set priority
				SetThreadPriority(pNew->_Handle, THREAD_PRIORITY_BELOW_NORMAL);
				assert(pNew->_Handle);
			}
			LeaveCriticalSection(&_csThreadVector);
		}
		else
		{	
			//这种情况在这里貌似是不会发生的???//
			iNum *= -1;
			ReleaseSemaphore(_SemaphoreDel, iNum > _lThreadNum ? _lThreadNum : iNum, NULL);
		}
		return (int)_lThreadNum;
	}
	//实际上最终调用的都是这个函数,它的作用只是把用户作业加入作业队列并把_SemaphoreCall信号量加一
	//调用线程池(方式一:自定义的工作函数)
	void Call(void (*pFunc)(void *), void *pPara = NULL)
	{
		assert(pFunc);
		EnterCriticalSection(&_csWorkQueue);
		_JobQueue.push(new JobItem(pFunc, pPara));
		LeaveCriticalSection(&_csWorkQueue);
		ReleaseSemaphore(_SemaphoreCall, 1, NULL);
	}
	//调用线程池(方式二:从Threadpool上扩展线程对象)
	inline void Call(ThreadJob * p, void *pPara = NULL)
	{
		Call(CallProc, new CallProcPara(p, pPara));
	}
	//结束线程池, 并同步等待
	bool EndAndWait(DWORD dwWaitTime = INFINITE)
	{
		SetEvent(_EventEnd);
		return WaitForSingleObject(_EventComplete, dwWaitTime) == WAIT_OBJECT_0;
	}
	//结束线程池
	inline void End()
	{
		SetEvent(_EventEnd);
	}
	inline DWORD Size()
	{
		return (DWORD)_lThreadNum;
	}
	inline DWORD GetRunningSize()
	{
		return (DWORD)_lRunningNum;
	}
	bool IsRunning()
	{
		return _lRunningNum > 0;
	}
protected:
	//工作线程,用来管理线程池中的线程
	static DWORD WINAPI DefaultJobProc(LPVOID lpParameter = NULL)
	{
		ThreadItem *pThread = static_cast<ThreadItem*>(lpParameter);
		assert(pThread);
		ThreadPool *pThreadPoolObj = pThread->_pThis;
		assert(pThreadPoolObj);
		InterlockedIncrement(&pThreadPoolObj->_lThreadNum);
		HANDLE hWaitHandle[3];
		hWaitHandle[0] = pThreadPoolObj->_SemaphoreCall;
		hWaitHandle[1] = pThreadPoolObj->_SemaphoreDel;
		hWaitHandle[2] = pThreadPoolObj->_EventEnd;
		JobItem * pJob;
		bool fHasJob;
		for(;;)
		{
			DWORD wr = WaitForMultipleObjects(3, hWaitHandle, false, INFINITE);//不是等待所有句柄均有信号
			//响应删除线程信号
			if(wr == WAIT_OBJECT_0 + 1)
				break;
			//从队列里取得用户作业
			EnterCriticalSection(&pThreadPoolObj->_csWorkQueue);
			if(fHasJob = !pThreadPoolObj->_JobQueue.empty())
			{
				pJob = pThreadPoolObj->_JobQueue.front();
				pThreadPoolObj->_JobQueue.pop();
				assert(pJob);
			}
			LeaveCriticalSection(&pThreadPoolObj->_csWorkQueue);
			//受到结束线程信号 确定是否结束线程(结束线程信号 && 是否还有工作)
			if(wr == WAIT_OBJECT_0 + 2 && !fHasJob)
				break;
			if(fHasJob && pJob)
			{
				InterlockedIncrement(&pThreadPoolObj->_lRunningNum);
				pThread->_dwLastBeginTime = GetTickCount();
				pThread->_dwCount++;
				pThread->_fIsRunning = true;
				pJob->_pFunc(pJob->_pPara); //运行用户作业
				delete pJob;
				pThread->_fIsRunning = false;
				InterlockedDecrement(&pThreadPoolObj->_lRunningNum);
			}
		}
		//删除自身结构
		EnterCriticalSection(&pThreadPoolObj->_csThreadVector);
		pThreadPoolObj->_ThreadVector.erase(find(pThreadPoolObj->_ThreadVector.begin(), pThreadPoolObj->_ThreadVector.end(), pThread));
		LeaveCriticalSection(&pThreadPoolObj->_csThreadVector);
		delete pThread;
		InterlockedDecrement(&pThreadPoolObj->_lThreadNum);
		if(!pThreadPoolObj->_lThreadNum) //所有线程结束
			SetEvent(pThreadPoolObj->_EventComplete);
		return 0;
	}
};
#endif 
#include "main.h"
#include <iostream>
using namespace std;

class MyThreadJob:public ThreadJob
{
public:
	virtual void DoJob(void *p)
	{
		int *val=(int *)p;
		cout << "do job" << *val << endl;
		//使用sleep挂起线程,使得工作线程处于保持工作的状态不退出从而查看共有多少个在运行线程
		Sleep(20000);
	}
};

void threadfunc(void *p)
{
	int *val=(int *)p;
	cout << "do job" << *val << endl;
	Sleep(20000);
}

int main()
{
	//使用从Treadpool扩展线程对象的方式传入要执行的任务进行线程池调用
	/*MyThreadJob mt[100];//假设共有一百个客户端有任务需要处理(客户端任务其实就是一个函数和这个函数的参数)
	ThreadPool tp;		//初始化这个线程池,默认有四个线程
	tp.AdjustSize(10);	//向这个线程池中增加十个线程
	for(int i=0;i<100;i++)//调用线程池进行任务处理(即使用线程池中的线程进行客户端任务的执行)
	{
		tp.Call(mt+i,&i);
		//获取当前线程池的大小和当前在运行的线程的个数
		cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
		cout << "the size is:"  << tp.Size() << endl;
		cout << "the running size is: " << tp.GetRunningSize() << endl;
		cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
		Sleep(1000);
	}

	Sleep(5000);
	cout << "****************************************************" << endl;
	cout << "the size is:"  << tp.Size() << endl;
	cout << "the running size is: " << tp.GetRunningSize() << endl;
	cout << "****************************************************" << endl;*/
	
	//使用用户自定义的方式传入要执行的任务进行线程池调用
	ThreadPool tp02;
	tp02.AdjustSize(10);
	for(int i=0;i<100;i++)
	{
		tp02.Call(threadfunc,&i);
		cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
		cout << "the size is:"  << tp02.Size() << endl;
		cout << "the running size is: " << tp02.GetRunningSize() << endl;
		cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl;
		Sleep(1000);
	}

	Sleep(10000);
	return 0;
}

代码解析:

#pragma warning/comment/pack/once
修改编译器编译时候的方式

C++类构造函数初始化列表。
http://www.360doc.com/content/12/0307/08/8484669_192379614.shtml
构造函数初始化列表以一个冒号开始,接着是以逗号分割数据成员列表每个数据成员后面跟一个放在括号中的初始化式。
初始化和复制对内置类型的成员没有什大的区别。对于非内置类型成员变量,为了避免两次构造,推荐哈斯用类构造函数初始化列表。但是有的时候必须使用带有初始化列表的构造函数。第一、成员类型是没有默认构造函数的类。第二、const成员或者引用类型的成员。
初始化数据成员和对数据成员赋值的含义是什么,具体区别是什么?
初始化列表的成员初始化顺序是什么?

如何确定一个资源需不需要进行互斥的临界保护?
只要这个资源是在多线程情况下工作,并且可能被多个线程同时访问操作,就要考虑是否需要进行保护,全部进行保护是最保险的。

线程池代码解析:
线程池中各个成员:
Threadpool是线程池类,负责线程池中线程和工作队列的保存,释放以及调度。
ThreadItem是线程池中保存线程的结构,通过使用线程结构中的参数来实现对线程的管理。
_ThreadVector是一个ThreadItem的vector容器,使用这个数组容器实现线程池中线程维护。
_csThreadVector是用来保护多个线程同时访问_ThreadVector的一个临界段。
JobItem是一个用户函数的结构体,就是需要线程池实际执行的用户任务的函数及参数构成。
_JobQueue是一个JobItem的queue容器,使用这个队列容器进行用户任务的保存和调度。
_csWorkQueue是用来保护多个线程同时访问_JobQueue的一个临界段。
_EventEnd是用来通知要终止线程池运行的事件,手动设置。
_EventComplete是通知线程池中没有待执行任务的时间,自动设置。
_SemaphoreCall是一个标识当前工作队列中有多少个等待工作的信号量
_SemaphoreDel是一个表示要删除线程池中的线程的信号量,在调整线程池线程数时使用。
_lThreadNum和_lRunningNum标识线程池中线程总数和工作线程数。通过InterLockxxx操作。
实现线程池管理的接口:
AjustSize实现线程池中线程个数的调整。
Call实现将用户任务加入任务队列,并将_SemaphoreCall增加1。
EndAndWait实现等待工作任务全部执行结束,并等待所有线程池中线程都已被删除时返回
End实现在没有工作任务的时候可以停止线程池的功能,不会等待。
Size获取线程池的规模
getRunningSize获取线程池中正在工作的线程个数
IsRunning判断线程池是否有线程正在工作
线程池的线程管理函数:
DefaultJobProc实现了线程池中各个线程具体工作流程的管理。就是在这里可以实现线程池可以在有任务到来的时候唤醒线程进行工作,当任务数大于线程池规模的时候就会进行等待,一旦有线程空闲下来就会去等待队列中取任务进行执行的功能。
任务接口:
ThreadJob类中的DoJob接口,线程池的线程必须要独立于用户的具体任务,即可以执行用户传入的任何任务,从而实现线程池与具体的任务无关。这样我们级将线程执行的任务抽象出来,这里抽象为ThreadJob的纯虚类,所有用户任务都要从这个虚类继承,并重写这个DoJob接口作为用户需要执行的具体任务函数。
在这个线程池的实现中,我们可以有两种做法来使用线程池。第一种就是通过继承ThreadJob并重写DoJob接口,然后使用Call传入任务对象和执行参数。第二种方法就是也可以自己重写一个用户自定义函数void threadfunc(void *p),直接使用Call将这个自定义函数和执行参数传入线程池执行。
这是因为在我们这个线程池的实现中,重载了线程池的Call接口,使得我们可以选择使用从ThreadJob扩展线程对象的方式调用线程池还是使用用户自定义的函数的方式调用线程池。个人觉得这点设计非常巧妙



 

将Lambda表达式作为参数传递并解析——在构造函数参数列表中使用Lambda表达式(C#)

话不多说,先上代码: public class DemoClass { /// /// 通过Lambda表达式,在构造函数中赋初始值 /// /// /...
  • honantic
  • honantic
  • 2016年03月02日 11:13
  • 21757

初始化与清理(构造函数初始化,可变参数列表,enum)

练习2:创建一个类,它包含一个在定义时就被初始化了的String域,以及另一个通过构造器初始化的String域。这两种方式有何差异? s1初始化了1次。s2初始化了2次。...

JavaScript深入理解函数参数列表及“不存在重载”

函数的参数JS和其他大多数语言一个较为明显的区别就在于函数参数的处理上。因为在JS中调用函数的时候,传入的参数数据类型是可以不固定的,个数也无所谓多少个。听起来很奇怪,实际上,JS中的参数在内部是用一...

函数参数列表为空和void的区别

在C/C++的学习过程中,一个很常见的问题就是void main和int main有什么区别呢?本文试图回答该问题,并涉及一些相关问题,包括一些main函数的其他实现。   首要说明的一点是这些(主...

C/C++函数参数列表变量的计算顺序

首先从一道2016校招阿里巴巴C++工程师的笔试题说起:函数B(int a, int b) {cout }我们想当然的认为,结果应该是  1 , 0  可是,阿里真的会考这么简单的问题吗?真确答案让人...

反射获取构造函数参数,枚举小例子,常量池

getConstructor(String.class,int.class)中的参数是怎么确定?为什么就是String.class和int.class,参数类型为什么又写成(String.class,...

Javascript基础知识(三):函数参数(传参)

1.函数参数分类及使用上一篇博客已经讲到函数参数有实参和形参两种。 函数参数使用时需要注意以下几点:1.如果形参有两个赋值,而实参只给了一个值,那么就要把这个值赋予第一个形参.第二个形参没有赋值。 ...

C++函数参数的基础知识

》参数是函数间进行数据交换的主要方式。在C++中,函数之间传递参数有传值和传地址两种传递方式。此外,C++还提供默认参数机制,可以简化复杂函数的调用。 >传值         传值是将实参...

C#基础知识系列五(构造函数)

前言   本节主要来学习下实例构造函数、私有构造函数和静态构造函数。   构造函数的作用:   1、当一个类在new实例化对象时,可调用构造函数进行初始化对象。   2、不带参...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Window线程池实现和解析、#pragma和构造函数参数列表基础知识
举报原因:
原因补充:

(最多只允许输入30个字)