2
\$\begingroup\$

I'm kinda new to C++ Multithreading, and I'm keeping it simple right now. I will keep things simple and write psuedo-code. I'm making a Turn Based Strategy Game but let's keep it at Chess for now.

I have a basic draw thread and update thread.

function DrawThread
    CurrentScreen Drawn
end

function LogicThread
    CurrentScreen Update
end 

Both of these functions run in their seperate threads. Now I've run into a few problems.

For example: My pieces (units) are stored in a vector in the CurrentScreen

Class MapScreen
public:
    GetPieces returns VectorOfpieces

private:
    VectorOfpieces
end

The draw thread constantly draws the Chess pieces.

function Drawn
    for each chesspiece in VectorOfpieces
        draw chesspiece
    end
end

I have a cursor that moves grid-based and on, for example, ENTER checks if there is a Chess piece underneath it. (I've omitted any other code that has nothing to do with it)

function PollInput
    if KeyPressed is ENTER
        CheckUnderNeath
    end
end

function CheckUnderNeath
    for each chesspiece in MapScreen.GetPieces
        if chesspiece position is cursor position
            return chesspiece
        end
    end
end

Now as you might have guessed this Cursor update is in the other thread

function Update
    Cursor PollInput
end

And as you might have seen coming miles away, both threads will eventually end up using the same vector at the same time, causing a crash.

Is there any easy implementation to fix a problem like this, as my 2-3 problems right now are eventually down to this problem, threads bumping heads.

What bothers me is this crash isn't consistant. Because it's a vector I will get a "map/set iterator not dereferencable" error.

I'd appreciate a C++ answer eventhough my question was in Psuedocode. Thanks in advance!

TL:DR One thread tries to update the position of the same pointer the other tries to draw.

\$\endgroup\$
  • \$\begingroup\$ You don't seem to mention anything about mutexes. It's a feature to have only one thread access a resource at a time. \$\endgroup\$ – Alexandre Vaillancourt Jul 27 '16 at 15:37
  • 1
    \$\begingroup\$ There are ways to fix this specific issue for this specific scenario, but there are also ways to better make use of threads that avoid some of these problems entirely. A big part of effectively leveraging multithreading is understanding when using it doesn't make sense because of data sharing and contention issues. Which type of solution are you more interested in here? \$\endgroup\$ – Josh Jul 27 '16 at 15:47
  • \$\begingroup\$ @JoshPetrie I'm highly interested in just avoiding these problems entirely. \$\endgroup\$ – GigaNova Jul 27 '16 at 15:50
  • \$\begingroup\$ The related questions sidebar has quite a few other questions you'll probably want to browse. This one in particular is possibly almost a duplicate. \$\endgroup\$ – Josh Jul 27 '16 at 17:33
4
\$\begingroup\$

When you have multiple threads that need to access a shared bit of data, you can wrap that access in a thread synchronization primitive, such as a mutex. While one thread has access to the data via the primitive, other threads will be blocked from acquiring the access and will wait. Thus, you prevent scenarios where two threads are writing to data, or one is writing while one is reading, et cetera.

(C++ containers in particular are not generally thread-safe on their own, concurrent access and modification must be synchronized, or you'll get all kinds of problems like invalidated iterators in other thread leading to crashes.)

However in the worst-case scenario this can completely eliminate any point to threads at all, because they will simply execute serially waiting for the requisite access. Generally, when you find yourself needing to write a bunch of locks around data access, you should ask yourself if you're really spreading your work out across threads ideally.

In this case I don't think you are. You have three tasks: updating the game, rendering, and collecting input. All of these tasks have significant dependencies on each. You can't update without current input (and there is little reason to update more than once based on prior input). You don't need to render unless there's new state to display (issues related to decoupling render and update rates notwithstanding).

Putting each of these tasks on its own thread isn't a very good use of concurrency. Not only do they all depend on eachother serially, but there's on three of them, so the architecture isn't scalable. You achieve maximum theoretical throughput at three cores. With fewer cores, you suffer an active penalty because at least one task must always be blocked. With more cores, you simply waste the processing power since you can't do more than three things.

A better approach is probably to break each task into as many concurrent chunks of work as possible. Go "wide" within each task, but continue to execute each task serially.

This involves identifying, for each kind of task, isolated groups of sub-tasks that don't rely on eachother. This might be updating sets of objects that exists in logical "islands" (all of them are near eachother, but far enough away from everything else that they could only possibly interact with eachother), or updating all physics on one thread while you update all lifetime/countdown timers on another. Or grouping renderable objects into batches of 25 obejcts that you farm out to threads to update animations for prior to drawing.

Basically, identify functions that do not interact directly or share data, and make those your tasks that you farm out to as many cores as are available.

This addresses both problems. You don't need locks because nothing shares data, so they can't read/write or write/write or anything. It scales better: you can group these independent tasks onto threads based on how many threads are actually reasonable for the actual machine (based on core count), so you can always attempt to maximize throughput.

\$\endgroup\$
1
\$\begingroup\$

I think one of the core reasons to use multithreading is extra speed you can get, but that not always happens ! Choosing multithreading leads to a bunch of problems, one of them is asynchronized shared access to some data between multiple threads. This is the problem you are facing now. A simple solution is the usage of mutexes. A good explanation . But in your case I would just get rid of all the threading you have. It is not needed. Using multithreading to separate game logic is pointless. You won't get anything but time loss. Write your game first, then if you have any performance issues you can take a look at multithreading and such.

\$\endgroup\$
0
\$\begingroup\$

Multithreading wont really improve efficiency. With gl commands, the biggest bottleneck comes when issuing draw calls to the GPU (vbos being filled). This doesnt mean the GPU will struggle drawing, but the CPU will have problems sending all the objects' information to it quickly. Multithreading wont solve that, but it might 'feel' like its an ideal solution to get a hang of. In fact, im guessing it will only cause problems, and uneccessary overhead.

What you want to look at are ways to reduce the amount of data being sent from the CPU to the GPU (if you worry about efficiency) this will be the biggest bottleneck further down the line. Calls such as gldrawelements() and gldrawlementsinstanced() (or instanced drawing) is what you should look at, rather than thinking of ways to make the engine run multithreaded. Like in my engine, the cpu cycle finishes in less than 2 milliseconds, leaving it to idle 3ms, and still being updated every 5ms, thats 200 fps, without a problem. The engine is rarely the bottleneck, and it wont be solved by multithreading.

When you multithread, its the same as running 2 seperate programs. The CPU doesnt know that however, and while the 2 exes will run seperately, they each still send tasks to the CPU for it to solve, much like they would have if they were running as functions being called every now and then.

Your game shouldnt be using 100% cpu ever. Lying between 0.5% 10% is more than enough to know that you havent done something horribly wrong.

So take my advice, and dont worry about multithreading to seperate your engine and draw calls (which I suspect is what your trying to do). I use freegluts glutTimerFunc() and have it run an engine cycle and a draw cycle. I combine that with this stopwatch class:

class stopwatch
{
    double PCFreq = 0.0;
    __int64 CounterStart = 0;
    LARGE_INTEGER li;

public:

    void StartCounter()
    {
        if (!QueryPerformanceFrequency(&li))ExitProcess(0);
        PCFreq = double(li.QuadPart) / 1000.0;
        QueryPerformanceCounter(&li); CounterStart = li.QuadPart;
    }

    double GetCounter() { QueryPerformanceCounter(&li); return double(li.QuadPart - CounterStart) / PCFreq; }
};

stopwatch drawclock, pushclock;

I use them like this in my push/draw functions:

//make sure calculations are done before waiting
Sleep(3); //leave 2ms for calculations (more than enough)
while (pushclock.GetCounter() <= 5) {};
//cout << pushclock.GetCounter() << endl; //cout timer for each pushcycle
pushclock.StartCounter();

This will delay each cycle of the engine to 5ms and sleep when in between, to avoid the CPU from running away.

I think this is the problem your facing, and this is how I solve it; by using freeglut (which is great, dont let its old website fool you, its updated still) and the stopwatch class to regulate the cycles.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.