Multithreading in Game Engines

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von Andy.

    Multithreading in Game Engines

    Hallo, nachdem ich nun 3D Animationen und die Physik in meiner Engine fertig gestellt habe, würde ich mich gerne um den nächsten Schritt kümmern. Hierbei geht es um Multithreading. Ich habe mich dazu etwas in diverse Literatur eingelesen und das ganze so verstanden, dass ich einen Thread für das Updaten und einen Thread für das Rendern benötige. Diese werden dann am ende Synchronisiert. Nun gibt es ja mehrere Möglichkeiten um Threads in C# zu Synchronisieren. Zum einem gibt es die Möglichkeit mit Join(), was meiner Meinung nach nicht wirklich viel Sinn macht in einer Game Engine, da beide Threads nacheinander ausgeführt werden, was im Endeffekt keine Auswirkung auf die Performance hätte.

    Aus diesem Grund habe ich mich für AutoResetEvent entschieden, welche ich für alle Threads nutze und im Main Thread dann warte bis diese gesetzt wurden um das neue Frame zu beginnen. Dies würde bedeuten beide Threads werden unabhängig parallel zueinander ausgeführt. Es bedeutet aber auch, dass der Scene Tree mit den Entitys erst nach dem die Threads synchronisiert wurden manipuliert werden darf. Sprich neue Entitys hinzufügen oder alte Entfernen. Ich habe hier mal ein kleines beispiel erstellt und würde gerne eurer Meinung dazu erfahren, ob man das ganze so machen könnte und wo es zu Problemen kommen könnte.

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.ComponentModel;
    4. using System.Data;
    5. using System.Drawing;
    6. using System.Linq;
    7. using System.Text;
    8. using System.Threading;
    9. using System.Threading.Tasks;
    10. using System.Windows.Forms;
    11. namespace MTTest
    12. {
    13. public partial class Form1 : Form
    14. {
    15. AutoResetEvent updateThread = new AutoResetEvent(false);
    16. AutoResetEvent renderThread = new AutoResetEvent(false);
    17. //AutoResetEvent mainLoopEvent = new AutoResetEvent(false);
    18. public Form1()
    19. {
    20. InitializeComponent();
    21. }
    22. /// <summary>
    23. /// Starten der main loop
    24. /// </summary>
    25. /// <param name="sender"></param>
    26. /// <param name="e"></param>
    27. private void button1_Click(object sender, EventArgs e)
    28. {
    29. Thread thread = new Thread(new ThreadStart(MainLoop));
    30. thread.Start();
    31. }
    32. /// <summary>
    33. /// Simulieren des Frame rendern und updaten
    34. /// </summary>
    35. private void MainLoop()
    36. {
    37. int i = 0;
    38. while (i < 10)
    39. {
    40. Console.WriteLine("*******************************************");
    41. Console.WriteLine("New Frame");
    42. Console.WriteLine("*******************************************");
    43. DateTime start = DateTime.Now;
    44. Thread ut = new Thread(new ThreadStart(UpdateThread));
    45. Thread rt = new Thread(new ThreadStart(RenderThread));
    46. ut.Start();
    47. rt.Start();
    48. updateThread.WaitOne();
    49. renderThread.WaitOne();
    50. // Hier sind beide threads mit ihrer arbeit fertig
    51. // und es könnten manipulationen an den GameElements durchgeführt werden.
    52. // um diese für das nächste frame zu haben
    53. DateTime end = DateTime.Now;
    54. var span = end - start;
    55. Console.WriteLine(span.Milliseconds);
    56. i++;
    57. }
    58. }
    59. /// <summary>
    60. /// Updaten der Scene
    61. /// </summary>
    62. private void UpdateThread()
    63. {
    64. Console.WriteLine("Update Thread");
    65. for (int i = 0; i < 10; i++)
    66. {
    67. Console.WriteLine("Update " + i.ToString());
    68. }
    69. Thread.Sleep(30);
    70. Console.WriteLine("Update Done!");
    71. updateThread.Set();
    72. //renderThread.WaitOne();
    73. }
    74. /// <summary>
    75. /// Rendern der Scene
    76. /// </summary>
    77. private void RenderThread()
    78. {
    79. Console.WriteLine("Render Thread");
    80. for (int i = 0; i < 10; i++)
    81. {
    82. Console.WriteLine("Render " + i.ToString());
    83. }
    84. Thread.Sleep(10);
    85. Console.WriteLine("Render Done!");
    86. renderThread.Set();
    87. //updateThread.WaitOne();
    88. //mainLoopEvent.Set();
    89. }
    90. }
    91. }


    beste Grüße

    Andy
    Meine Projekte Genesis Game Engine | GFX | smartli.me - Der smarte URL shortener

    Ja die Struktur ist sehr sub optimal. Das habe ich bereits gemerkt. Das Problemm mit dieser Struktur wäre sowieso das OpenGL nur in einem Thread rendern kann. Das heißt jedes mal ein neuen Render Thread erstellen würde eh nicht gehen. Habe mir nun überlegt, das ich erstmal nur das Updaten und die Physik mit einem Separaten Thread mache. Für das Updaten der Layer würde ich wohl eine Parallel Foreach nutzen.

    Meine Projekte Genesis Game Engine | GFX | smartli.me - Der smarte URL shortener

    Also Multithreading würde ich hauptsächlich zum Laden anwenden, so kann man gut einen Fortschritt anzeigen, auch Intro Videos und Animationen ruckeln dann nicht(nicht so). Dabei unbedingt auf Threadsicherheit achten:

    https://learn.microsoft.com/de-de/dotnet/standard/collections/thread-safe/when-to-use-a-thread-safe-collection




    Den Renderthread würde ich nicht schlafen lassen, genausowenig einen Thread indem irgendwas geupdated wird. Will der User die Frequenz seines Bildschirms, nutze VSync und lass das den User ein/aus-schalten.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D
    Hat das einen bestimmten Grund, dass du innerhalb deines Threads wieder mehrere Threads in einer Schleife aufrufst?
    Threads in dotnet sind ziemlich teuer, wenn du sie so erstellst.

    Nimm lieber den ThreadPool und lass das Betriebssystem sich drum kümmern.

    Zum synchronisieren kannst du ein ganzes einfaches Lock-Objekt nutzen.
    Die beiden zusammen, dann kannst du ein ziemlich robustes System nutzen.
    Quellcode lizensiert unter CC by SA 2.0 (Creative Commons Share-Alike)

    Meine Firma: Procyon Systems

    Selbstständiger Softwareentwickler & IT-Techniker.

    Andy schrieb:

    Es geht eher darum diverse aufgaben parallel zu machen, statt nacheinander.
    Hast Du mal über Parallel.For() oder Parallel.ForEach() nachgedacht?
    Da gehst Du einfach in eine Prozedur mit einem Index bzw. Item rein und tust abhängig davon was zu tun ist.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!