Jump to content
  • Advertisement

Archived

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

Jackie Chao

Any pro & con of using threads in RTS game?

This topic is 5400 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, dear everyone. I''m now using Java to write a RTS game. Now, I have a unit which sould has the ability to search the path, avoid the obstacles and finding which region it is in. Should I use many threads for each actions? Or I just need to use a single thread to do all the actions? Are there any pro and con of using threads in RTS game? Anyone could help me? Thanks for your helps! Sincerely, Jackie.

Share this post


Link to post
Share on other sites
Advertisement
Ok, I have never writen a pathfinding system, so I can not render an opinion of whether or not it would need to run in a seperate thread from the main locial process. However, I do know a bit about multithreading under the Win32 envrionment.

Having a speperate thread for each pathfind that is outstanding is the programming equivilant of shooting yourself in the foot and hitting a major artery. That being said, I'll explain.

Let's take Star Craft as an example. I forget the maximum number of object allowed, so I'll use the greatest amount that must be allowed. In Star Craft, you are allowed 200 points of unit space, and the small units (most of them) take up 1 point of unit space. Maximum of 8 players on a server at one time, that's 200x8 = 1600. Add in "critters" (non-aligned small roving animals) and a couple of Zergling Rushers (as I recall, the Zergling takes up 1/3 of a Unit Point), and you have a very real posibility of hitting 1600 mobile units on the server. Now, not all of them are going to be moving about at once, but it's safe to assume that durring the most heated Death Match senario, there will be at least half of them running and doing things at any given time. So, that's 800 outstanding pathfinding requests.

Now let us say that you are spliting each pathfind request into a seperate thread. That's a conservitive estimate of 800 threads at peak devoted to pathfinding.

If you are running NT / 2k / XP, do a quick CTRL + ALT + DELETE and bring up the task manager. Hop on over to the "Preformance" tab. In the "totals" group box, find the value for "Threads". Currently I am sitting at 395 threads running on my system at this time. I have two processes of MSVC 6.0 up, Netscape Navigator 7.something with about a bazillion browser tabs open, Kazza Lite ++, Task Manager, the MSDN within the Windows Help viewer, about seven instances of explore.exe, a few Internet Explorer windows, the Java 2 VM, Yahoo Pager, an Appache 2.0 web server, MySQL handling querries to six databases, and PHP4.0 all running on top of my system processes (which are glutted by a lot of services that I don't need, but am too lazy to remove). Ohh yea, and a VERY anoying spyware program that is pulling 12 threads on me for some ungodly reason.

Now, let's take a look at the CPU usage...
The only programs pulling CPU cycles are Netscape, Kazaa Lite, the windows services manager, task manager, the system process, and the System Idle Process (not to mention that unforgivable spyware).

Something odd? GMT.exe and winlogin.exe pull 22 and 20 threads respectivley at current, yet they are using 0 CPU cycles.

Now let's have a little fun. Compile this niffty dandy never-ending loop (and be sure you know what ALT+F4 does ).


// Never Ending Loop example


// Windows headers

#include <windows.h> // There is a better way to get access to the Sleep() function, but this is easier

// ANSI Standard Library headers

#include <stdio.h>
// STL headers

// NONE

// API headers

// NONE

// Internal headers

// NONE


void main()
{
// Print program header

printf("Never Ending Loop Example\n");
printf("This program WILL NOT TERMINATE!\n");
printf("Use ALT+F4 to shut it down\n");
printf("This program SHOULD NOT cause any memory leaks or uneeded mode switches on WindowsNT based systems\n");

// Sleep() state

printf("\n\n\nEntering 20 second Sleep()\n");
printf("This process should be getting 0 CPU cycles\n");
Sleep(20000);


// Never Ending Loop state

printf("\n\n\nEntering never ending logic loop\n");
printf("This process should be getting all spare CPU cycles, if not 95 - 100 precent\n");
printf("Now, click the little \"X\" or hit ALT+F4\n");
while(1)
{

}

// Process termination, we should never reach this!

// Mwuhahahah!!!

return;
}



Notice that when the program hits the Sleep() statment, it gets 0 CUP cycles. That is because Windows takes the process off of the thread time slice list (as I understand it) untill Sleep() returns. Then, durring that horrid while() (See FOOTNOTE 1) loop, it draws every CPU cycle that is free, and the System Idle Process drops to 0. That is what happens when you have a program that actualy runs all the time. You can see this in Windows applications as well. Most use GetMessage() rather than PeekMessage() for thier message loops. The reason is simple. GetMessage() makes the process sleep (I.E., it gets taken off the thread time slice list) untill there is a message to actualy get. Meanwhile, PeakMessage() returns imediatly, allowing the application to go and do other things that just sit there waitting on a windows message.

Now we're going to tie the two branches of thought into one. Hundreds of threads doing constant, non-sleeping opperations. Here's another copy-paste program to try out.


// Never Ending Loop example, Thread Edition


// Compile Directives

// Tell the compiler that we are using the Standard Template Library

#define _STL_USING
#pragma warning( disable : 4786 )
// Windows headers

#include <windows.h>
#include <process.h> // For _beginthreadex

// ANSI Standard Library headers

#include <stdio.h>
// STL headers

#include <vector>
// API headers

// NONE

// Internal headers

// NONE


#define NELE_TE_MAXTHREADS 20 // Number of threads to create

#define NELE_TE_SLEEPTIME 30 // Number of seconds to let the threads run amuck


UINT __stdcall ThreadProc(void* vArg);
HANDLE hDieEventHandle;
long lThreadsRunning;

void main()
{
// Data Members

HANDLE hTemp;
int i;
UINT uUnused;
DWORD dwFailSafe;
std::vector<HANDLE> threads;
std::vector<HANDLE>::iterator it;

// Print program header

printf("Never Ending Loop Example, Thread Edition\n");
printf("This program WILL TERMINATE after making a mess of your CPU cache\n");
printf("This program WILL TERMINATE after %d seconds\n", NELE_TE_SLEEPTIME);
printf("This program SHOULD NOT cause any memory leaks, but WILL CAUSE lots\n and lots of uneeded context switches on WindowsNT based systems\n");

// Create our die event

hDieEventHandle = CreateEvent(NULL, true, false, NULL);
if(!hDieEventHandle)
{
printf("\n\n\nCould not create the die event!\nProcess Termination\n");
return;
}

// Create all the threads

for(i = 0; i < NELE_TE_MAXTHREADS; i ++)
{
hTemp = (HANDLE) _beginthreadex(0, 0, ThreadProc, NULL, 0, &uUnused);
if(!hTemp)
{
printf("Failed to create a thread!\n");
}
else
{
threads.push_back(hTemp);
}
}

// Sleep for NELE_TE_SLEEPTIME, and then signal the die event

Sleep(NELE_TE_SLEEPTIME * 1000);
if(!SetEvent(hDieEventHandle))
{
// Could not signal the die event

// This is going to be a very harsh shut-down

printf("COULD NOT SIGNAL THE DIE EVENT!\n");
printf("It might be wise to reboot after process termination\n");
printf("Process Termination in 15 seconds\n");
Sleep(15000);
return;
}

// Wait on all threads to return

printf("Process Termination in progress\n");
dwFailSafe = 0;
while(lThreadsRunning)
{
// NOT THE RIGHT WAY to do a time-out

// On a very old system, this could take hours

// On a high-end server rig, it could take less than a second

if(dwFailSafe == 0xFFFFFFFF)
{
// This has gone on for WAY too long

// Probable failure with the running thread count

// Another nasty shut down

printf("THREAD TERMINATION TIMEOUT!\n");
printf("It might be wise to reboot after process termination\n");
printf("Process Termination in 15 seconds\n");
Sleep(15000);
return;
}
dwFailSafe ++;
}

// Quick sleep just incase ThreadProc() lost context between the InterlockedDecrement() call and

// the return statement

Sleep(5000);

// Close all thread handles

for(it = threads.begin(); it != threads.end(); it++)
{
CloseHandle(*it);
}

// Close the die event handle

CloseHandle(hDieEventHandle);

// Process termination

printf("Process Termination\n");
return;
}

UINT __stdcall ThreadProc(void* vArg)
{
// Data Members

// static int i;


InterlockedIncrement(&lThreadsRunning);

while(1)
{
// Check our die event

/* if(WaitForSingleObject(hDieEventHandle, 0) == WAIT_OBJECT_0)
{
printf("DIE EVENT!\n");
break;
}

// Do stuff
// NOTE: Removed division, replaced with modulus
// Division was causing division by zero in no time flat :D
srand(i);
i = rand() + rand() * (rand() + (rand() - rand())) - rand() + (rand() * rand() * rand() - (rand() - rand() * rand()) + rand() * rand() - rand()) - rand() + rand() * rand();
*/
// Yesh

}

InterlockedDecrement(&lThreadsRunning);

return 0;
}


(See FOOTNOTE 2)

Never mind a LOT of the stuff in there. This program WILL NOT TERMINATE. I was origionaly trying to clean everything up after a time-out, but WaitForSingleObject(), even with a 0 wait time, cleared up the very problem I was trying to demonstrate. And no, using WaitForSingleObject() does NOT fix the problem under real-world situations, it just masks it in this demonstraion.

So, don't run this too much. Windows will be very mad at you if you do

Okay... now shut down Task Manager and run this horrid mess with 20 threads. Now try to bring Task Manager back up. Try bringing up any decent size application for that matter. Things get slow. I didn't try this program with 800 threads because, quite frankly, I am affraid that if I did I would be bringing the web server down well before the scheduled maintainance

Bottom Line:
As this demonstration shows, the more active threads you have, the slower things get. This is due in large part to Context Switching Overhead.

Article: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndllpro/html/msdn_threadli.asp

Exerpt:
quote:

Unless the application executes on a multiprocessor machine (in which subcomputations can truly execute in parallel), CPU-bound computations cannot possibly execute faster in multiple threads than in a single thread. This is because the single CPU must still execute all computations, be it in little pieces (in the multithreaded case) or in big chunks (as is the case when the computations are executed one after another in the same thread). As a rule, a given set of computations, when executed in multiple threads, will typically finish later than the same set of computations executed sequentially because there is an overhead incurred in creating the threads and switching the CPU between threads.



Food for thought

FOOTNOTE 1
You can use while(1) loops in your projects, just make sure that all control paths lead to a "break;" statement in a finite amount of loops. This is similar to a for(; loop (notice that there are not evalutation expressions, thus an endless loop), but is in my opinion, quicker to type.

These kinds of loops are useful when repeating a long set of function calls over and over again, conditionaly. If you have an error that can not be tolerated, or if you have ran out of things to do, hit a "break;" statement. If you run into a tolerable error, yet need to disregard the current loop, hit a "continue;" statement.

FOOTNOTE 2
To get this code sample to compile under MSVC 6.0, you will have to set the code generation to Multithreaded mode. To do this, go to Project->Settings->C/C++. In the "Category" combo box, select "Code Generation". In the "Use Runtime Library:" combo box, select "Multithreaded".

Also, this code sample uses the Standard Template Library. If you don't know what it is, you are REALLY missing out. Google for "STL Tutorial".

Tell him about the twinky...

[edited by - QBRADQ on February 27, 2004 8:18:20 AM]

Share this post


Link to post
Share on other sites
Dear QBRADQ:

Thank you for telling me so much valuable
knowledge and experiences.

I''ll make some testing to see how much performance would be
consumed when the the threads are used in different amounts,
and then decide which way to go.

According my tests in Java environment, thread would help in
responding to user when input and paint events are happening at
the same time.

But if you use the "synchronized" keyword may slow down the
performance while many threads of objects are running concurrently.

Finally, it''s my honor to meet you such an expert here.
Thank you for answering me. ^_^

Best regards,
Jackie.

Share this post


Link to post
Share on other sites

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!