Click here to Skip to main content
15,884,628 members
Articles / Desktop Programming / Win32

Concurrency Runtime in Visual C++ 2010

Rate me:
Please Sign up or sign in to vote.
4.98/5 (60 votes)
11 Nov 2010CPOL49 min read 147.3K   2.2K   164  
Learn about parallel algorithms, parallel containers, tasks, task groups, agents library, task scheduler etc in VC10
// Copyright (C) 2010
// Ajay Vijayvargiya

// This source code was published on CodeProject.com, under CPOL license
// This code CANNOT be used for similar publication on the web or as printed material.
// This can, however, be used as educational reference in educational institutions.
// You are allowed to use the accompanying code in your programs, 
//    commercial or non commercial. 

// This source code is only intended to explicate the Concurrency Runtime in Visual C++ 2010,
// and thus may contain bugs, some logical issues etc.
// The explanation is relevant to Visual C++ 2010 (Compiler version: 16.0)

// http://www.codeproject.com/KB/cpp/parallelcpp.aspx


// For performance benchmarking, simple approach, GetTickCount is used.
// If you prefer, you may use other high performance counters.
// Ignore the importance and non-optimized code, they are just for illustration.



#include "stdafx.h"
#include "CR MFC App.h"
#include "CR MFC AppDlg.h"
#include "afxdialogex.h"
#include <ppl.h>
#include <vector>
#include <algorithm>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


#define EMPTY_STRING  _T("")



bool IsPrimeNumber(int nNumber)
{
	bool bIsPrime = true;
	for(int i = 3; i <= (int)sqrt((float)nNumber); i+=2)
	{
		if(nNumber % i == 0)
		{
			bIsPrime = false;
			break;           
		}
	}

	return bIsPrime;
}




// CCRMFCAppDlg dialog
CCRMFCAppDlg::CCRMFCAppDlg(CWnd* pParent /*=NULL*/)
	: CDialogEx(CCRMFCAppDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CCRMFCAppDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_SUM_TO, m_cSumTill);
	DDX_Control(pDX, IDC_OUT_LIST, m_cOutList);
	DDX_Control(pDX, IDC_PRIME_TO, m_cPrimeTill);
}

BEGIN_MESSAGE_MAP(CCRMFCAppDlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BTN_SUM, &CCRMFCAppDlg::OnBnClickedBtnSum)
	ON_EN_SETFOCUS(IDC_SUM_TO, &CCRMFCAppDlg::OnEnSetfocusSumTo)
	ON_EN_SETFOCUS(IDC_PRIME_TO, &CCRMFCAppDlg::OnEnSetfocusPrimeTo)
	ON_BN_CLICKED(IDC_BTN_PRIME, &CCRMFCAppDlg::OnBnClickedBtnPrime)
	ON_BN_CLICKED(IDC_BTN_TASKS, &CCRMFCAppDlg::OnBnClickedBtnTasks)
END_MESSAGE_MAP()


// CCRMFCAppDlg message handlers
BOOL CCRMFCAppDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here

	return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CCRMFCAppDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CCRMFCAppDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}










void CCRMFCAppDlg::OnBnClickedBtnSum()
{	
	EnableControls(FALSE);

	// Designating the "sum" task in different thread
	// has no correlation with CR. It is just to make sure
	// that UI gets updated on time, and UI doesn't freeze.
	AfxBeginThread(PerformParallelSum, this);
}


UINT CCRMFCAppDlg::PerformParallelSum(void* pParam)
{
	CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);
		
	pDlg ->ParallelSumProc();

	return 0;
}


/***************************************************************************/
/* Function: ParallelSum                                                   */
/***************************************************************************/
/* Demonstrates parallel_for algorithm  and the combinable class           */
/* It should be noted that each iteration should do something specific,    */
/* and not trivial thing like adding the number only. When each iteration. */
/* performs something, it gives performance benefits over serial execution */
/* That's why I sum only number divisible by some number. Had it been just */
/* summation of all numbers, serial version would perform faster, since    */
/* combinable::local function would use more of CPU than actual sum.       */
/***************************************************************************/

#define DIVISIBLE_BY	7

void CCRMFCAppDlg::ParallelSumProc()
{
	using namespace Concurrency;
	using namespace std;


	LONGLONG nSumTill;

	TCHAR strSumTill[64 + 1];

	// Can only be number (See resource for IDC_SUM_TO)
	m_cSumTill.GetWindowText(strSumTill, 64);

	// atoi -> atoi64 -> _wtoi64
	// _ttoi64 is just macro for string to __int64 conversion

	nSumTill = _ttoi64(strSumTill);
	
	CString strOutput;

	unsigned __int64 nSum;

	combinable<LONGLONG> Sum;

	// This object is just placed to demonstrate more of combinable class.
	combinable<vector<LONGLONG>> VectorOfDivisibles;

	vector<LONGLONG> Dummy;

	DWORD nTickStart, nTickEnd;

	// Clear the output first
	m_cOutList.ResetContent();

	m_cOutList.AddString(L"Accumulating serially (check that only 1 CPU-core is utilized)");;
	nTickStart = GetTickCount();

	// Execute serially
	nSum = 0;
	for ( LONGLONG nValue = 1; nValue <= nSumTill; ++nValue)
	{
		if ( (nValue % DIVISIBLE_BY) == 0)
		{
			nSum += nValue;
			Dummy.push_back(nValue);	// Put just to make sure both loops perform same work.
		}
	}

	nTickEnd = GetTickCount();
		
	strOutput.Format(L"The sum is: %I64d",  nSum);
	m_cOutList.AddString(strOutput);

	strOutput.Format(L"Which took %d ms", (nTickEnd - nTickStart));
	m_cOutList.AddString(strOutput);

	strOutput.Format(_T("Total numbers: %u"), Dummy.size());
	m_cOutList.AddString(strOutput);


	m_cOutList.AddString(EMPTY_STRING);
	m_cOutList.AddString(_T("\nAccumulating parallelly (Multiple cores are being utilized)"));
	nTickStart = GetTickCount();

	// Execute parallelly	
	parallel_for( (LONGLONG)1, nSumTill+1, [&](LONGLONG nValue)
	{
		if ( (nValue % DIVISIBLE_BY) == 0)
		{
			Sum.local() += nValue;

			// Add this number to 'this' thread-specific vector.
			VectorOfDivisibles.local().push_back(nValue);
		}		
	});

	int nNumberCount = 0 ;
	VectorOfDivisibles.combine_each([&nNumberCount](const vector<LONGLONG>& long_vector)
	{
		nNumberCount += long_vector.size();
	});

	nTickEnd = GetTickCount();

	ULONGLONG nCombineSum = Sum.combine(plus<ULONGLONG>());

	strOutput.Format( _T("The sum is: %I64d"), nCombineSum);;
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Which took %d ms"), (nTickEnd - nTickStart));
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Total numbers: %u"),  nNumberCount);	
	m_cOutList.AddString(strOutput);


	EnableControls(TRUE);	
}








void CCRMFCAppDlg::OnBnClickedBtnPrime()
{
	EnableControls(FALSE);

	// Designating the "prime find" task in different thread
	// has no correlation with CR. It is just to make sure
	// that UI gets updated on time, and UI doesn't freeze.
	AfxBeginThread(PerformParallelPrimeFind, this);
}

UINT CCRMFCAppDlg::PerformParallelPrimeFind(void* pParam)
{
	CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);

	pDlg ->ParallelPrimeFindProc();

	return 0;
}



/****************************************************************************/
/* Function: ParallelPrimeFind                                              */
/****************************************************************************/
/* Demonstrates parallel_for_each algorithm  and the combinable class       */
/* This function first generates few numbers (1 to ELEMENT_COUNT_FOR_PRIME) */
/* Then it shuffles the numbers.                                            */
/* For performance benchmarking of serial and parallel executions, it finds */
/* "count" of prime numbers in this range. Timing of both computation is    */
/* rendered to console.														*/
/****************************************************************************/

void CCRMFCAppDlg::ParallelPrimeFindProc()
{
	using namespace Concurrency;
	using namespace std;


	int nPrimeTill;

	TCHAR strSumTill[64 + 1];

	// Can only be number (See resource for IDC_SUM_TO)
	m_cPrimeTill.GetWindowText(strSumTill, 64);

	nPrimeTill = _ttoi(strSumTill);


	vector<int> Numbers;
	combinable<int> Primes;
	int nPrimes;

	DWORD nTickStart, nTickEnd;

	CString strOutput;

	m_cOutList.ResetContent();

	// Allocate vector
	Numbers.resize(nPrimeTill);

	// Generate incrementing numbers
	int nCounter = 1;
	generate(Numbers.begin(), Numbers.end(), [&nCounter] 
	{
		return nCounter++;
	});

	// Shuffle the vector
	random_shuffle(Numbers.begin(), Numbers.end());


	m_cOutList.AddString( _T("Finding primes serially...") );

	// Find the prime numbers (count), serially.
	// The lambda counts them.
	nTickStart = GetTickCount();

	nPrimes = 0;
	for_each(Numbers.begin(), Numbers.end(), [&nPrimes](int nNumber)
	{
		if(IsPrimeNumber(nNumber))
			nPrimes++;
	});

	nTickEnd = GetTickCount();

	strOutput.Format( _T("\nPrimes found: %d") ,nPrimes);
	m_cOutList.AddString( strOutput );

	strOutput.Format(_T("Which took %d ms") , (nTickEnd - nTickStart) );
	m_cOutList.AddString( strOutput );




	m_cOutList.AddString(EMPTY_STRING);
	m_cOutList.AddString( _T("Finding primes parallelly..."));

	// Find the prime numbers (count), serially.
	// The lambda counts them.
	nTickStart = GetTickCount();


	parallel_for_each(Numbers.begin(), Numbers.end(), [&Primes](int nNumber)
	{
		if(IsPrimeNumber(nNumber))
		{
			Primes.local()++;
		}
	});

	nTickEnd = GetTickCount();

	nPrimes = Primes.combine(plus<int>());

	strOutput.Format( _T("\nPrimes found: %d") ,nPrimes);
	m_cOutList.AddString( strOutput );

	strOutput.Format(_T("Which took %d ms") , (nTickEnd - nTickStart) );
	m_cOutList.AddString( strOutput );


	EnableControls(TRUE);
}








void CCRMFCAppDlg::OnBnClickedBtnTasks()
{
	EnableControls(FALSE);

	// Designating the tasks in different thread
	// has no correlation with CR. It is just to make sure
	// that UI gets updated on time, and UI doesn't freeze.
	AfxBeginThread(PerformParallelInvoke, this);
}




// 5 million / 50 lacs
#define ELEMENT_COUNT_PARALLEL_INVOKE	5000000

// 500 thousand / 5 lacs
#define ELEMENT_COUNT_FOR_PRIME		500000

UINT CCRMFCAppDlg::PerformParallelInvoke(void* pParam)
{
	CCRMFCAppDlg* pDlg = static_cast<CCRMFCAppDlg*>(pParam);

	pDlg ->ParallelInvokeProc();

	return 0;
}




/*****************************************************************************/
/* Function: ParallelInvoke				                                    **/
/*****************************************************************************/
/* It runs three tasks/function in serial and then parallel, and displays the*/
/* time taken.																 */
/* The first two routines, finds the count of even/odd number, and puts those*/
/* number in string format in a vector. Converting and inserting into vector */
/* is just to make sure that each function "does something" CPU intensive.   */
/* Since, I did not use sleep, UI, file-IO or other external factors, doing  */
/* was only choice.															 */
/* This third routine does the same with prime numbers.						 */
/*****************************************************************************/

void CCRMFCAppDlg::ParallelInvokeProc()
{
	using namespace Concurrency;
	using namespace std;

	CString strOutput; 

	ULONGLONG nEvenSum, nOddSum, nPrimeSum;

	vector<string> Evens, Primes, Odds;
	DWORD nTickStart, nTickEnd;

	// Let's define few lambdas locally and store them into 'auto' variables

	//.....//
	auto EvenAccumulator = [&nEvenSum, &Evens]
	{
		nEvenSum = 0;
		char sBuffer[64];	// Just a placeholder		

		// Non optimized way to sum...
		for (int nValue = 1; nValue <= ELEMENT_COUNT_PARALLEL_INVOKE; ++nValue)
		{
			if ( nValue%2 == 0 )
			{
				nEvenSum += nValue;

				sprintf_s(sBuffer, "%d", nValue);				
				Evens.push_back(sBuffer);			
			}
		}
	};

	auto OddAccumulator = [&nOddSum, &Odds]
	{
		nOddSum = 0;
		char sBuffer[64];	// Just a placeholder

		// Non optimized way to sum...
		for (int nValue = 1; nValue <= ELEMENT_COUNT_PARALLEL_INVOKE; ++nValue)
		{
			if ( nValue%2 != 0 )
			{
				nOddSum += nValue;

				sprintf_s(sBuffer, "%d", nValue);				
				Odds.push_back(sBuffer);
			}
		}		
	};

	auto PrimesCounter= [&Primes, &nPrimeSum]
	{	
		char sBuffer[64];	// Just a placeholder

		// Non optimized way to sum...
		for (int nValue = 1; nValue <= ELEMENT_COUNT_FOR_PRIME; ++nValue)
		{
			if( IsPrimeNumber(nValue) )
			{
				nPrimeSum += nValue;

				sprintf_s(sBuffer, "%d", nValue);				
				Primes.push_back(sBuffer);
			}
		}		
	};
	//.....//


	m_cOutList.ResetContent();

	// Do it serially...
	m_cOutList.AddString(_T("\nExecuting serially..."));
	m_cOutList.AddString(EMPTY_STRING);
	nTickStart = GetTickCount();

	EvenAccumulator();
	OddAccumulator();
	PrimesCounter();

	nTickEnd = GetTickCount();

	strOutput.Format( _T("Serial execution took %d ms") ,(nTickEnd - nTickStart));
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Even sum is: %I64d"), nEvenSum);
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Odd sum is: %I64d"), nOddSum);
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Primes count is: %d"), Primes.size());
	m_cOutList.AddString(strOutput);
	m_cOutList.AddString(EMPTY_STRING);


	Primes.clear();
	Odds.clear();
	Evens.clear();

	
	// Do it parallelly 
	m_cOutList.AddString(_T("\nExecuting parallelly..."));;
	m_cOutList.AddString(EMPTY_STRING);
	
	nTickStart = GetTickCount();

	parallel_invoke(
		EvenAccumulator,
		OddAccumulator,
		PrimesCounter);

	nTickEnd = GetTickCount();


	strOutput.Format( _T("Parallel execution took %d ms") ,(nTickEnd - nTickStart));
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Even sum is: %I64d"), nEvenSum);
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Odd sum is: %I64d"), nOddSum);
	m_cOutList.AddString(strOutput);

	strOutput.Format( _T("Primes count is: %d"), Primes.size());
	m_cOutList.AddString(strOutput);


	EnableControls(TRUE);
}






void CCRMFCAppDlg::EnableControls( BOOL bEnable )
{
	GetDlgItem(IDC_BTN_SUM)->EnableWindow(bEnable);	
	//GetDlgItem(IDC_SUM_TO)->EnableWindow(bEnable);

	GetDlgItem(IDC_BTN_PRIME)->EnableWindow(bEnable);
	//GetDlgItem(IDC_PRIME_TO)->EnableWindow(bEnable);

	GetDlgItem(IDC_BTN_TASKS)->EnableWindow(bEnable);
}


void CCRMFCAppDlg::OnEnSetfocusSumTo()
{
	// Set my buddy button to be default, when I have focus!
	SetDefID(IDC_BTN_SUM);
}


void CCRMFCAppDlg::OnEnSetfocusPrimeTo()
{
	// Set my buddy button to be default, when I have focus!
	SetDefID(IDC_BTN_PRIME);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Started programming with GwBasic back in 1996 (Those lovely days!). Found the hidden talent!

Touched COBOL and Quick Basic for a while.

Finally learned C and C++ entirely on my own, and fell in love with C++, still in love! Began with Turbo C 2.0/3.0, then to VC6 for 4 years! Finally on VC2008/2010.

I enjoy programming, mostly the system programming, but the UI is always on top of MFC! Quite experienced on other environments and platforms, but I prefer Visual C++. Zeal to learn, and to share!

Comments and Discussions