Click to See Complete Forum and Search --> : where to start?
jim enright
April 26th, 2007, 10:32 AM
looks like i have to get nto multi threading. appreciate any suggestions
on articles/links to get me going in the right direction.
some example of a two threaded app might be a good place to start.
thanks - j
Arjay
April 26th, 2007, 11:33 AM
Check out the articles listed in my signature line. They discussed the need for thread synchronization with shared variables and offer threading/sync techniques using RAII.
abcdefgqwerty
April 26th, 2007, 02:09 PM
Theres lots of research papers on synchronization objects and the problems of multithreading. When you sit down to actually make a program that is multi threaded you will see how big of a royal pain it really is. It easily makes a program many times more complex.
Arjay
April 26th, 2007, 08:33 PM
Theres lots of research papers on synchronization objects and the problems of multithreading. When you sit down to actually make a program that is multi threaded you will see how big of a royal pain it really is. It easily makes a program many times more complex.I disagree, it's really not that hard. Perhaps on a research level covering ALL the possibilities, but no so hard on a practical level encountering most real world scenarios.
There are really only a few of things to consider for the basic scenarios:
1) Protect any resources between threads with a synchronization object (cs, mutex, event, semaphore, etc.).
2) Signal the threads exit using an event rather than TerminateThread.
3) Clean up resources/handles after threads exit
4) Make synchronization tasks simpler by using RAII techniques.
Here's an example of two threads sharing a CString where the first thread changes the string while the second string sits in a loop and displays the string to the console (btw, without synchronization the string most likely would become corrupted).
The main thread first creates the secondary thread and then sits in a loop to execute the following code.
//
// Simple string assignment routine that copies a string in
// the main thread while a secondary thread prints the screen
// to the console.
void CMyClass::AssignString( LPCTSTR szDisplay )
{
// Lock the CLockableCS object to prevent the secondary
// thread access to the string resource
// NOTE: CAutoLockT will automatically unlock the lock
// object on method exit
CAutoLockT< CLockableCS > lock( &m_csStrLock );
// Assign the new string
m_sDisplay = szDisplay;
// End of autolock scope (m_csStrLock auto-unlocked here)
}
While the code above is executing in the main thread, the following code executes in the secondary thread. NOTE: on multi-core or multi-proc machines, the two code snippets will execute the code simulaneously (and will try to access the shared m_sDisplay string simultaneously as well).
//
// Secondary Display Thread - it's job in life is to simply
// output the string to the console
//
UINT WINAPI DisplayThread( )
{
int nLineNumber = 1;
while( TRUE )
{
{ // Autolock scope
// Prevent access to the string from our primary thread
// while we lock to display the string
CAutoLockT< CLockableCS > lock( &m_csStrLock );
// Print the string out to the console
std::cout
<< setiosflags( std::ios::right )
<< std::setw(3)
<< nLineNumber++
<< _T(") ")
<< m_sDisplay
<< std::endl;
} // End of autolock scope (m_csStrLock auto-unlocked here)
// Check if the shutdown event has been set; if so exit the thread
if( WAIT_OBJECT_0 == WaitForSingleObject( m_hShutdownEvent, 0 ) )
{
return 0;
}
}
// In this example, we never reach here
return 1;
}
Notice in the snippets above, there aren't any explicit locking/unlocking calls (such as EnterCriticalSection or LeaveCriticalSection api calls). This is because the locks are handled by the RAII-styled CAutoLockT class. This class autolocks the m_csStrLock critical section wrapper class and unlocks it when it (i.e. CAutoLockT) class goes out of scope.
This might seem like a very trivial example (and it is), but most complex threading scenarios can be decomposed into simple fundamentals. You could have other threads accessing the same resource say printing output to multiple files and their thread procs would look very similar (except instead of printing to the console they would print to files).
A couple of other things that are going on.
Exit Threads Cleanly.
When the main thread wants the secondary thread to exit it would call SetEvent on the m_hShutdownEvent which would cause the DisplayThread to exit (after checking this event with WaitForSingleObject). This is a preferred method for getting a secondary thread to exit by using an event in this manner.
Always release the synchronization object (no matter what).
This code kind of hides this, but by using the RAII technique (via the CAutoLockT locking class), we are assured that the critical section will always get released. Changes are, we aren't going to encounter an exception will reading or assigning the CString, but if we did the CAutoLockT class would unlock the string when the stack unwinds. That's a good thing because, it simplifies error handling and means we don't need explicit unlock code (this can be tedious when not using exception handling and need to make explicit LeaveCriticalSection calls)
JVene
April 27th, 2007, 01:38 AM
Sorry abcdefgqwerty, but I have to second Arjay on this excellent description.
I do agree with you, abcdefgqwerty, in that if Arjay's point regarding RAII is not take into account, and if the programmer isn't familiar with threading (through successful experience), it can be quite a pain.
It can be a considerable study just to learn how to launch a thread and use it as a queue, or have it wait for some dedicated task until signaled, synchronizing with locks and releases, learning about the need for the volatile keyword, or the value of smart pointers applicable to threaded work. When it's unfamiliar, that's what creates a mess.
Without objects to assist, threading can generate odd bugs, too. In my view, without objects, threading would have to be managed with C style coding constructs, and that leaves one open to bugs - made quite hard to track down when dealing with multiple threads. It also means there would be no leverage to the concepts involved in threaded development.
However, with objects to assist, threading can be reduced to a nearly trivial implementation, and many problems do break down into threading to good effect, even leverage. The key is to consider a framework that handles the issues around threaded development, based on practiced and proven techniques. Otherwise, Arjay's point breaks down into the chaos you've described - because the programmer is re-solving the issues in threading with each application.
To have, for example, an object that represents a thread of execution, such that simply instantiating that object means a thread is launched and waiting for something to do, is a huge relief over C oriented techniques. Right there I've saved myself 30 minutes of potential haggling over getting that rolling.
Synchronizing the conclusion of a problem broken into threads is another issue that, without objects, is quite a puzzle to solve. Say I'm going to process an image and I know the algorithm functions just as well if I have 4 threads run through the image in 4 'bands' - dividing the image into 4 groups of lines. One thread, which is probably the thread upon which the call to this process was made, will schedule 3 others to run 3 sections while it takes the 4th. When this executive thread is finished, it must wait, if necessary, for the other 3 threads to be done before it returns - so that application code is written as if this were a single, blocking call - and upon return, the job is known to be done (as long as errors didn't come up).
To do that involves a fair amount of planning, and a host of debugging issues could come up. The division of the task into 4 groups is all but trivial compared to this synchronization work. An object, on the other hand, used to schedule the 3 background threads can do it in such a way that the work is as trivial as creating the object on the stack, using it to initiate the call(s), and when the executive thread's code block is about to exit, the destructor of this synchronizing object 'holds' the thread in a wait state until the other 3 threads indicate they're done (or something went wrong, or time expired, etc).
A framework for threaded development isn't a trivial undertaking, and frankly I'm not entirely aware of any (though I've heard there are some). Framework, in this context, doesn't suggest an alternative to MFC, WFC or other GUI, but a framework specifically dealing with threads, locks and task oriented issues.
I must admit it was long ago that I built mine. At the time threading was brand new to Windows, but we had it in OS/2 a bit earlier. When I first began to implement threading in applications, it WAS difficult and buggy, which is exactly why I developed a framework for it. At the risk of drifting on a tangent (if you know me, that's not uncommon :) ) - I believe the major focus in OOP development is on that point. Once you solve a problem, express it in generic terms and keep the solutions in a library, and therefore all that time in debugging, and all that history of proof through deployment, that follows you throughout your career.
abcdefgqwerty
April 27th, 2007, 09:08 AM
Making something like that or a trivial program is one thing. Making a huge hundred thousand line program all multithreaded with no race conditions, no deadlock, no speed problems with locking, and with multiple people working on different pieces is a totally different story and what Im talking about. Threads make huge programs very much harder. Now try getting 8 threads like on an xbox 360 to all be actively running on different modules at the same time to maximize all the cpu cores. I promise you thats not as easy as you think it is even if you make some thread workload balancer or something. Although I will admit with languages that hide most of the details like java or C# where you just slap down a mutex object it is simpler then it used to be but most of the logic still needs to be thought about.
Arjay
April 27th, 2007, 01:22 PM
The thing is, is that C# or Java really doesn't help you much in the way that JVene and I are describing. Sure they offer some sync object wrappers, but what is important is how the code is structured that allow you to make use of the built-in features. Leveraging OOP and exception handling as JVene describes will go a long way toward avoiding the common threading pitfalls. On the other hand, if the program isn't structured correctly (say it uses a bunch of global variables shared between threads and/or explicit lock/unlock calls and no exception handling), then it will be very difficult to sort out (and quite fragile as well).
Just like a generic list implementation shouldn't ever be concerned with the type of object it contains or the number of objects, a threading implementation shouldn't be concerned whether 2 threads or 10 threads are accessing a shared resource.
With regard to the threading framework that JVene mentioned. I don't have a framework per se, but I have built some wrappers over the sync primitives and have a class that uses RAII to lock/unlock them. What I do do is follow the same 'division of responsibility' practice as JVene with regard to having a class create/manage any threads it needs (and utilize the wrappers I've created). The class is responsible for creating/sync shared data/pausing/stopping and cleaning up any thread resources. This encapsulation allows any users of the class not to be aware that the class itself contains threads and is performing synchronization.
Such a simple concept, and if you look at the source code of a complex app with multiple threads that structures the code in this manner, you may expect to see locking/unlocking calls all over the place, but in reality these sync calls end up in very few places. It's almost as if the code is single threaded.
jim enright
April 27th, 2007, 01:23 PM
appreciate the feed back. will have a simple use of threading:
have two buttons on a dialog box : Launch and Kill.
Launch initiates a double loop:
k=0;
for (i=0;i<200;i++){
for (i=0;i<5000;i++){
GetProblemParameters(k);
SolveTheProblem();
k++;
}
DetectIfKillHasBeenClicked();
}
Kill allows a graceful interruption of the process.
JVene
April 27th, 2007, 01:36 PM
Hey abcdefgqwerty,
Let me open this reply with a preface. First, I don't want to sound argumentative here - it's offered in the spirit of debate in order to inform the visiting public and participants here.
Second, there's a wide audience range here. There are students (and sometimes I have to be reminded there are 16-18 year olds with questions), there are grad students (I've chatted with a PhD candidate in a serious work), and there are professionals of considerable experience. Many of us in that latter group have 'seen the world' so to speak.
With that, let me reply directly to your point.
I promise you thats not as easy as you think it is even if you make some thread workload balancer or something.
...and again, with no 'flaming' intent here....
I promise you that it IS made simple, and I've done it for many years, in dozens of projects of considerable ambition - teams of dozens of developers, millions of lines of code, in projects that stretch back 10 years.
Without an object framework (and here I refer back to Arjay's reply - even a few simple classes help tremendously) - you're correct - it's a nightmare.
Objects change the entire picture, and not just 100%.
I would NOT have wanted a team working on threaded development of such complexity without objects. I'd have been required to put 1/3 of the team just on that subject.
I just want to underline that I'm not describing a theory, and I'm not proceeding on assumption. I'm providing feedback on work that spans over a decade (with threading - development history goes back 25 years). There's no doubt - threading can be done in such a way that the chaotic mess you describe, though quite a real danger (I'm in agreement with you there) can be so mitigated as to be reduced to as minor an issue as vectors and maps.
Arjay
April 27th, 2007, 01:51 PM
So, if you are asking how to get started on this, I would create a class that contains the thread management functionality (creation, starting, stopping, cleanup, and the thread proc that performs the work). This class would also contain the interfaces to hook up with the UI to start, stop, and/or pause the thread. It would also contain method(s) to extract any info to be displayed to the UI.
You would then add a member of this class to your Dialog and wire up the OnStart, OnStop handlers.
The following is a code snippet from the LVSelState sample included in this post (http://www.codeguru.com/forum/showthread.php?t=329464). This code pretty much covers the techniques we're talking about here (and adds a few UI niceties).
This code was mainly to illustrate using a 'virtual' listctrl that can display up to a 1,000,000 items in the list. To make the sample more interesting, I populate the list in a separate thread (the data needs to be syncronized because the user could repopulate the list while the UI is repainting). Anyway, if you set the spin control to add 100,000 items, click retrieve and immediately click Cancel, you will cause the thread that is populating the list to exit.
The code that deals with the threading is contained within the following three calls (see below):
m_LVItemDataMgr.Cancel( );
m_LVItemDataMgr.Retrieve( m_uMaxCount );
m_LVItemDataMgr.Populate( m_ctlList );
Because the data needs to be synchronized between the filling thread and the UI thread, I chose to pass the list control ( m_ctlList ) to the populate method, so that internally it can perform the synchronization while it populates the list.
Other than the three calls made above, the other code in the button handlers are fluff that shows progress to the user, disables buttons, etc.