🔒 Locked
Multithreading questions
Advertisement
Original Post
I'm new to multithreading so for the purpose of learning, I have decided to create a small app in which 2 threads would just increment a global counter. Alright, so I start by creating the two threads in WinMain : threadNumber = 1; g_hThread[0] = (HANDLE) _beginthreadex(NULL, 0, ThreadFunction, &threadNumber, 0, (unsigned *)&g_ThreadID[0]); threadNumber = 2; g_hThread[1] = (HANDLE) _beginthreadex(NULL, 0, ThreadFunction, &threadNumber, 0, (unsigned *)&g_ThreadID[1]); --- The 2 threads call the same function (ThreadFunction). This function just increments a global variable (g_GlobalCounter) and prints the new counter value in my window. unsigned __stdcall ThreadFunction(void* param) { char szLine[1000]; static int textOffsetY = 0; static int lineNumber = 0; int threadNumber = *static_cast(param); if (WaitForSingleObject(g_hMutex, 3000) == WAIT_TIMEOUT) { return 1; } while (g_GlobalCounter < 400) { g_GlobalCounter++; string oFileLine(szLine); wsprintf(szLine, _T("Thread %d : %d"), threadNumber, g_GlobalCounter); HDC hDC = GetDC(g_hWnd); TextOut(hDC, 1, textOffsetY, szLine, strlen(szLine)); textOffsetY += 15; if (textOffsetY > 600) { textOffsetY = 0; } ReleaseDC((HWND)param, hDC); Sleep(100); } ReleaseMutex(g_hMutex); return 0; } -- I thought Thread1 would start incrementing the global counter during its time slice, then it would pause and Thread2 would increment the counter during its time slice, then this cycle would repeat. So the printed values would look something like : Thread 1 : 0 Thread 1 : 1 Thread 1 : 2 Thread 2 : 3 Thread 2 : 4 Thread 1 : 5 Thread 1 : 6 ... Instead, only one of the threads starts incrementing the counter and the other thread does nothing : Thread 1 : 0 Thread 1 : 1 Thread 1 : 2 Thread 1 : 3 ... Thread 1 : 100234 --- What I'm I doing wrong ? Can 2 threads call the same function ?
Quote:
Original post by stonemetal
Try putting a print statement in the thread program so that you know they both are being started. Two you are sleeping inside the mutex that means thread 2 can't pick up the mutex even though thread 1 has given up its time slice.
Exactly.
While Sleep() relinquishes the time step to threads that are pending to run, it does NOT release the mutex. So thread 2 just times out.
So what you probably need is:
unsigned __stdcall ThreadFunction(void* param){ char szLine[1000]; static int textOffsetY = 0; static int lineNumber = 0; boolean complete = false; int threadNumber = *static_cast<int*>(param); while (!complete) { if (WaitForSingleObject(g_hMutex, 3000) == WAIT_TIMEOUT) { return 1; } if (g_GlobalCounter < 400) { g_GlobalCounter++; string oFileLine(szLine); wsprintf(szLine, _T("Thread %d : %d"), threadNumber, g_GlobalCounter); HDC hDC = GetDC(g_hWnd); TextOut(hDC, 1, textOffsetY, szLine, strlen(szLine)); textOffsetY += 15; if (textOffsetY > 600) { textOffsetY = 0; } ReleaseDC((HWND)param, hDC); } else { complete = true; } ReleaseMutex(g_hMutex); if (!complete) Sleep(100); } return 0;}I think. I don't do threading in Windows...
That worked nicely, thanks !
I'm starting to understand why simple things can be hard to find when doing multithreading. Here's an example of what I mean :
WinMain
{
int threadNumber;
threadNumber = 1;
g_hThread[0] = (HANDLE) _beginthreadex(NULL, 0, ThreadFct1, (void *) &threadNumber, 0, (unsigned *)&g_ThreadID[0]);
threadNumber = 2;
g_hThread[0] = (HANDLE) _beginthreadex(NULL, 0, ThreadFct1, (void *) &threadNumber, 0, (unsigned *)&g_ThreadID[0]);
}
Since both threads are calling the same function (ThreadFct1), I needed a way to know inside this function which thread had made the call. This is why I had the idea of passing the thread number (1 or 2) as a parameter (the 4th parameter of the _beginthreadex function is the parameter of the thread function (ThreadFct1).
Simple enough, right ? Wrong. It didn't work...
Solution ? This...
new int(1) for the first call to _beginthreadex()
new int(2) for the second call to _beginthreadex()
No wonder why the question "Do you have experience with multithreading ?" is always asked nowadays during a programming interview.
[Edited by - JoyRide on February 26, 2009 5:06:20 PM]
I'm starting to understand why simple things can be hard to find when doing multithreading. Here's an example of what I mean :
WinMain
{
int threadNumber;
threadNumber = 1;
g_hThread[0] = (HANDLE) _beginthreadex(NULL, 0, ThreadFct1, (void *) &threadNumber, 0, (unsigned *)&g_ThreadID[0]);
threadNumber = 2;
g_hThread[0] = (HANDLE) _beginthreadex(NULL, 0, ThreadFct1, (void *) &threadNumber, 0, (unsigned *)&g_ThreadID[0]);
}
Since both threads are calling the same function (ThreadFct1), I needed a way to know inside this function which thread had made the call. This is why I had the idea of passing the thread number (1 or 2) as a parameter (the 4th parameter of the _beginthreadex function is the parameter of the thread function (ThreadFct1).
Simple enough, right ? Wrong. It didn't work...
Solution ? This...
new int(1) for the first call to _beginthreadex()
new int(2) for the second call to _beginthreadex()
No wonder why the question "Do you have experience with multithreading ?" is always asked nowadays during a programming interview.
[Edited by - JoyRide on February 26, 2009 5:06:20 PM]
Doing multi-threading RIGHT, as you're just in the process of finding out, is __hard__. I suppose it may be a good experience to take your first steps using a low-level approach, like you're doing by using the WinThread API.
But once you've learnt the basics, I advise you to drop raw threading and switch to using a higher level approach to writing threaded applications.
The boost C++ library collection offers, among many other useful things, a very advanced and clean threading library that helps you preserve your sanity when writing multi-threaded applications.
Specifically, you should never rely on manual lock acquisition / release as this is just asking for dead-locks. Use RAII (resource acquisition is initialization) guards to acquire and release locks (critical sections, in Microsoft-speak, also known as mutexes).
To give you an example, assume you have a simple integer variable that is accessed by 2 threads and a mutex / critical section to protect it from concurrent access.
If you had used a RAII guard, forgetting to release the mutex would have been impossible:
But once you've learnt the basics, I advise you to drop raw threading and switch to using a higher level approach to writing threaded applications.
The boost C++ library collection offers, among many other useful things, a very advanced and clean threading library that helps you preserve your sanity when writing multi-threaded applications.
Specifically, you should never rely on manual lock acquisition / release as this is just asking for dead-locks. Use RAII (resource acquisition is initialization) guards to acquire and release locks (critical sections, in Microsoft-speak, also known as mutexes).
To give you an example, assume you have a simple integer variable that is accessed by 2 threads and a mutex / critical section to protect it from concurrent access.
int sharedInt = 0;CRITICAL_SECTION cs;void threadFunc(){ // increment integer by 1 per call of this function // acquire critical section EnterCriticalSection( &cs ); ++sharedInt; if ( someCondition ) { // oops, we forgot to release the mutex ... any other threads waiting to acquire it will block!! return; } // release critical section LeaveCriticalSection( &cs );}If you had used a RAII guard, forgetting to release the mutex would have been impossible:
class MutexKeeper{public: // acquire mutex in constructor explicit MutexKeeper( CRITICAL_SECTION& theMutex ) : m_mutex( theMutex ) { EnterCriticalSection( &m_mutex ); } // and release it in the destructor ~MutexKeeper() { LeaveCriticalSection( &m_mutex ); }private: CRITICAL_SECTION& m_mutex;};int sharedInt = 0;CRITICAL_SECTION cs;void threadFunc(){ // acquires mutex on construction and releases it on destruction ... regardless of where or how we return from this function (even if an exception is thrown!) MutexKeeper mk( cs ); ++sharedInt; if ( someCondition ) { return; }}
Red Ant, thanks for your feedback !
Boost : I will look into it, but I'm wondering if a lot of companies use it. I mean if you get used to coding with Boost and let say most companies don't want to use it, then what ?
RAII : I didn't know this pattern, thanks. Makes a lot of sense to use it for resource management like threads, files, etc.
Code example : very good example. Even with my very little multithreading experience, I actually experienced this one already :)
---
Any good resources (books, etc.) to speed up the learning curve for doing things the multithreaded way ?
It seems to be a trial & error process that requires VTune...
Boost : I will look into it, but I'm wondering if a lot of companies use it. I mean if you get used to coding with Boost and let say most companies don't want to use it, then what ?
RAII : I didn't know this pattern, thanks. Makes a lot of sense to use it for resource management like threads, files, etc.
Code example : very good example. Even with my very little multithreading experience, I actually experienced this one already :)
---
Any good resources (books, etc.) to speed up the learning curve for doing things the multithreaded way ?
It seems to be a trial & error process that requires VTune...
Quote:
Original post by JoyRide
Since both threads are calling the same function (ThreadFct1), I needed a way to know inside this function which thread had made the call. This is why I had the idea of passing the thread number (1 or 2) as a parameter (the 4th parameter of the _beginthreadex function is the parameter of the thread function (ThreadFct1).
Simple enough, right ? Wrong. It didn't work...
Hopefully it's obvious why it didn't, because you're sharing one variable between 3 threads - the 2 you create, and the main thread where it was defined. Change it in one place, and it'll be changed everywhere.
This is why the best practice for concurrent programming is generally not to try and put locks and semaphores around shared data, but to send copies of data.
Quote:
Original post by JoyRide
Boost : I will look into it, but I'm wondering if a lot of companies use it.
They do.
Quote:
I mean if you get used to coding with Boost and let say most companies don't want to use it, then what ?
Then nothing. What is your concern? You'll have to learn new APIs for as long as you write code. Besides, any sensible threading library will have a concept of a scoped lock. Even more besides, the Boost threads API is being adopted by the C++ standard too, so it will be with you always.
Quote:
Any good resources (books, etc.) to speed up the learning curve for doing things the multithreaded way ?
For "traditional threads", Programming with POSIX Threads by Butenhof is very good. It only applies to systems that have pthreads (Windows doesn't natively, but there are projects that try to add pthreads on top on the Windows API). The boost threads API is really a C++ version of pthreads.
Patterns for Parallel Programming by Mattson et al is pretty good. It tends to repeat itself quite a lot, but it will get you thinking about concurrency at a higher level.
Lastly, check out a programming language designed around immutability, such as Haskell or Clojure. You'll find writing correct concurrent programs in these languages easier and it will give you another valuable perspective.
Topic Locked
This topic has been locked by a moderator. New replies are not allowed.
Sign in to reply to this topic.
Advertisement