Click here to Skip to main content
15,898,134 members
Articles / Desktop Programming / MFC

The Practical Guide to Multithreading - Part 2

Rate me:
Please Sign up or sign in to vote.
4.96/5 (119 votes)
12 Apr 2010CPOL43 min read 150.9K   5K   316  
More of practical situations to use multithreading!
// PrimeNumberSearchPage.cpp : implementation file
//

#include "stdafx.h"
#include "ParallelSearch_MFC.h"
#include "PrimeNumberSearchPage.h"

#include "RelationalSearchPage.h"

// For sqrt
#include <math.h>


// CPrimeNumberSearchPage dialog

IMPLEMENT_DYNAMIC(CPrimeNumberSearchPage, CPropertyPage)

CPrimeNumberSearchPage::CPrimeNumberSearchPage()
	: CPropertyPage(CPrimeNumberSearchPage::IDD)
{

}

CPrimeNumberSearchPage::~CPrimeNumberSearchPage()
{
}

void CPrimeNumberSearchPage::DoDataExchange(CDataExchange* pDX)
{
	CPropertyPage::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_SEARCH_RESULT, m_cSearchResult);
	DDX_Control(pDX, IDC_STATUS, m_cStatus);
}


BEGIN_MESSAGE_MAP(CPrimeNumberSearchPage, CPropertyPage)
	ON_BN_CLICKED(IDOK, &CPrimeNumberSearchPage::OnBnClickedOk)
	ON_MESSAGE(WM_SEARCH_REPLY_MESSAGE, OnSearchReplyMessage)
	ON_NOTIFY(LVN_GETDISPINFO, IDC_SEARCH_RESULT, &CPrimeNumberSearchPage::OnLvnGetdispinfoSearchResult)
END_MESSAGE_MAP()


// CPrimeNumberSearchPage message handlers


int CPrimeNumberSearchPage::OnInitDialog()
{
	CPropertyPage::OnInitDialog();

	// Set it as default for 'threads-for-search'
	SetDlgItemInt(IDC_THREAD_COUNT, theApp.m_nProcessorCount);

	m_cSearchResult.InsertColumn(0, _T("From Thread"), LVCFMT_LEFT, 90);
	m_cSearchResult.InsertColumn(1, _T("Number"), LVCFMT_LEFT,156);

	m_cSearchResult.SetExtendedStyle(LVS_EX_FULLROWSELECT);

	// Set it as default for 'threads-for-search'
	SetDlgItemInt(IDC_THREAD_COUNT, theApp.m_nProcessorCount);

	EnableWindow(true);

	return 0;
}

bool IsPrimeNumber(int nNumber);

UINT PrimeNumberSearchThread(void* pArg)
{
	SearchInfo* pSearchInfo = static_cast<SearchInfo*> ( pArg );

	// From where to search (just put the address, instead of index)
	int* pNumberBegin =  m_gNumberCollection.m_pNumbers + pSearchInfo->LowIndex;
	// &m_gNumberCollection.m_pRandomNumbers[pSearchInfo->LowIndex]

	// Till where?
	int* pNumberEnd=	m_gNumberCollection.m_pNumbers+ pSearchInfo->HighIndex;
	// &m_gNumberCollection.m_pRandomNumbers[pSearchInfo->HighIndex]

	const int nGreaterThan = pSearchInfo->NumberToSearch;


	int nCurrentNumber;

	NumberCollection* pNumberCollection = new NumberCollection(pSearchInfo->NotifyThreshold);
	int nCount=0;	// Current found count, will be reset after posting


	while (pNumberBegin < pNumberEnd)
	{
		nCurrentNumber = *pNumberBegin;
		pNumberBegin++;

		// Discard less-than required 'minimum'	and even numbers.

		if( nCurrentNumber%2==0 || nCurrentNumber <= nGreaterThan  )
			continue;

		if(IsPrimeNumber(nCurrentNumber))
			//nCurrentNumber < nNumberToSearch)
		{
			pNumberCollection->m_pNumbers[nCount++] = nCurrentNumber;

			if(nCount == pSearchInfo->NotifyThreshold)
			{
				pSearchInfo->m_pWnd->PostMessage(WM_SEARCH_REPLY_MESSAGE,
					(WPARAM)pNumberCollection,
					pSearchInfo->ThreadID);


				pNumberCollection  = new NumberCollection(pSearchInfo->NotifyThreshold);


				nCount=0;
			}
		}
	}

	// Send whatever collected by this time.
	// But send the ThreadID to be negative, so that GUI-thread knows
	// this thread has finished.
	// Other ideas exists for letting thread know about 'completion'
	// this is one of them (though a dirty one). Take it! :)

	// Send correct count, ignore rest of values in pointer.
	pNumberCollection->m_nCount = nCount;

	pSearchInfo->m_pWnd->PostMessage(WM_SEARCH_REPLY_MESSAGE,
		(WPARAM)pNumberCollection,
		-pSearchInfo->ThreadID);		// Note the negative sign

	delete pSearchInfo;

	return 0;
}

LRESULT CPrimeNumberSearchPage::OnSearchReplyMessage(WPARAM wParam, LPARAM lParam)
{
	CString strItem;
	CString strText;
	NumberCollection* pNumbers = (NumberCollection*)wParam;

	int nThreadId = (int)lParam;

	bool bIsSearchFinished=false;

	// If nThread ID was send negative, it means thread has finished.
	if(nThreadId < 0)
	{
		nThreadId = -nThreadId;
		m_nThreadFinishedCount++;

		if(m_nThreadFinishedCount == m_nThreadCount)
		{
			EndTime = GetTickCount();
			bIsSearchFinished = true;
		}
	}


	ItemData item_data;
	item_data.ThreadId = static_cast<BYTE> ( nThreadId );


	for (int nItem = 0; nItem<pNumbers->m_nCount;++nItem)
	{
		// Just put into vector
		// this is needed for fast response
		item_data.Number = pNumbers->m_pNumbers[nItem];
		ItemDataVector.push_back(item_data);			
	}

	// Update virtual list control
	// The callback would display
	m_cSearchResult.SetItemCount( (int) ItemDataVector.size() );


	if(bIsSearchFinished)
	{
		EnableControls(true);
		strText.Format(_T("Search finished with %d results in %.2f seconds"),  
			ItemDataVector.size(), 
			(EndTime - StartTime) / 1000.0f );
		m_cStatus.SetWindowText( strText);

		GetDlgItem(IDC_GREATER_THAN)->SetFocus();
	}


	delete pNumbers;

	return 0;
}

void CPrimeNumberSearchPage::OnBnClickedOk()
{
	// Just validate threads-count
	m_nThreadCount = GetDlgItemInt(IDC_THREAD_COUNT);
	const int nGreaterThan= GetDlgItemInt(IDC_GREATER_THAN);

	if(m_nThreadCount<1 || m_nThreadCount>MAX_THREAD_COUNT)
	{
		AfxMessageBox( _T("Please set thread-count be in 1 to 32 range."));
		return;
	}

	if(nGreaterThan < 10)
	{
		AfxMessageBox( _T("Please specify value more than 10"));
		return;

	}

	EnableControls(false);
	ItemDataVector.clear();
	m_cSearchResult.SetItemCount(0);

	m_nThreadFinishedCount=0;

	int nStartIndex = 0 ;


	int nBreakAt = m_gNumberCollection.m_nCount / m_nThreadCount;

	// First END index.
	int nEndIndex = 0;

	StartTime = GetTickCount();

	// Spawn up the number of threads, and let them know the range.
	// Range: low and high index in Number-array where the thread has to lookup
	for(int nThread = 1; nThread <= m_nThreadCount; nThread++)
	{
		// Since int is 4-byte, we cannot pass two integers (low and high index values)
		// The size of pointer (void*) is 4-bytes, thus we must pass this way.
		// The 0th index will be array-index from where the thread would begin search
		// The 1st index is the last-index PLUS 1 till where thread should search.
		// So 100,200 would mean: 100-199 indexes

		// Yes, we can also pass the addresses within the number-array
		// instead of indexes. But this is easier to understand.

		// The thread would delete this pointer.

		SearchInfo* pSearchInfo = new SearchInfo;
		pSearchInfo->NotifyThreshold = 1000;

		// Utilize NumberToSearch member to let thread know of 'minimum' number
		pSearchInfo->NumberToSearch = nGreaterThan;

		// pSearchInfo->m_pCompFunc = pFunctionAddress;

		// Calculated EndIndex...
		// If this is last thread to be created.
		// It MUST Handle all remaining items.
		// For example, if there are 100 items to search
		// And user specified 8 threads, the first seven threads would 
		// handle 12 items to search, but the last has to search 16 items.
		// Please calculate and understand yourself!
		if(nThread == m_nThreadCount )
			nEndIndex = m_gNumberCollection.m_nCount;
		else
			nEndIndex += nBreakAt;


		pSearchInfo->ThreadID = static_cast<short>  (nThread);

		pSearchInfo ->m_pWnd = this;		// To notify
		pSearchInfo->LowIndex	= nStartIndex;		// LOW-INDEX
		pSearchInfo ->HighIndex 	= nEndIndex;	// HIGH-INDEX (excluding the index itself).	

		// Next start index. Remember that High/End index means last-index to search PLUS ONE.
		nStartIndex = nEndIndex;


		AfxBeginThread(PrimeNumberSearchThread, pSearchInfo);
	}

	m_cStatus.SetWindowText( _T("Searching..."));
}

void CPrimeNumberSearchPage::EnableControls(bool bEnable)
{
	GetDlgItem(IDOK)->EnableWindow(bEnable);
	// m_cRelationalOperatorCombo.EnableWindow(bEnable);

	GetDlgItem(IDC_THREAD_COUNT)->EnableWindow(bEnable);
	GetDlgItem(IDC_GREATER_THAN)->EnableWindow(bEnable);
}


// Prime Number function
bool IsPrimeNumber(int nNumber)
{
	// As controlled in calling program, nNumber cannot be EVEN or less than 10
	// So we can start off with 3 till square-root of number, incrementing by 2
	// Any number can ONLY be divisible in range: 1 to sqrt(n)
	// Formulas exist for more optimal solution, but would add more complexity.
	// I chose this one.

	const int nCheckTill = static_cast<int> ( sqrt((float)nNumber) );

	for(int nCheckWith = 3; nCheckWith <= nCheckTill; ++nCheckWith)
	{
		if(nNumber % nCheckWith == 0)
			return false;
	}
	
	// Reached here, that means number is prime
	return true;
}

// The callback for displaying content to list-control
// This would be called whenever some item content is to be displayed
// We kept data in vector, so just returning them.
// This list-control must have OWNER DATA style set.
void CPrimeNumberSearchPage::OnLvnGetdispinfoSearchResult(NMHDR *pNMHDR, LRESULT *pResult)
{
	NMLVDISPINFO *pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
	
	LV_ITEM& Item= pDispInfo->item;

	if (Item.mask & LVIF_TEXT)
	{
		CString text;

		//Which column?
		switch (Item.iSubItem)
		{
		case 0: // Location name
			text.Format( _T("%d"), ItemDataVector[Item.iItem].ThreadId);
			break;

		case 1:	// STD Code
			text.Format( _T("%d"), ItemDataVector[Item.iItem].Number );
			break;		
		}	

		//Copy the text to the LV_ITEM structure
		//Maximum number of characters is in pItem->cchTextMax
		wcsncpy(Item.pszText, text, Item.cchTextMax);
	}

	*pResult = 0;
}

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