Archived

This topic is now archived and is closed to further replies.

Dragonskin

Multi-threading Question

Recommended Posts

Dragonskin    122
Ok, I''ve searched around this forum (and a few others) and gone through the tutorials I''ve found on the net. I haven''t quite found what I''m looking for, though. I''m doing a test with multi-threading (so no "don''t use multi-threading" messages, please...I can''t believe HOW many I''ve read tonight already) to separate the message pump and the "game" loop. I read the tutorial here on Gamedev on how to do it, but it doesn''t seem to work with my implementation. I''m hoping someone here can either tell me what I''m doing wrong or confirm my suspicions. I start off by creating a thread that runs my RunGame function and then exits. The RunGame function just does a loop where it does stuff until the ESC key is pressed. The OpenGL window is created in that second thread (if I create it in the main thread, I can''t render to it). It will loop through and render properly, but it''s happily going along while ignoring any windows messages that come to it. This problem goes away if I put the message processing routines (PeekMessage, etc.) into RunGame''s loop. That isn''t what I''m going for. I''ve tried using the OpenGL window''s hWnd as a parameter for the main thread''s GetMessage or PeekMessage (tried both, only difference seems to be rendering performance). It still doesn''t get the messages, so I''m thinking messages can''t be gotten across threads (even with the proper window handle). After reading a lot (that search is HANDY!) of posts about message pumps and multi-threading, I''ve noticed some posts from people who are able to separate the message pump out and have it work. Anyone know what I might be missing? Or, if I can''t get messages from windows in other threads, anyone have a workaround that might work? Thanks in advance!

Share this post


Link to post
Share on other sites
Shannon Barber    1681
The first thread should create the window and start the pump (it''s a good idea to have the thread that creates a window, pump it, and have that thread be the first one).
In one of the first message handlers (after WM_CREATE), create the second thread. You want to make sure the window is alive and valid before handing it off to the GL renderer.

Make a simple window app first, make sure it works, then add the second thread. In the first thread you can now hang in the message pump using GetMessage (this will keep that thread idle until it has work to do as well).

GL may need to be told it''s being used in a MT app in the initialization functions.
Render to the window in the second thread - this needs to work this way.

You have mild syncronization issues for window resizing, moving, and GL blitting. You can worry about those later, it shouldn''t crash, but it might ''bleed'' over the window''s border.

Also put a small sleep in the render thread, Sleep(0) or Sleep(1) is good. You might be starving the message pump thread... you can also try increasing the priority of the pump thread and decreasing the priority of the render thread.

Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

"What I see is a system that _could do anything - but currently does nothing !" - Anonymous CEO

Share this post


Link to post
Share on other sites
ANSI2000    122
Like Magmai said, wait till you catch your WM_CREATE message then, create your thread.

Althought there afew other solutions also, am assumin you want to run OGL in its own thread and when windows executes a certain message you want to check for it and take appropriate action?

If you know C++ well enough you can use whats called a listener patern and have ogl listen for certain events and take action from there... It not that important to have high performance on catching windows events, more important catching input events, playing sound and plastering billions and billions of polygons on screen

Share this post


Link to post
Share on other sites
Brannon    122
Also, you can create your rendering context on the main thread, but in order to render to the window from the worker/rendering thread, you must call wglMakeCurrent() with the render context associated with the window you created.

If you weren''t calling wglMakeCurrent() from the second thread, then that''s why when the window is created on the first thread, you can''t render to it.


-Brannon

Share this post


Link to post
Share on other sites
Dragonskin    122
Thanks for the responses. I''m working on implementing some changes now.

This was a simple, working app before I started playing around with it.

I''ll get back to you all with the results of my typing.

Share this post


Link to post
Share on other sites
Dragonskin    122
Ok, now THAT part''s running. I can''t get GetMessage to work yet, though. I''m still using PeekMessage because GetMessage won''t let my loop finish to get to where I''m checking to see if the thread is done. I''ve tried moving the checking code, but it doesn''t make a difference. Any ideas on how to get around that?

The only other problem I have is that going from fullscreen to windowed and back isn''t going to be nearly as easy. I''ll work on a way of synchronizing that sometime tomorrow night.

Thanks all!!

Share this post


Link to post
Share on other sites
Brobanx    136
I''ve got two threads, one for the window, one for OpenGL rendering as well. I plan on adding a third and fourth threads for user input and background loading of expected resources (models, maps, etc...) No problems except for resizing the window, but that''s not very crucial and I should have it fixed soon.

At first I had problems exiting the program. What I did was a made a global boolean g_Exit, and when g_Exit is true, it exits both threads. What I found VERY strange is that even though I created the window in the main thread, I had to destroy it in the rendering thread.

Currently I''m calling CreateThread directly after I make the window, I might need to change where I''m calling it from. All options in CreateThread are set to 0 except for the mandatory ones.

Share this post


Link to post
Share on other sites
Brobanx    136
Well I''ve got the threads working very well now, I just need to figure out the SuspendThread and ResumeThread functions... I can sure suspend the thread properly, but then I can''t get it back. Anyone ever get those two functions working properly?

Share this post


Link to post
Share on other sites
Dragonskin    122
Well, I''m thinking my problem has something to do with the close message getting sent to the wrong thread (I''ll try PostThreadMessage when I get home). I use a variable to tell me when the thread is done, which worked in an earlier test that I tried.

As for destroying the window in the second thread, I tried it but it wouldn''t unregister the class. I tried destroying it in the main thread and it destroyed it without any problems.

I''m at work for another hour so I won''t be able to try anything until then, though. I''m hoping that''s all it is.

Share this post


Link to post
Share on other sites
Brobanx    136
Ok this is good, my code works perfectly now. It suspends and resumes the rendering thread exactly the way I want it to, shutdowns just fine and everything.

Just to make sure it was all perfect, I searched in the docs for threads, and found the ExitThread() function. Do I actually have to call this function to properly shutdown my program? Currently it just calls CloseHandle() on the thread with no problems, and when I call ExitThread() and I close the program, it is still running in the background without a window! (I have to close it with task manager). The docs aren''t very clear on how to manage threads, it just says that ExitThread() "exits the thread" =/.

Share this post


Link to post
Share on other sites
Dragonskin    122
Well, I got my close button working. I had ESC jump out of the main loop, and it would destroy the window properly, but the close button wouldn''t (because WM_CLOSE was posting a quit message instead of telling the thread to close). So, I moved my "game loop keep running" variable into the message handler and made my ESC key checker just post a WM_CLOSE message instead of setting the variable itself. I figure it''s MORE threadsafe that way.

Also, I think I''ll look into that wglMakeCurrent, as Brannon suggested. To make the window and the contexts independently I had to split up my Init and End functions, and I''d like to pull those back together if I can.

Share this post


Link to post
Share on other sites
Shannon Barber    1681
The worker thread should should poll either a flag or an event to determine when to exit each loop (or every nth loop). The main message pumping thread should set the flag/signal the event to tell the worker thread to exit (in say the WM_CLOSE message handler). After the primary thread sets this flag/event it should WaitForSingleObject(m_WorkerThreadHandle, 5000); If the wait succeeds, it means the worker thread has terminated, and it''s safe to exit the primary thread. If the wait fails, it''s time for baby-seal technology - TerminateThread(m_WorkerThreadHandle); Then exit the primary thread/message pump.

If you want it to exit when you hit ESC, have the input thread PostQuitMessage(hWnd, 0), and everything will come down like a ton of bricks.

Share this post


Link to post
Share on other sites
Dragonskin    122
I played around with wglMakeCurrent a bit but it didn''t want to work as I had my functions set up. It returned with a "BUSY" error, so I''m thinking it''s because I needed to release it before I tried making it current.

So, rather than try playing with that even more (putting a couple of my init functions BACK together started giving weird errors, so that contributed to my decision) I''ll stick with creating the windows separately from the device contexts.

I did get my fullscreen stuff working again, too (after a lot of quick CTRL-ALT-DEL to get to my task manager ). I ended up going with a toggle-fullscreen flag that I set to true in the game loop. I then post a close message so the game loop exits out and destroys the window. I put the main while loop inside a do-while loop that will check for the toggle-fullscreen flag. I had to throw in some extra checks and such (to keep it from looping indefinitely, giving error message boxes all the time, and to keep it from exiting because the GameThreadDone value was already true...)

It''s been VERY interesting. Thanks for your help, everyone. Special kudos to you, Magmai!

I got my Mutex class completed a little while ago, so I''m going through and protecting some of the stuff my wndproc hits.

Later all!

Share this post


Link to post
Share on other sites
Shannon Barber    1681
quote:
Original post by Dragonskin
It''s been VERY interesting. Thanks for your help, everyone. Special kudos to you, Magmai!

I got my Mutex class completed a little while ago, so I''m going through and protecting some of the stuff my wndproc hits.

Later all!


Welcome

One last thing, Win32 Mutexes are for inter-processes synchronization and Critical Sections are for inter-thread synchronization (they''re an order of magnitude faster than the mutex, so they''re preferable). Both work like traffic lights, just have different scopes.

Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

"What I see is a system that _could do anything - but currently does nothing !" - Anonymous CEO

Share this post


Link to post
Share on other sites
Brannon    122
You really shouldn''t ever call TerminateThread(). If your thread is hanging on exit, then fix what is causing the hang. Don''t hide it by killing the thread. TerminateThread() kills the thread "with extreme prejudice". That means that DllMain(DLL_THREAD_DETACH) doesn''t get called, etc. Rather than kill the thread that way, you can just let it sit there. When you go to exit the process the system will take care of destroying the thread.


Calling ExitThread() will exit the current thread. If you call this on your main thread, your process will shutdown. You don''t actually need to call ExitThread(). Just ''return'' from the thread function, and the system call (in kernel32.dll) that started your thread will take care of shutting it down. Also, if you use any CRT functions in your threads, use _beginthreadex() (not CreateThread).

If you are processing messages in your thread function and you want to be able to wait on a shutdown event, then use MsgWaitForMutlipleObjects(), and specify that you want to be woken up on all messages (then use PeekMessage() to retrieve the message). If your are woken up because the event is signalled, then you can just exit the thread.


-Brannon

Share this post


Link to post
Share on other sites
Dragonskin    122
It was kind of interesting last night trying to figure out why I suddenly started having problems going between fullscreen and windowed mode (occasionally, it would just exit the program). Finally traced it back to the window handle not being valid in time for the second thread to start again (and that''s AFTER the WM_CREATE has been sent, interestingly enough). I decided to put the second thread to sleep for 5 seconds and then try doing the init again. If it fails the second time it''ll give me a message box.

As for the GetMessage/PeekMessage problem I was having...well, I still am. I did come up with a work-around for the moment. Since I have an "Active" variable for my program, I just check for that in my main loop. If the program isn''t active it''ll call WaitMessage. That way at least when I minimize my CPU usage goes down to almost nothing. Anyone see a problem with this method, or should I be ok?

And I got critical sections put in. Lucky me I designed the Mutex class with a simple on/off interface that worked well with critical sections as well. Change the class type on all of the mutexes and clear sailing afterwards.

Share this post


Link to post
Share on other sites
Brannon    122
What problem are you having with GetMessage/PeekMessage?

WaitMessage is essentially doing the same thing as GetMessage, except that I don''t think WaitMessage retrieves the message for you, it just wakes up the thread.

If you don''t want your message loop to chew up all of your resources, then don''t use PeekMessage. Use GetMessage instead. If there are no messages pending, then GetMessage will block (put the thread to sleep).


-Brannon

Share this post


Link to post
Share on other sites
Dragonskin    122
Ok, the problem I''m having with GetMessage is that it is preventing my main thread from closing because it never gets the last message.

I do a check on a GameThreadDone variable which is set to true when the game thread is done (and then the thread returns 0). I''m having the thread post a close message as well, which worked fine when I was doing testing before. If I use GetMessage, it never gets down to where it''ll post the quit message because the thread is done. Peekmessage allows me to get down further in the loop.

And yes, I realize that WaitMessage doesn''t get a message, just waits for it. That''s why I''m using it if the program isn''t "Active"...it''ll wait until a message comes, and then my message handler loops through and takes care of it.

It''s not that it doesn''t WORK, I''m just trying to figure out why GetMessage doesn''t get my last close message.

Share this post


Link to post
Share on other sites
Shannon Barber    1681
That''s very odd... let''s see some code (snipped please )

I usually add an ASSERT if I need to resort to baby-seal technology, but it''s more desirable to TerminateThread than hang the program, in a worse-case-scenario. (Fixing the root problem as soon as it''s discovered is a very good idea though.)

Magmai Kai Holmlor

"Oh, like you''ve never written buggy code" - Lee

"What I see is a system that _could do anything - but currently does nothing !" - Anonymous CEO

Share this post


Link to post
Share on other sites
Dragonskin    122
Here's the "relevant" parts.

      
unsigned WINAPI GameThread(LPVOID arg1) {
// Run the window's game loop

OpenGLWindow.RunGame();

// The thread is done, alert the message handler

PostMessage(NULL,WM_CLOSE,0,0);

// Alert the main thread that this thread is done

GameThreadDoneCS.On(); // Ensuring we're the only ones writing right now

GameThreadDone = true;
GameThreadDoneCS.Off(); // Protection is over


return 0;
}


// Main windows function

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MSG msg; // For processing messages

bool done; // For the main loop


// Get the screen resolution to run in

if (!OpenGLWindow.ChooseScreenRes(hInstance))
return 0; // Didn't click the OK button


// Loop around unless we aren't toggling fullscreen mode

do {
GameThreadDoneCS.On(); // I think I'm getting carried away with these, but I'm not taking many chances

GameThreadDone = false;
GameThreadDoneCS.Off(); // Protection off


// Create the OpenGL window (but not everything)

if (!OpenGLWindow.CreateWnd(ProgramName, CustomGLWndProc)) {
MessageBox(NULL,"Unable to create new window (in main)","ERROR",MB_OK|MB_ICONEXCLAMATION);
return 0; // Problem creating the window

}

done = false;

// Main message pump loop

while (!done) {
// Get any message that may be waiting

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
// Is it a quit message?

if (msg.message == WM_QUIT) {
// Program is over

done = true;
} else {
// Process the message

TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

// Check to see if the thread is done yet so we can exit

if ((GameThreadDone == true) && (ToggleFullscreen == true)) {
done = true;
} else if (GameThreadDone == true) {
PostQuitMessage(0);
}

if (!bProgramActive)
WaitMessage(); // Releases control until another message comes

}

OpenGLWindow.DestroyWnd();
} while (ToggleFullscreen == true);

return int(msg.wParam); // Exit code

}


This is the way that works. I really don't want to terminate the thread by any means other than returning, so I'm keeping away from TerminateThread (especially since THIS works).

All I'm doing to test the GetMessage is converting PeekMessage to GetMessage (and removing PM_REMOVE, of course) and then taking out the WaitMessage stuff.

I've tried moving around the order I tell the main loop my thread is done (post the message afterwards) but it will still hang at the GetMessage. At least, I'm assuming that's where it's hanging because that's the statement the debugger is always on when I pause execution.

Edited by - Dragonskin on December 20, 2001 4:51:40 PM

Share this post


Link to post
Share on other sites
Brannon    122
So, why are you passing NULL as the hWnd to PostMessage() in your thread? You should probably be using PostThreadMessage() here. This will probably solve your GetMessage() problem.

Probably better to use for this though are Events. Create an event that is used to signal the thread is shutting down (instead of having GameThreadDone). Events are atomic, so you don''t need to protect them with critical sections, etc. Then you can use WaitForSingleObject(hEvent, 0) to poll the event to see if it''s set yet (WaitForSingleObject doesn''t need to be protected either).


-Brannon

Share this post


Link to post
Share on other sites
Dragonskin    122
Now that I look at it again I don''t even NEED that PostMessage in there, because the window is already closing and the thread is exiting. I think it was leftover from when the window was created in the second thread. I removed it and it still works (with PeekMessage).

As for changing it to PostThreadMessage, I''ve looked at the MSDN docs and it doesn''t mention what the ThreadID of the initial thread is. I store the second thread''s ID, but you never get the first one because it''s your main loop.

And I''m going to look into the events. Thanks!

Share this post


Link to post
Share on other sites
Shannon Barber    1681
You're going about it in a convoluted way...

  
//Global handles

HANDLE g_thrGameLoop=INVALID_HANDLE_VALUE;
HANDLE g_evExit==INVALID_HANDLE_VALUE;
HANDLE h_hMainWnd;



WinMain/Main Thread
        
//...

g_evExit = CreateEvent(NULL, TRUE, FALSE, 0);
//The thread is created in one of the init window messages

// as is g_hMainWnd;

while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
SetEvent(g_evExit);
if(WAIT_OBJECT_0!=WaitForSingleObject(g_thrGameLoop, 5000))
{
OutputDebugString("GameLoop Thread Failed to exit gracefully!!!!\n");
TerminateThread(m_thrGamLoop, -42);
}
CloseHandle(m_evExit);
CloseHandle(m_thrGameLoop);
return(0);


//GameLoop
  
DWORD dwYieldTime_ms = 0;
while(WAIT_OBJECT_0!=WaitForSingleObject(g_evExit, dwYieldTime_ms))
{
//Game Loop goes here

if(DecideToQuit())
{
PostMessage(h_hMainWnd, WM_QUIT, 0, 0);
}

}

//Clean-up

return(0);


Magmai Kai Holmlor

"Oh, like you've never written buggy code" - Lee

"What I see is a system that _could do anything - but currently does nothing !" - Anonymous CEO


Edited by - Magmai Kai Holmlor on December 20, 2001 8:37:27 PM

Share this post


Link to post
Share on other sites
Dragonskin    122
I understand the convoluted part. The reason that I checked for WM_QUIT messages to exit the loop was because, well, I was using PeekMessage and it doesn't return 0 when a quit message comes through. Guess I should have looked to make sure exactly GetMessage's return value was, huh? After reading your code I decided to check again... makes me want to say "argh" all night, if you know what I mean.

Thank you, again. I have GetMessage working, and now I don't have to explicitly wait when the program isn't active. That actually had a bug that I didn't think of until a bit ago...if it was minimized and you right-click and close it, it'd run into the same problem the GetMessage was earlier. That could be gotten around by checking to see if the thread was active or not, but this is MUCH better.

Major thanks!!!!!

Edited by - Dragonskin on December 20, 2001 9:42:47 PM

Share this post


Link to post
Share on other sites