上一篇文章windows编程 使用C++实现多线程类仅仅是介绍了怎样用类来实现多线程,这篇文章则重点介绍多线程中数据同步的问题。好了,废话不多说,进入主题。
问题场景:这里我们假设有这样一个工作流水线(CWorkPipeline),它不断的生成一个SquareInfo的对象,这个对象包含x和y坐标,同时包括一个未得到结果的平方和(squareSum),这些流水线类似于现实世界的工厂不断产出产品(SquareInfo对象),很多个这样的流水线把产生的SquareInfo对象汇集给处理中心。
处理中心(CProcessCenter)是一个多线程类,它不断接收流水线上的数据,并计算每个SquareInfo对象的squareSum,同时把对应的信息打印出来。
我们看CWorkPipeline的定义:
/************************************************************************/
/* FileName: WorkPipeline.h
* Date: 2015-5-13
* Author: huangtianba
* Description: 模拟工作流水线类
*/
/************************************************************************/
#pragma once
#include
#include
struct SquareInfo
{
int x;
int y;
int squareSum;
};
typedef std::tr1::function FoundItemFn;
class CWorkPipeline
{
public:
CWorkPipeline();
~CWorkPipeline();
bool Init(const FoundItemFn &foundItemFn);
bool UnInit();
static DWORD CALLBACK WorkThread(LPVOID lpParam);
DWORD WorkProc();
private:
HANDLE m_hThread;
FoundItemFn m_foundItemFn;
};
这里需要注意的是Init()函数接受一个FoundItemFn的函数对象,这个对象是CProcessCenter出给它的回调,用来接收流水线产生的SquareInfo对象。
下面是CWorkPipeline类各个函数的实现:
/************************************************************************/
/* FileName: WorkPipeline.cpp
* Date: 2015-5-14
* Author: chenzba
* Description:
*/
/************************************************************************/
#include "stdafx.h"
#include "WorkPipeline.h"
#include
CWorkPipeline::CWorkPipeline(): m_hThread(NULL)
{
}
CWorkPipeline::~CWorkPipeline()
{
}
bool CWorkPipeline::Init(const FoundItemFn &foundItemFn)
{
srand(unsigned int(time(NULL)));
// 创建线程
m_hThread = CreateThread(NULL, 0, &CWorkPipeline::WorkThread, this, CREATE_SUSPENDED, NULL);
if (NULL == m_hThread) {
return false;
}
m_foundItemFn = foundItemFn;
ResumeThread(m_hThread);
return true;
}
bool CWorkPipeline::UnInit()
{
if (m_hThread != NULL)
{
CloseHandle(m_hThread);
m_hThread = NULL;
}
m_foundItemFn = FoundItemFn();
return true;
}
DWORD CALLBACK CWorkPipeline::WorkThread(LPVOID lpParam)
{
if (NULL == lpParam) {
return 0;
}
CWorkPipeline *lpThis = reinterpret_cast (lpParam);
return lpThis->WorkProc();
}
// 线程处理函数
DWORD CWorkPipeline::WorkProc()
{
while (true)
{
// 声明一个SquareInfo对象,给x和y随机赋值
SquareInfo squareInfo = {};
squareInfo.x = rand() % 10000;
squareInfo.y = rand() % 10000;
// 将squreInfo通知给回调
m_foundItemFn(squareInfo);
// Sleep一段时间
Sleep(max(20, rand() % 2000));
}
return 0;
}
这里值得注意的一点是:UnInit()函数中必须将m_foundItemFn对象置为空对象,假如不这样做,如果CProcessCenter对象已经从栈释放,而流水线线程还没退出,继续调用m_foundItemFn(squareInfo)将导致不可预料的错误。
下面看看CProcessCenter类的定义:
/************************************************************************/
/* FileName: ProcessCenter.h
* Date: 2015-5-14
* Author: chenzba
* Description: 数据处理汇集中心,处理多个线程汇集过来的数据
*/
/************************************************************************/
#pragma once
#include
#include
#include
#include
struct SquareInfo;
class CWorkPipeline;
class CProcessCenter
{
public:
typedef ATL::CComAutoCriticalSection DataLock;
public:
CProcessCenter();
~CProcessCenter();
bool Init();
bool UnInit();
void DoWork();
static DWORD CALLBACK ProcessThread(LPVOID lpParam);
DWORD ProcessProc();
//
// 被CWorkPileline回调的函数
//
void NotifyFoundItem(const SquareInfo &squareInfo);
//
// 通知线程退出
//
void NotifyExit();
void ProcessData();
private:
std::vector m_workLineList; // 工作流水线线程列表
std::list m_dataList; // 流水线产生的数据列表
DataLock m_dataLock; // m_dataList数据锁
HANDLE m_hThread;
ATL::CEvent m_pipeEvent; // 通知处理数据事件
bool m_isExit;
};
/************************************************************************/
/* FileName: ProcessCenter.cpp
* Date: 2015-5-14
* Author: chenzba
* Description: The implement of CProcessCenter class.
*/
/************************************************************************/
#include "stdafx.h"
#include "ProcessCenter.h"
#include "WorkPipeline.h"
#include
#include
#include "../boost/tr1/functional.hpp"
CProcessCenter::CProcessCenter(): m_hThread(NULL), m_isExit(false)
{
}
CProcessCenter::~CProcessCenter()
{
}
bool CProcessCenter::Init()
{
// 创建事件
BOOL created = m_pipeEvent.Create(NULL, TRUE, FALSE, NULL);
if (!created) {
return false;
}
m_hThread = CreateThread(NULL, 0, &CProcessCenter::ProcessThread, this, CREATE_SUSPENDED, NULL);
if (NULL == m_hThread)
{
UnInit();
return false;
}
// 创建10个工作流水线
for (int i = 0; i < 10; i++)
{
CWorkPipeline *pipeLine = new(std::nothrow) CWorkPipeline();
if (pipeLine != NULL)
{
pipeLine->Init(std::tr1::bind(&CProcessCenter::NotifyFoundItem, this, std::tr1::placeholders::_1));
m_workLineList.push_back(pipeLine);
}
}
m_isExit = false;
ResumeThread(m_hThread);
return true;
}
bool CProcessCenter::UnInit()
{
// 释放流水线资源
std::vector::iterator it;
for (it = m_workLineList.begin(); it != m_workLineList.end(); it++)
{
if ((*it) != NULL)
{
(*it)->UnInit();
delete (*it);
(*it) = NULL;
}
}
m_workLineList.clear();
if (m_pipeEvent != NULL) {
m_pipeEvent.Set();
}
if (m_hThread != NULL)
{
WaitForSingleObject(m_hThread, 100);
CloseHandle(m_hThread);
m_hThread = NULL;
}
m_pipeEvent.Close();
m_isExit = true;
return true;
}
要说明一点,
std::tr1::bind(&CProcessCenter::NotifyFoundItem, this, std::tr1::placeholders::_1)
使用bind方法产生一个函数对象,有些人编译的时候可能找不到bind函数。如果编译环境支持c++11(vs2012以上),bind函数属于c++标准库,直接在std::中就能找到;但是在vs2008或者一下,不支持c++11的编译环境(也就是我现在写代码的环境),bind函数是属于boost库的函数。Boost库可以跟C++标准库完美共同工作,并且为其提供扩展功能,因此,这里需要引用Boost库,相关库大家可以到网上了解相关信息。
下面我们继续看NotifyFoundItem函数的实现:
void CProcessCenter::NotifyFoundItem(const SquareInfo &squareInfo)
{
// 数据同步 加锁
CCritSecLock locker(m_dataLock.m_sec);
m_dataList.push_back(squareInfo);
m_pipeEvent.Set();
}
我们回到CWorkPipeline中的线程函数,m_foundItemFn(squareInfo),这句代码就进入NotifyFoundItem函数,在这里找个函数被10线程调用,m_dataList是共享数据,这里给m_dataList加锁。下面继续,看看加锁的原因。
DWORD CALLBACK CProcessCenter::ProcessThread(LPVOID lpParam)
{
if (NULL == lpParam) {
return 0;
}
CProcessCenter *lpThis = reinterpret_cast (lpParam);
return lpThis->ProcessProc();
}
DWORD CProcessCenter::ProcessProc()
{
while (true)
{
DWORD dwResult = WaitForSingleObject(m_pipeEvent, 2000);
if (dwResult == WAIT_TIMEOUT && (!m_isExit)) {
continue;
}
// 处理数据
ProcessData();
if (m_isExit) {
break;
}
}
return 0;
}
void CProcessCenter::ProcessData()
{
std::list tempList;
m_dataLock.Lock();
tempList = m_dataList;
m_dataList.clear();
m_pipeEvent.Reset();
m_dataLock.Unlock();
int counter = 0;
std::list::iterator it;
for (it = tempList.begin(); it != tempList.end(); it++)
{
counter++;
(*it).squareSum = (*it).x * (*it).x + (*it).y * (*it).y;
std::cout <<"x: " <<(*it).x <<" y: " <<(*it).y <<" square sum: " <<(*it).squareSum <
void CProcessCenter::NotifyExit()
{
m_isExit = true;
}
void CProcessCenter::DoWork()
{
Sleep(20000);
}
ProcessData声明一个tempList临时对象,然后把m_dataList的值赋给tempList,最后把m_dataList清空。
首先我们先探讨线程同步问题,加入不给m_dataList加锁,当线程执行到tempList = m_dataList之后,另外一个CWorkPipeline线程获得cpu时间片,执行m_dataList.push(),这时候m_dataList新增一个未处理的数据,接下来返回到tempList = m_dataList后,执行m_dataList.clear(),这样新增的未处理的数据就丢失了,因此这里必须给m_dataList上锁,保证任何时刻只有一个线程获得m_dataList的使用权限。
这里有人可能会疑问,为什么要另外声明一个tempList对象呢?为什么不直接使用m_dataList用于计算呢?
原因是,在多线程同步中,我们为了提高程序的执行效率,尽可能的让线程获得共享数据(m_dataList)的使用权限时间最短,假如我们直接使用m_dataList计算数据,那么我们必须对一大堆计算逻辑的代码进行加锁或者在每次访问m_dataList的时候加锁,这样效率是相当慢的。我想大家应该很容易理解明白。
好了,多线程就暂时讲到这里,语言冗余,请大家多多包涵,也希望大家多多指正其中可能的错误。