8

Click here to load reader

Multi Threading Tutorial - C

  • Upload
    abasak

  • View
    339

  • Download
    3

Embed Size (px)

Citation preview

Page 1: Multi Threading Tutorial - C

7,248,533 members and growing! (18,507 online) Sign out

threads

Abhishek Basak

30 Article Browse Code Stats Revisions

» General Programming » Threads, Processes & IPC » Multi-threading

Multithreading TutorialBy John Kopplin | 28 Dec 2006

This article demonstrates how to write a multithreaded Windowsprogram in C++ using only the Win32 API.

Download source and demo projects - 425 KB

Background

When you run two programs on an Operating System that offers memory protection, as Windows andUNIX/Linux do, the two programs are executed as separate processes, which means they are givenseparate address spaces. This means that when program #1 modifies the address 0x800A 1234 in itsmemory space, program #2 does not see any change in the contents of its memory at address 0x800A1234. With simpler Operating Systems that cannot accomplish this separation of processes, a faultyprogram can bring down not only itself but other programs running on that computer (including theOperating System itself).

The ability to execute more than one process at a time is known as multi-processing. A processconsists of a program (usually called the application) whose statements are performed in anindependent memory area. There is a program counter that remembers which statement should beexecuted next, and there is a stack which holds the arguments passed to functions as well as thevariables local to functions, and there is a heap which holds the remaining memory requirements of theprogram. The heap is used for the memory allocations that must persist longer than the lifetime of asingle function. In the C language, you use malloc to acquire memory from the heap, and in C++, you

use the new keyword.

Sometimes, it is useful to arrange for two or more processes to work together to accomplish one goal.One situation where this is beneficial is where the computer's hardware offers multiple processors. Inthe old days this meant two sockets on the motherboard, each populated with an expensive Xeon chip.Thanks to advances in VLSI integration, these two processor chips can now fit in a single package.Examples are Intel's "Core Duo" and AMD's "Athlon 64 X2". If you want to keep two microprocessorsbusy working on a single goal, you basically have two choices:

design your program to use multiple processes (which usually means multiple programs), or1.

design your program to use multiple threads.2.

So, what's a thread? A thread is another mechanism for splitting the workload into separate executionstreams. A thread is lighter weight than a process. This means, it offers less flexibility than a full blownprocess, but can be initiated faster because there is less for the Operating System to set up. What'smissing? The separate address space is what is missing. When a program consists of two or morethreads, all the threads share a single memory space. If one thread modifies the contents of the address0x800A 1234, then all the other threads immediately see a change in the contents of their address0x800A 1234. Furthermore, all the threads share a single heap. If one thread allocates (via malloc ornew) all of the memory available in the heap, then attempts at additional allocations by the other

threads will fail.

But each thread is given its own stack. This means, thread #1 can be callingFunctionWhichComputesALot() at the same time that thread #2 is calling

FunctionWhichDrawsOnTheScreen(). Both of these functions were written in the same program.

There is only one program. But, there are independent threads of execution running through thatprogram.

What's the advantage? Well, if your computer's hardware offers two processors, then two threads canrun simultaneously. And even on a uni-processor, multi-threading can offer an advantage. Mostprograms can't perform very many statements before they need to access the hard disk. This is a veryslow operation, and hence the Operating System puts the program to sleep during the wait. In fact, theOperating System assigns the computer's hardware resources to somebody else's program during the

4.40 / 5, 41 votes

Home Articles Questions & Answers Learning Zones Jobs Features Help! Lounge

Sponsored Links

See Also...

Announcements

The Daily Insider

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

1 of 8 8/14/2010 2:49 AM

Page 2: Multi Threading Tutorial - C

wait. But, if you have written a multi-threaded program, then when one of your threads stalls, yourother threads can continue.

The Jaeschke Magazine Articles

One good way to learn any new programming concept is to study other people's code. You can findsource code in magazine articles, and posted on the Internet at sites such as CodeProject. I came acrosssome good examples of multi-threaded programs in two articles written for the C/C++ Users Journal, byRex Jaeschke. In the October 2005 issue, Jaeschke wrote an article entitled "C++/CLI Threading: Part1", and in the November 2005 issue, he wrote his follow-up article entitled "C++/CLI Threading: Part2". Unfortunately, the C/C++ Users Journal magazine folded shortly after these articles appeared. But,the original articles and Jaeschke's source code are still available at the following websites:

Part 1Part 2

You'll notice that the content from the defunct C/C++ Users Journal has been integrated into the Dr.Dobb's Portal website, which is associated with Dr. Dobb's Journal, another excellent programmingmagazine.

You might not be familiar with the notation C++/CLI. This stands for "C++ Common LanguageInfrastructure" and is a Microsoft invention. You're probably familiar with Java and C#, which are twolanguages that offer managed code where the Operating System rather than the programmer isresponsible for deallocating all memory allocations made from the heap. C++/CLI is Microsoft's proposalto add managed code to the C++ language.

I am not a fan of this approach, so I wasn't very interested in Jaeschke's original source code. I am sureJava and C# are going to hang around, but C++/CLI attempts to add so many new notations (andconcepts) on top of C++, which is already a very complicated language, that I think this language willdisappear.

But, I still read the original C/C++ Users Journal article and thought Jaeschke had selected goodexamples of multi-threading. I especially liked how his example programs were short and yet displayeddata corruption when run without the synchronization methods that are required for successfulcommunication between threads. So, I sat down and rewrote his programs in standard C++. This is whatI am sharing with you now. The source code I present could also be written in standard C. In fact, that'seasier than accomplishing it in C++ for a reason we will get to in just a minute.

This is probably the right time to read Jaeschke's original articles, since I don't plan to repeat his greatexplanations of multitasking, reentrancy, atomicity, etc. For example, I don't plan to explain how aprogram is given its first thread automatically and all additional threads must be created by explicitactions by the program (oops). The URLs where you can find Jaeschke's two articles are given above.

Creating Threads Under Windows

It is unfortunate that the C++ language didn't standardize the method for creating threads. Therefore,various compiler vendors invented their own solutions. If you are writing a program to run underWindows, then you will want to use the Win32 API to create your threads. This is what I willdemonstrate. The Win32 API offers the following function to create a new thread:

Collapse

uintptr_t _beginthread( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist );

This function signature might look intimidating, but using it is easy. The _beginthread() function

takes three passed parameters. The first is the name of the function which you want the new thread tobegin executing. This is called the thread's entry-point-function. You get to write this function, andthe only requirements are that it take a single passed parameter (of type void*) and that it returnsnothing. That is what is meant by the function signature:

Collapse

void( __cdecl *start_address )( void * ),

The second passed parameter to the _beginthread() function is a requested stack size for the new

thread (remember, each thread gets its own stack). However, I always set this parameter to 0, whichforces the Windows Operating System to select the stack size for me, and I haven't had any problemswith this approach. The final passed parameter to the _beginthread() function is the single parameter

you want passed to the entry-point-function. This will be made clear by the following example program:

Collapse

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

2 of 8 8/14/2010 2:49 AM

Page 3: Multi Threading Tutorial - C

#include <stdio.h>#include <windows.h>#include <process.h> // needed for _beginthread()

void silly( void * ); // function prototype

int main(){ // Our program's first thread starts in the main() function.

printf( "Now in the main() function.\n" );

// Let's now create our second thread and ask it to start // in the silly() function.

_beginthread( silly, 0, (void*)12 );

// From here on there are two separate threads executing // our one program.

// This main thread can call the silly() function if it wants to.

silly( (void*)-5 ); Sleep( 100 );}

void silly( void *arg ){ printf( "The silly() function was passed %d\n", (INT_PTR)arg ) ;}

Go ahead and compile this program. Simply request a Win32 Console Program from Visual C++ .NET2003's New Project Wizard and then "Add a New item" which is a C++ source file (.CPP file) in whichyou place the statements I have shown. I am providing Visual C++ .NET 2003 workspaces forJaeschke's (modified) programs, but you need to know the key to starting a multi-threaded programfrom scratch: you must remember to perform one modification to the default project properties that theNew Project Wizard gives you. Namely, you must open up the Project Properties dialog (select "Project"from the main Visual C++ menu and then select "Properties"). In the left hand column of this dialog,you will see a tree view control named "Configuration Properties", with the main sub-nodes labeled"C/C++", "Linker", etc. Double-click on the "C/C++" node to open this entry up. Then, click on "CodeGeneration". In the right hand area of the Project Properties dialog, you will now see listed "RuntimeLibrary". This defaults to "Single Threaded Debug (/MLd)". [The notation /MLd indicates that this choicecan be accomplished from the compiler command line using the /MLd switch.] You need to click on thisentry to observe a drop-down list control, where you must select Multi-threaded Debug (/MTd). Ifyou forget to do this, your program won't compile, and the error message will complain about the_beginthread() identifier.

A very interesting thing happens if you comment out the call to the Sleep() function seen in this

example program. Without the Sleep() statement, the program's output will probably only show a

single call to the silly() function, with the passed argument -5. This is because the program's process

terminates as soon as the main thread reaches the end of the main() function, and this may occur

before the Operating System has had the opportunity to create the other thread for this process. This isone of the discrepancies from what Jaeschke says concerning C++/CLI. Evidently, in C++/CLI, eachthread has an independent lifetime, and the overall process (which is the container for all the threads)persists until the last thread has decided to die. Not so for straight C++ Win32 programs: the processdies when the primary thread (the one that started in the main function) dies. The death of this threadmeans the death of all the other threads.

Using a C++ Member Function as the Thread's Entry-Point-Function

The example program I just listed really isn't a C++ program because it doesn't use any classes. It isjust a C language program. The Win32 API was really designed for the C language, and when youemploy it with C++ programs, you sometimes run into difficulties. Such as this difficulty: "How can Iemploy a class member function (a.k.a. an instance function) as the thread's entry-point-function?"

If you are rusty on your C++, let me remind you of the problem. Every C++ member function has ahidden first passed parameter known as the this parameter. Via the this parameter, the function

knows which instance of the class to operate upon. Because you never see these this parameters, it is

easy to forget they exist.

Now, let's again consider the _beginthread() function which allows us to specify an arbitrary entry-point-function for our new thread. This entry-point-function must accept a single void* passed param.

Aye, there's the rub. The function signature required by _beginthread() does not allow the hidden

this parameter, and hence a C++ member function cannot be directly activated by _beginthread().

We would be in a bind were it not for the fact that C and C++ are incredibly expressive languages(famously allowing you the freedom to shoot yourself in the foot) and the additional fact that_beginthread() does allow us to specify an arbitrary passed parameter to the entry-point-function.

So, we use a two-step procedure to accomplish our goal: we ask _beginthread() to employ a staticclass member function (which, unlike an instance function, lacks the hidden this parameter), and we

send this static class function the hidden this pointer as a void*. The static class function knows to

convert the void* parameter to a pointer of a class instance. Voila! We now know which instance of the

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

3 of 8 8/14/2010 2:49 AM

Page 4: Multi Threading Tutorial - C

class should call the real entry-point-function, and this call completes the two step process. The relevantcode (from Jaeschke's modified Part 1 Listing 1 program) is shown below:

Collapse

class ThreadX{public:

// In C++ you must employ a free (C) function or a static // class member function as the thread entry-point-function.

static unsigned __stdcall ThreadStaticEntryPoint(void * pThis) { ThreadX * pthX = (ThreadX*)pThis; // the tricky cast

pthX->ThreadEntryPoint(); // now call the true entry-point-function

// A thread terminates automatically if it completes execution, // or it can terminate itself with a call to _endthread().

return 1; // the thread exit code }

void ThreadEntryPoint() { // This is the desired entry-point-function but to get // here we have to use a 2 step procedure involving // the ThreadStaticEntryPoint() function.

}}

Then, in the main() function, we get the two step process started as shown below:

Collapse

hth1 = (HANDLE)_beginthreadex( NULL, // security 0, // stack size ThreadX::ThreadStaticEntryPoint,// entry-point-function o1, // arg list holding the "this" pointer CREATE_SUSPENDED, // so we can later call ResumeThread() &uiThread1ID );

Notice that I am using _beginthreadex() rather than _beginthread() to create my thread. The "ex"

stands for "extended", which means this version offers additional capability not available with_beginthread(). This is typical of Microsoft's Win32 API: when shortcomings were identified, more

powerful augmented techniques were introduced. One of these new extended capabilities is that the_beginthreadex() function allows me to create but not actually start my thread. I elect this choice

merely so that my program better matches Jaeschke's C++/CLI code. Furthermore, _beginthreadex()

allows the entry-point-function to return an unsigned value, and this is handy for reporting status backto the thread creator. The thread's creator can access this status by calling GetExitCodeThread().

This is all demonstrated in the "Part 1 Listing 1" program I provide (the name comes from Jaeschke'smagazine article).

At the end of the main() function, you will see some statements which have no counterpart in

Jaeschke's original program. This is because in C++/CLI, the process continues until the last threadexits. That is, the threads have independent lifetimes. Hence, Jaeschke's original code was designed toshow that the primary thread could exit and not influence the other threads. However, in C++, theprocess terminates when the primary thread exits, and when the process terminates, all its threads arethen terminated. We force the primary thread (the thread that starts in the main() function) to waitupon the other two threads, via the following statements:

Collapse

WaitForSingleObject( hth1, INFINITE ); WaitForSingleObject( hth2, INFINITE );

If you comment out these waits, the non-primary threads will never get a chance to run because theprocess will die when the primary thread reaches the end of the main() function.

Synchronization Between Threads

In the Part 1 Listing 1 program, the multiple threads don't interact with one another, and hence theycannot corrupt each other's data. The point of the Part 1 Listing 2 program is to demonstrate how thiscorruption comes about. This type of corruption is very difficult to debug, and this makes multi-threadedprograms very time consuming if you don't design them correctly. The key is to providesynchronization whenever shared data is accessed (either written or read).

A synchronization object is an object whose handle can be specified in one of the Win32 waitfunctions such as WaitForSingleObject(). The synchronization objects provided by Win32 are:

eventmutex or critical sectionsemaphorewaitable timer

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

4 of 8 8/14/2010 2:49 AM

Page 5: Multi Threading Tutorial - C

An event notifies one or more waiting threads that an event has occurred.

A mutex can be owned by only one thread at a time, enabling threads to coordinate mutually exclusiveaccess to a shared resource. The state of a mutex object is set to signaled when it is not owned by anythread, and to nonsignaled when it is owned by a thread. Only one thread at a time can own a mutexobject, whose name comes from the fact that it is useful in coordinating mutually exclusive access to ashared resource.

Critical section objects provide synchronization similar to that provided by mutex objects, except thatcritical section objects can be used only by the threads of a single process (hence they are lighter weightthan a mutex). Like a mutex object, a critical section object can be owned by only one thread at a time,which makes it useful for protecting a shared resource from simultaneous access. There is no guaranteeabout the order in which threads will obtain ownership of the critical section; however, the OperatingSystem will be fair to all threads. Another difference between a mutex and a critical section is that if thecritical section object is currently owned by another thread, EnterCriticalSection() waits

indefinitely for ownership whereas WaitForSingleObject(), which is used with a mutex, allows you

to specify a timeout.

A semaphore maintains a count between zero and some maximum value, limiting the number ofthreads that are simultaneously accessing a shared resource.

A waitable timer notifies one or more waiting threads that a specified time has arrived.

This Part 1 Listing 2 program demonstrates the Critical Section synchronization object. Take a look atthe source code now. Note that in the main() function, we create two threads and ask them both to

employ the same entry-point-function, namely the function called StartUp(). However, because the

two object instances (o1 and o2) have different values for the mover class data member, the two

threads act completely different from each other. Because in one case isMover = true and in the

other case isMover = false, one of the threads continually changes the Point object's x and y

values while the other thread merely displays these values. But, this is enough interaction that theprogram will display a bug if used without synchronization.

Compile and run the program as I provide it to see the problem. Occasionally, the print out of x and yvalues will show a discrepancy between the x and y values. When this happens, the x value will be 1larger than the y value. This happens because the thread that updates x and y was interrupted by thethread that displays the values between the moments when the x value was incremented and when they value was incremented.

Now, go to the top of the Main.cpp file and find the following statement:

Collapse

//#define WITH_SYNCHRONIZATION

Uncomment this statement (that is, remove the double slashes). Then, re-compile and re-run theprogram. It now works perfectly. This one change activates all of the critical section statements in theprogram. I could have just as well used a mutex or a semaphore, but the critical section is the mostlight-weight (hence fastest) synchronization object offered by Windows.

The Producer/Consumer Paradigm

One of the most common uses for a multi-threaded architecture is the familiar producer/consumersituation where there is one activity to create packets of stuff and another activity to receive andprocess those packets. The next example program comes from Jaeschke's Part 2 Listing 1 program. Aninstance of the CreateMessages class acts as the producer, and an instance of the ProcessMessages

class acts as the consumer. The producer creates exactly five messages and then commits suicide. Theconsumer is designed to live indefinitely, until commanded to die. The primary thread waits for theproducer thread to die, and then commands the consumer thread to die.

The program has a single instance of the MessageBuffer class, and this one instance is shared by both

the producer and the consumer threads. Via synchronization statements, this program guarantees thatthe consumer thread can't process the contents of the message buffer until the producer thread has putsomething there, and that the producer thread can't put another message there until the previous onehas been consumed.

Since my Part 1 Listing 2 program demonstrates a critical section, I elected to employ a mutex in thisPart 2 Listing 1 program. As with the Part 1 Listing 2 example program, if you simply compile and runthe Part 2 Listing 1 program as I provide it, you will see that it has a bug. Whereas the producer createsthe five following messages:

Collapse

11111111112222222222333333333344444444445555555555

the consumer receives the five following messages:

Collapse

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

5 of 8 8/14/2010 2:49 AM

Page 6: Multi Threading Tutorial - C

12111111111322222222243333333335444444444

There is clearly a synchronization problem: the consumer is getting access to the message buffer assoon as the producer has updated the first character of the new message. But the rest of the messagebuffer has not yet been updated.

Now, go to the top of the Main.cpp file and find the following statement:

Collapse

//#define WITH_SYNCHRONIZATION

Uncomment this statement (that is, remove the double slashes). Then, re-compile and re-run theprogram. It now works perfectly.

Between the English explanation in Jaeschke's original magazine article and all the comments I haveput in my C++ source code, you should be able to follow the flow. The final comment I will make is thatthe GetExitCodeThread() function returns the special value 259 when the thread is still alive (andhence hasn't really exited). You can find the definition for this value in the WinBase header file:

Collapse

#define STILL_ACTIVE STATUS_PENDING

where you can find STATUS_PENDING defined in the WinNT.h header file:

Collapse

#define STATUS_PENDING ((DWORD )0x00000103L)

Note that 0x00000103 = 259.

Thread Local Storage

Jaeschke's Part 2 Listing 3 program demonstrates thread local storage. Thread local storage ismemory that is accessible only to a single thread. At the start of this article, I said that an OperatingSystem could initiate a new thread faster than it could initiate a new process because all threads sharethe same memory space (including the heap) and hence there is less that the Operating System needsto set up when creating a new thread. But, here is the exception to that rule. When you request threadlocal storage, you are asking the Operating System to erect a wall around certain memory locations inorder that only a single one of the threads may access that memory.

The C++ keyword which declares that a variable should employ thread local storage is__declspec(thread).

As with my other example programs, this one will display an obvious synchronization problem if youcompile and run it unchanged. After you have seen the problem, go to the top of the Main.cpp file andfind the following statement:

Collapse

//#define WITH_SYNCHRONIZATION

Uncomment this statement (that is, remove the double slashes). Then, re-compile and re-run theprogram. It now works perfectly.

Atomicity

Jaeschke's Part 2 Listing 4 program demonstrates the problem of atomicity, which is the situationwhere an operation will fail if it is interrupted mid-way through. This usage of the word "atomic" relatesback to the time when an atom was believed to be the smallest particle of matter and hence somethingthat couldn't be further split. Assembly language statements are naturally atomic: they cannot beinterrupted half-way through. This is not true of high-level C or C++ statements. Whereas you mightconsider an update to a 64 bit variable to be an atomic operation, it actually isn't on 32 bit hardware.Microsoft's Win32 API offers the InterlockedIncrement() function as the solution for this type of

atomicity problem.

This example program could be rewritten to employ 64 bit integers (the LONGLONG data type) and theInterlockedIncrement64() function if it only needed to run under a Windows 2003 Server. But,

alas, Windows XP does not support InterlockedIncrement64(). Hence, I was originally worried that I

wouldn't be able to demonstrate an atomicity bug in a Windows XP program that dealt only with 32 bitintegers. But, curiously, such a bug can be demonstrated as long as we employ the Debug mode settingsin the Visual C++ .NET 2003 compiler rather than the Release mode settings. Therefore, you will noticethat unlike the other example programs inside the .ZIP file that I distribute, this one is set for a Debugconfiguration.

As with my other example programs, this one will display an obvious synchronization problem if youcompile and run it unchanged. After you have seen the problem, go to the top of the Main.cpp file and

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

6 of 8 8/14/2010 2:49 AM

Page 7: Multi Threading Tutorial - C

Article Top

find the following statement:

Collapse

static bool interlocked = false; // change this to fix the problem

Change false to true, and then re-compile and re-run the program. It now works perfectly because it

is now employing InterlockedIncrement().

The Example Programs

In order that other C++ programmers can experiment with these multithreaded examples, I makeavailable a .ZIP file holding five Visual C++ .NET 2003 workspaces for the Part 1 Listing 1, Part 1 Listing2, Part 2 Listing 1, Part 2 Listing 3, and Part 2 Listing 4 programs from Jaeschke's original article (nowtranslated to C++). Enjoy!

Conclusion

This is my second submission to CodeProject. The first demonstrated how to use Direct3D 8 to model theMunsell color solid so that you could then fly through this color cube as in a video game. I also have awebsite where I offer a complete introduction to programming, including assembly languageprogramming. My home page is www.computersciencelab.com.

License

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

About the Author

John Kopplin

United States

Member

Comments and Discussions

FAQ SearchSearchSearchSearch

Noise Tolerance Medium Layout Normal Per page 25 UpdateUpdateUpdateUpdate

New Message Msgs 1 to 25 of 30 (Total in Forum: 30) (Refresh) First Prev Next

Olaf Petersen 8:28 10 Aug '10

srivas 23:33 10 Sep '09

amirkool 11:36 22 Mar '09

CNN73 21:35 17 Jan '09

aryamunish7:00 8 Jan '09

RTrelles 0:13 2 Jul '08

Alexei Valyaev 20:13 23 Mar '08

ben_th 0:59 16 Nov '07

yesitookmypills 16:19 30 Oct '07

vic12000 21:53 2 Apr '07

PhilDeets19:52 17 May '07

Ms. Agrawal 0:15 2 Apr '07

sylgas 9:10 6 Feb '07

Rate this article for us! Poor Excellent VoteVoteVoteVote

My vote of 5

A possible flaw

hw t convert c# file to VLSI chip

problem with the Part 2 Listing 3 program

ReadArticleAndRunCode--YouWillBeAmazedWithExcellencyOfArticle.

// No success multithreading with WinMain //

join

Thanks for this great Tutorial !

Great Article

the purpose of the event in Part2Listing1, result of Part2Listing3

Re: the purpose of the event in Part2Listing1, result ofPart2Listing3

Executing Two Functions Simultaneously

multiple parameters in arglist?

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

7 of 8 8/14/2010 2:49 AM

Page 8: Multi Threading Tutorial - C

gc3sanjose 12:16 27 Jun '07

Kaixi Luo 12:19 4 Jan '07

Michel Wassink 4:17 4 Jan '07

Jeffrey Walton 0:21 29 Dec '06

John Kopplin 10:45 29 Dec '06

Nguyen Luong Son 1:38 30 Sep '06

WhiteSky 23:53 15 Aug '06

prasikumbhare 5:21 21 Jul '06

ssanand 6:26 18 Jul '06

DotNET42 6:15 18 Jul '06

Thief^ 1:04 18 Jul '06

John Kopplin 21:33 19 Jul '06

Last Visit: 8:28 13 Aug '10 Last Update: 14:07 13 Aug '10 12 Next »

General News Question Answer Joke Rant Admin

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+PgUp/PgDown to switch pages.

PermaLink | Privacy | Terms of UseLast Updated: 28 Dec 2006

Copyright 2006 by John KopplinEverything else Copyright © CodeProject, 1999-2010

Web20 | Advertise on the Code Project

Re: multiple parameters in arglist?

Great!

No obvious synchronization problem

Article Formatting

Re: Article Formatting

Duo core aware?

Thanks

Event Description is missing [modified]

Thanks

C++ 2005

Cache miss?

Re: Cache miss?

Multithreading Tutorial - CodeProject http://www.codeproject.com/KB/threads/MultithreadingTutorial.aspx

8 of 8 8/14/2010 2:49 AM