1.继续线程池
1.1 将线程池中任务队列使用的STL容器更改为自己写的容器
1.我们自己定义一个循环队列,用这个循环队列来存储任务指针,起始时定义总长度为最多可存储元素个数+1(多出来的一个元素不做存储),头指针指向-1位置,尾指针指向0位置,向链表中添加元素则将尾指针 ++ ,当存满时(即尾指针指向最后一个元素),将尾指针重置为0;向链表中删除元素时,将头指针 ++ ;
2.添加一个循环队列类: class CQueue ;向类中添加成员变量: int m_nBegin (头指针); int m_nEnd (尾指针); int m_nSize (容器中的元素个数); CTask** m_pArrTask (存储任务的指针数组);(本次使用int类型来表示头尾指针,因为定义的任务队列是一个指针数组,可以直接通过索引下标来进行任务指针的查找);
3.在构造函数中初始化: m_nBegin = -1 ; m_nEnd = 0 ; m_nSize = 0 ; m_pArrTask = new CTask*[SIZE_BUFFER] ; ::ZeroMemory(m_pArrTask, SIZE_BUFFERsizeof(CTask)) (SIZE_BUFFER 是定义的宏,定义为 1000+1);
4.在析构函数中重置,并删除指针数组: m_nBegin = -1 ; m_nEnd = 0 ; m_nSize = 0 ; delete[] m_pArrTask ; m_pArrTask = 0 ;
5.添加成员函数: bool my_push(CTask* pTask) (添加元素)、 void my_pop() (删除元素)、 bool my_isempty() (是否为空)、 CTask* my_front() (取头元素);
6.在添加元素函数中,首先判断输入参数是否是空或者队列是否满了,若是则返回false;通过下标索引添加元素: m_pArrTask[m_nEnd] = pTask ;将 m_nSize 与 m_nEnd 进行自加操作;再判断自加后的 m_nEnd 是否超过了最大存储范围;判断是否是第一次添加元素,若是则将 m_nBegin 加 1 ;
7.在删除元素函数中,首先判断队列中有没有元素,没有的话需要将 m_nBegin 和 m_nEnd 都重置为初始值;在头部删除: m_pArrTask[m_nBegin] = 0 ;将 m_nBegin 减 1 ,将 m_nSize 加 1 ;再判断加 1 后的 m_nBegin 是否超出了队列的最大存储范围,超出了的话就将其置为 0 ;
8.判断是否为空函数,直接判断 m_nSize 的值即可;
9.取头元素的函数,直接将 m_nBegin 位置的元素返回即可;
// 用自己写的容器来替换STL容器
#define SIZE_BUFFER 1000+1
class CQueue
{
private:
int m_nBegin; // 头尾
int m_nEnd;
int m_nSize;
CTask** m_pArrTask; // 任务数组
public:
CQueue()
{
m_nBegin = -1; // 头尾
m_nEnd = 0;
m_nSize = 0;
m_pArrTask = new CTask*[SIZE_BUFFER]; // 任务数组
::ZeroMemory(m_pArrTask, SIZE_BUFFER*sizeof(CTask*));
}
~CQueue()
{
m_nBegin = -1; // 头尾
m_nEnd = 0;
m_nSize = 0;
delete[] m_pArrTask;
m_pArrTask = 0;
}
// 添加元素
bool my_push(CTask* pTask)
{
// 判断队列是不是满了
if(pTask == 0 || m_nSize == SIZE_BUFFER-1)
{
return false;
}
// 添加元素
m_pArrTask[m_nEnd] = pTask;
m_nSize++;
m_nEnd++;
// 判断有没有超出范围
if(m_nEnd >= SIZE_BUFFER)
m_nEnd = 0;
// 判断是不是第一个元素,是的话需要将begin++
if(m_nBegin == -1)
m_nBegin++;
return true;
}
// 删除元素
void my_pop()
{
// 若队列中没有东西,则重置begin和end
if(m_nSize == 0)
{
m_nBegin = -1;
m_nEnd = 0;
return;
}
// 在头部删除
m_pArrTask[m_nBegin] = 0;
m_nBegin++;
m_nSize--;
// 判断有没有超出范围
if(m_nBegin == SIZE_BUFFER)
m_nBegin = 0;
}
// 判断是否为空
bool my_isempty()
{
if(m_nSize == 0)
return true;
return false;
}
// 取头元素
CTask* my_front()
{
return m_pArrTask[m_nBegin];
}
};
1.2 进行调用
1.将 CMyThreadPool 类中的任务队列成员变量更改为 CQueue m_quTask ;
2.在投递任务函数 CMyThreadPool::PushTask 中将压任务入任务队列的相关操作更改为新写的 CQueue 类的函数;
3.将线程处理函数 CMyThreadPool::ThreadProc 中有关任务队列的操作也更改为新写的 CQueue 类的函数;
// 投递任务
bool CMyThreadPool::PushTask(CTask* p_task)
{
// 先判断给进来的任务是不是空
if(p_task == NULL)
return false;
// 把任务放到任务队列中
m_lockList.MyLock();
// 需要加一个判断来让用户知道是否将任务压入了队列
if(m_quTask.my_push(p_task) == false)
{
m_lockList.MyUnlock();
return false;
}
// 看看有没有空闲的线程
if(m_dwRunThreadCount < m_dwCurrentThreadCount)
{
// 有空闲的线程则给其释放一个信号量
::ReleaseSemaphore(m_hSemaphoreRun, 1, 0);
}
else if(m_dwCurrentThreadCount < m_dwMaxThreadCount)
{
// 没有空闲的线程,但是可以继续创建
HANDLE h_thread = (HANDLE)_beginthreadex(0, 0, &CMyThreadPool::ThreadProc, this, 0, 0);
if(h_thread != 0)
{
m_lsThread.push_back(h_thread);
m_dwCurrentThreadCount++;
// 释放信号让其开始执行
::ReleaseSemaphore(m_hSemaphoreRun, 1, 0);
}
}
else
{
// 没有其他情况了
}
m_lockList.MyUnlock();
return true;
}
// 线程处理函数
unsigned int _stdcall CMyThreadPool::ThreadProc( void * pVoid)
{
CMyThreadPool* p_this = (CMyThreadPool*)pVoid;
while (p_this->m_bQuitThread)
{
if(::WaitForSingleObject(p_this->m_hSemaphoreRun, 10) == WAIT_TIMEOUT)
continue;
// 每运行一个线程就自加一次
// ::InterlockedIncrement(&(p_this->m_dwRunThreadCount));
p_this->m_lockList.MyLock();
p_this->m_dwRunThreadCount++;
p_this->m_lockList.MyUnlock();
// 不停得在任务队列中取任务出来执行
while (1)
{
CTask* p_task;
p_this->m_lockList.MyLock();
if(p_this->m_quTask.my_isempty() == true)
{
p_this->m_lockList.MyUnlock();
break;
}
p_task = p_this->m_quTask.my_front();
p_this->m_quTask.my_pop();
p_this->m_lockList.MyUnlock();
p_task->RunTask();
delete p_task;
p_task = 0;
::Sleep(0); // 降低CPU的占有率
}
// 每退出一个线程就自减一次
// ::InterlockedDecrement(&(p_this->m_dwRunThreadCount));
p_this->m_lockList.MyLock();
p_this->m_dwRunThreadCount--;
p_this->m_lockList.MyUnlock();
}
return 0;
}
2.使用线程池来完成之前 Clean 项目的搜索文件任务
2.1 给 TabCtrl 控件上的第四个按钮 扫描删除 中添加一个新的对话框
1.新建一个对话框,在其上添加两个按钮 创建线程池 以及 搜索 按钮;
2.更改对话框的 Border 属性修改为 None (无边框), Style 属性修改为 Child (内建于主窗口中);
3.给新对话框添加类 class CDialogThreadPool : public CDialogEx ;
4.在 CMyTabCtrl 类中添加新添加的对话框的对象: CDialogThreadPool m_dlgTP ;
5.在 CMyTabCtrl::InitTabCtrl 函数中对 m_dlgTP 进行显示: m_dlgTP.Create(IDD_THREADPOOL,this) ; m_dlgTP.ShowWindow(SW_HIDE) ; m_dlgTP.MoveWindow(&rect) ;
6.在点击 Tab 列表时选择控制显示的对话框;
// CDialogThreadPool 对话框
class CDialogThreadPool : public CDialogEx
{
DECLARE_DYNAMIC(CDialogThreadPool)
public:
CDialogThreadPool(CWnd* pParent = NULL); // 标准构造函数
virtual ~CDialogThreadPool();
// 对话框数据
enum { IDD = IDD_THREADPOOL };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButton1();
afx_msg void OnBnClickedButton2();
};
// CMyTabCtrl 初始化函数
void CMyTabCtrl::InitTabCtrl()
{
... ...
// 线程池对话框
m_dlgTP.Create(IDD_THREADPOOL,this);
m_dlgTP.ShowWindow(SW_HIDE);
... ...
m_dlgTP.MoveWindow(&rect);
}
2.2 完成创建线程池按钮
1.将线程池项目中的 MyThreadPool.h 文件与 MyThreadPool.cpp 文件拷贝到Clean项目下,并将这两个文件添加到Clean项目中;
2.在类 CDialogThreadPool 中定义一个线程池对象 CMyThreadPool m_thread_pool ;
3.在按钮 创建线程池 的处理函数中创建线程池: m_thread_pool.CreateThreadPool() ;
void CDialogThreadPool::OnBnClickedButton1()
{
// 创建线程池
if(m_thread_pool.CreateThreadPool() == true)
MessageBox(_T("创建成功"), _T("提示"), MB_OK);
else
MessageBox(_T("创建失败"), _T("提示"), MB_OK);
}
2.3 完成 搜索 按钮
1.在搜索按钮中,我们首先确定哪些任务需要多任务来完成:基本就是在主窗口中搜索按钮中,那些需要从列表中取下来的路径,然后将队列更改为向线程池中push任务可以用线程池来完成;
2.创建一个 CTask 的子类: class CSearchTask : public CTask ,用来作为本次的搜索任务,其中有一个 CString m_strPath 成员,用来作为搜索的初始路径,在初始化函数 InitTask 被赋值;
3.首先获取 CCleanDlg 句柄,因为需要其中的控件对象: CCleanDlg* p_cleanDlg = (CCleanDlg*)theApp.m_pMainWnd ;
4.将标签中CShowDlg里的列表项的文本取下来,判断该项是否被选中,若选中则创建一个 CSearchTask 指针用来作为要存入线程池中的任务指针;
// 定义一个任务子类
class CSearchTask : public CTask
{
private:
CString m_strPath;
public:
void InitTask(CString strPath)
{
m_strPath = strPath;
}
virtual void RunTask();
};
void CDialogThreadPool::OnBnClickedButton2()
{
// 在搜索按钮中,我们首先确定哪些任务需要多任务来完成
// 首先就是在主窗口中搜索按钮中,那些需要从列表中取下来的路径,然后将队列更改为向线程池中push任务
// 取下来 标签中CShowDlg里的列表项的文本
CCleanDlg* p_cleanDlg = (CCleanDlg*)theApp.m_pMainWnd;
for(int i=0;i<p_cleanDlg->m_tab.m_dlgShow.m_lcShowPath.GetItemCount();i++)
{
// 判断是不是选中的
if(p_cleanDlg->m_tab.m_dlgShow.m_lcShowPath.GetCheck(i) == TRUE)
{
// 取得文本
CString strPath = p_cleanDlg->m_tab.m_dlgShow.m_lcShowPath.GetItemText(i,0);
// 将选中的路径添加到搜索任务中
CSearchTask* pTask = new CSearchTask;
pTask->InitTask(strPath);
if(m_thread_pool.PushTask(pTask) == false)
delete pTask;
}
}
}
2.4 完成类 CSearchTask 中的虚函数
1.该函数中完成的即之前写过的在 CCleanDlg::OnBnClickedButton1 中完成过的功能:给定 strPath 进行搜索相关的后缀名,若匹配则存储起来;
2.由于下列大量代码需要用到 CCleanDlg 中的对象,因此先定义一个 CCleanDlg 指针;
3.在 m_strPath 路径下开始查找具有相同后缀名的文件;
4.若获得的是文件夹,则将该文件夹继续作为一个任务压入线程池中;若是文件则与之前代码大体相同;
void CSearchTask::RunTask()
{
// 由于下列代码是从之前写的 搜索 按钮中拷贝下来的,因此部分代码需要用到主窗口句柄
CCleanDlg* p_cleanDlg = (CCleanDlg*)theApp.m_pMainWnd;
// 以下代码为拷贝的 CCleanDlg::OnBnClickedButton1 中的代码
// 在这个路径下查找文件
CFileFind file;
BOOL bflag = file.FindFile(m_strPath+"\\*.*");
while(bflag)
{
bflag = file.FindNextFile();
// 过滤 . 和 .. 文件
if(file.IsDots() == TRUE)
continue;
// 获取名字
CString strFileName = file.GetFileName();
// 判断 是文件夹,则继续将当前文件夹的路径作为参数创建一个任务,并将该任务压入线程池中
if(file.IsDirectory() == TRUE)
{
// 和 从队列取出来的文件夹的路径拼接 放回到队列
CSearchTask* pTask = new CSearchTask;
pTask->InitTask(m_strPath+'\\'+strFileName);
if(p_cleanDlg->m_tab.m_dlgTP.m_thread_pool.PushTask(pTask) == false)
delete pTask;
}
else // 是文件
{
// 截取后缀名
int index = strFileName.ReverseFind('.');
if(index != -1)
{
CString strExName = strFileName.Right(strFileName.GetLength()-index);
// 和CSetDlg 里装后缀名的集合匹配
if(p_cleanDlg->m_tab.m_dlgSet.m_stExName.count(strExName) == 1)
{
// 匹配成功 把这个文件的路径 插入到m_lcDeletPath 列表
p_cleanDlg->m_lcDeletPath.InsertItem(
p_cleanDlg->m_lcDeletPath.GetItemCount(),
m_strPath+'\\'+strFileName);
}
}
}
}
}
3.编程过程中出现的错误
3.1 出现如下错误
error C2146:语法错误,缺少“;”,(在标识符 “m_tab” 的前面)
error C4430:缺少类型说明符-假定为int。注意:C++不支持默认int
1.出现上述的原因我以为是缺少 “;” 导致的,但是在我检查过之后发现并不是,因为我创建 DialogThreadPool 类的时候,可以将这个新建的对话框显示出来,当我完成整个程序时就出现了上述错误,所以不是缺少 “;” 的问题;再检查了检查头文件,发现我在 DialogThreadPool 类的头文件以及源文件中都包含了头文件 CCleanDlg.h ,那么说明上述错误是重复引用所导致的,将其中一个删除之后,程序正常运行。