Why detach the game's render thread from the logical thread?
In-game rendering is a very time-consuming operation, especially for relatively complex games, where rendering usually takes up most of the time in a frame. and high-quality games will require FPS at 60, so a frame of time is only 16 milliseconds.
If you want to accomplish both logical and rendering tasks in 16 milliseconds, it is often difficult for a large game, even in extreme optimizations, to be able to meet better-performing devices, and it is almost impossible to accomplish all tasks in less than 16 milliseconds on poorly performing devices.
So if you detach the render thread from the logical thread, in theory, they each have 16 milliseconds to complete their task, because they are in parallel so that there is more time for rendering and logical execution.
In a single thread, we usually start with a logical task and then render, because the rendered data often relies on the result of a logical calculation.
Now split into two threads, we still need the logical line enters upgradeable to complete the task, and then render the thread to start the task of rendering, so this involves thread synchronization.
Using the conditional variables in c++11, the problem of synchronizing logic threads and rendering threads in a game is implemented.
Conditional variables are a common method of multithreading synchronization:
Std::condition_variable in C + + can be used to declare a condition variable, and his core approach is to wait:
void Wait( std::unique_lock<std::Mutex>& Lock ) ;
void Wait( std::unique_lock<std::Mutex> & Lock, predicate pred );
Parameter 1 is a unique_lock type of lock, used to lock a mutex, parameter 2 is usually a return bool worthwhile function.
If there is only the first parameter, then the wait method will attempt to lock the mutex when it is awakened, if the mutex is not reached, it will block the thread, and if it succeeds in obtaining the mutex, the thread will continue to execute and the mutex should be freed after the execution of the thread, that is, unlocking.
If there is a second parameter, then the wait method will be woken up to check whether the condition is satisfied, that is, whether the second parameter's return value is true, if the condition is satisfied, locked, continues the execution of the thread, if the condition is not met, blocks the thread, and unlocks, that is, to yield the mutex.
Examples are as follows:
To facilitate detection, FPS is a frame per second, rendering tasks and logical tasks also through the sleep function to simulate the execution time, are 1 seconds, through the printing can be seen, the logic and rendering threads of the task parallel execution, rendering thread statistics The consumption time is about 1 seconds.
The whole process is basically:
1. The main thread is the render thread, which starts with an empty render, because the logical task has not yet begun to execute, i.e. there is nothing to render;
2. The first empty rendering is not blocked, and the render thread wakes up the logical thread before performing a real rendering task;
3. The rendering task starts executing, and the logical task starts executing in parallel;
4. Finish the rendering task and check if the next render time is coming. If there is no arrival, the short time sleep waits for the rendering time to arrive;
5. When the rendering time arrives, the blocking check condition variable satisfies the condition before starting a true rendering task, that is, whether the logical task is complete. If the logical task is not completed, blocking waits for the logical thread to wake up. If the logical task is complete, proceed back to the 2 process;
If you change the execution time of a logical task to 2 seconds, the rendering thread will count for 2 seconds. Because every logical task is prepared for the next frame render, the render thread waits for the logical task to finish before the next render, so the time that the render thread counts is the maximum of the time that the logical and render threads spend, and if in a single thread, the time spent will be the sum of the logical and render execution time.
//ThreadTest3.cpp: Defines the entry point of the console application. //#include"stdafx.h"#include<mutex>#include<condition_variable>#include<chrono>#include<thread>using namespaceStd;mutex LOGIC_MT;//mutex, locked during the execution of a logical task, and the next render task must be blocked and waiting to be awakened before unlockingCondition_variable LOGIC_CV;//a conditional variable for a logical thread that is awakened by the rendered thread when the condition is metCondition_variable RENDER_CV;//the conditional variable of the render thread, which is awakened by the logical thread when the condition is metBOOLTo_do_logic =false;//true indicates a condition that satisfies the execution of a logical task, False indicates an unsatisfiedBOOLTo_do_render =false;//true to satisfy the condition that the render task was performed, false to indicate that it is not satisfied//Logical ThreadsvoidLogic_thread_proc () { while(true) {printf ("_____________ logic begin \ n"); //blocking, waiting for timed wakeup of the render threadUnique_lock<mutex>Lock(LOGIC_MT); Logic_cv.wait (Lock, [](){ returnto_do_logic; }); To_do_logic=false;//This condition can only be reset by the render thread//Logical Tasks ... This_thread::sleep_for (Chrono::microseconds (1000000)); printf ("_____________ logic \ n"); //logical task complete, wake-up render threadTo_do_render =true; Lock. Unlock (); Render_cv.notify_one (); printf ("_____________ logic end \ n"); }}//Main Threadint_tmain (intARGC, _tchar*argv[]) {Thread Logic_thread (LOGIC_THREAD_PROC); //Render line enters upgradeable start, drive logical threadTo_do_render =true; Chrono::system_clock::time_point _clock=Chrono::high_resolution_clock::now (); while(true) { Long LongMicrosec = Chrono::d uration_cast<chrono::microseconds> (Chrono::high_resolution_clock::now ()-_clock). Count (); //fps is one frame per second if(Microsec >=1000000) {_clock=Chrono::high_resolution_clock::now (); printf ("_____________ render begin \ n"); //blocking, waiting for the logical thread to complete the taskUnique_lock<mutex>Lock(LOGIC_MT); Render_cv.wait (Lock, [](){ returnTo_do_render; }); To_do_render=false;//the condition can only be reset by a logical thread Lock. Unlock (); //Wake logical threads, parallel logical tasks for next frameTo_do_logic =true; Logic_cv.notify_one (); //Render Task ... This_thread::sleep_for (Chrono::microseconds (1000000)); printf ("_____________ render%lld \ n", microsec); //If you wake up here, the rendering task and the logical task will not be parallel, but the serial//to_do_logic = true; //Logic_cv.notify_one ();printf ("_____________ render end \ n"); } Else{this_thread::sleep_for (Chrono::milliseconds (0));//temporarily hibernate, give up CPU } } return 0;}
Parallel logical threads and logical threads in the game