Parallel-Luau

*Dieser Inhalt wurde mit KI (Beta) übersetzt und kann Fehler enthalten. Um diese Seite auf Englisch zu sehen, klicke hier.

Mit dem Parallelen Luau -Programmiermodell kannst du Code auf mehreren Threads gleichzeitig ausführen, was die Leistung deines Erlebnisses verbessern kann.Wenn du dein Erlebnis mit mehr Inhalten erweiterst, kannst du dieses Modell übernehmen, um die Leistung und Sicherheit deiner Luau-Skripte zu erhalten.

Modell der parallelen Programmierung

Standardmäßig werden Skripte sequentiell ausgeführt.Wenn deine Erfahrung komplexe Logik oder Inhalte hat, wie z. B. Nicht-Spieler-Charaktere (NPCs), Raycasting-Validierung und prozedurale Generierung, kann die sequenzielle Ausführung Verzögerungen für deine Benutzer verursachen.Mit dem parallelen Modellkönnen Sie Aufgaben in mehrere Skripte aufteilen und sie parallel ausführen.Dies beschleunigt den Lauf deines Erfahrungscodes, was die Erlebnisverbessert.

Das parallele Programmiermodell fügt Ihrem Codesauch Sicherheitsvorteile hinzu.Durch die Aufteilung von Code in mehrere Threads wirkt sich die Bearbeitung von Code in einem Thread nicht auf anderen Code aus, der parallel ausgeführt wird.Dies reduziert das Risiko, dass ein Fehler in deinem Code die gesamte Erlebnisbeschädigt und minimiert die Verzögerung für Benutzer auf Live-Servern, wenn du ein Update pushest.

Die Annahme des parallelen Programmiermodells bedeutet nicht, alles in mehrere Threads zu legen.Zum Beispiel setzt die Server-seitige Raycasting-Validierung jedem einzelnen Benutzer ein Remote-Ereignis parallel, erfordert aber immer noch, dass der ursprüngliche Code seriell ausgeführt wird, um globale Eigenschaften zu ändern, was ein häufiges Muster für die parallele Ausführung ist.

Die meisten Male musst du serielle und parallele Phasen kombinieren, um deine gewünschte Ausgabe zu erzielen, da es derzeit einige Operationen gibt, die nicht parallel unterstützt werden und die Ausführung von Skripten verhindern können, wie das Ändern von Instanzen in parallelen Phasen.Für weitere Informationen über die Ebene der Verwendung von APIs parallel sehen Sie Thread-Sicherheit.

Code in mehrere Threads aufteilen

Um die Skripte deines Erlebnisses in mehreren Threads gleichzeitig auszuführen, musst du sie in logische Chunks unter verschiedenen Schauspielern im Datenmodell aufteilen.Schauspieler werden durch Actor Instanzen repräsentiert, die von DataModel erben.Sie arbeiten als Einheiten der Ausführungsisolation, die die Belastung auf mehrere gleichzeitig laufende Kerne verteilen.

Akteur-Instanzen platzieren

Du kannst Akteure in passende Container legen oder sie verwenden, um die obersten Instanztypen deiner 3D-Entitäten wie NPCs und Strahlenwerfer zu ersetzen, und anschließend die entsprechenden Skripte hinzufügen.

An example of a Script under an Actor

Für die meisten Situationen solltest du einen Akteur nicht als Kind eines anderen Akteurs im Modellplatzieren.Wenn du dich jedoch dafür entscheidest, ein Skript in mehreren Akteuren für deinen spezifischen Anwendungsfall verschachtelt zu platzieren, ist das Skript im Besitz seines nächsten Vorfahren-Akteurs.

A tree of actors and scripts that shows how a script is owned by its closest actor

Desynchronisieren von Threads

Obwohl das Setzen von Skripten unter Akteure ihnen die Fähigkeit zur parallelen Ausführung gewährt, wird der Code standardmäßig immer noch seriell auf einem einzigen Thread ausgeführt, was die Erfüllungnicht verbessert.Du musst die task.desynchronize() aufrufen, eine abrufbare Funktion, die die Ausführung der aktuellen Koroutine für die parallele Ausführung von Code unterbricht und sie bei der nächsten parallelen Ausführungsgelegenheit wieder aufnimmt.Um ein Skript auf die serielle Ausführung zurückzuschalten, rufen Sie task.synchronize() an.

Alternativ kannst du die RBXScriptSignal:ConnectParallel()-Methode verwenden, wenn du einen Signal-Callback planen möchtest, um deinen Code sofort nach dem Auslösen parallel auszuführen.Du musst nicht task.desynchronize() innerhalb des Signal-Callbacks aufrufen.

Synchronisierung eines Threads aufheben

local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Ein paralleler Code, der eine Updateberechnet
task.synchronize()
... -- Einige serielle Code, der den Zustand von Instanzen ändert
end)

Skripte, die Teil desselben Akteurs sind, werden immer nacheinander ausgeführt, so dass Sie mehrere Akteure benötigen.Wenn du beispielsweise alle parallel aktivierten Verhaltensskripte für deinen NPC in einem Akteur platzierst, laufen sie immer noch seriell auf einem einzigen Thread, aber wenn du mehrere Akteure für unterschiedliche NPC-Logik hast, läuft jeder von ihnen parallel auf seinem eigenen Thread.Für weitere Informationen, siehe Best Practices.

Parallelcode in Akteuren, die seriell in einem einzigen Thread laufen
>

Parallelcode in Akteuren, die gleichzeitig in mehreren Threads laufen
>

Thread-Sicherheit

Während der parallelen Ausführung kannst du auf die meisten Instanzen der DataModel Hierarchie wie üblich zugreifen, aber einige API-Eigenschaften und -Funktionen sind nicht sicher zu lesen oder zu schreiben.Wenn du sie in deinem parallelen Codesverwendest, kann die Roblox-Engine diese Zugriffe automatisch erkennen und verhindern.

API-Mitglieder haben ein Thread-Sicherheitslevel, das anzeigt, ob und wie du sie in deinem parallelen Codesverwenden kannst, wie die folgende Tabelle zeigt:

StufeFür EigenschaftenFür Funktionen
Unsicher Kann nicht parallel gelesen oder geschrieben werden.Kann nicht parallel aufgerufen werden.
Parallel lesen Kann gelesen, aber nicht parallel geschrieben werden.N/A
Lokaler Safe Kann innerhalb desselben Akteurs verwendet werden; kann von anderen Actors parallel gelesen, aber nicht geschrieben werden.Kann innerhalb desselben Akteurs aufgerufen werden; kann nicht von anderen Actors parallel aufgerufen werden.
Sicher Kann gelesen und geschrieben werden.Kann abgerufen werden.

Du kannst Thread-Sicherheitstags für API-Mitglieder auf der API-Referenz finden.Wenn du sie verwendest, solltest du auch berücksichtigen, wie API-Aufrufe oder Eigenschaftsänderungen zwischen parallelen Threads interagieren können.Es ist in der Regel sicher, dass mehrere Akteure die gleichen Daten lesen wie andere Akteure, aber den Zustand anderer Akteure nicht ändern.

Cross-Thread-Kommunikation

Im Multithreading-Kontext können Sie Skripts in verschiedenen Akteuren immer noch erlauben, miteinander zu kommunizieren, um Daten auszutauschen, Aufgaben zu koordinieren und Aktivitäten zu synchronisieren.Die Engine unterstützt die folgenden Mechanismen für die Cross-Thread-Kommunikation:

Du kannst mehrere Mechanismen unterstützen, um deine Cross-Thread-Kommunikationsbedürfnisse zu decken.Zum Beispiel können Sie eine gemeinsame Tabelle über das Actor Messaging API senden.

Actor-Messaging

Die Actor-Messaging-API ermöglicht es einem Skript, das. PL: die Skripts, entweder in einem seriellen oder parallelen Kontext, Daten an einen Akteur im selben Modellzu senden.Die Kommunikation über diese API ist asynchron, in der der Sender nicht blockiert, bis der Empfänger die Nachricht erhält.

Wenn Sie Nachrichten mit dieser API senden, müssen Sie ein Thema definieren, um die Nachricht zu kategorisieren.Jede Nachricht kann nur an einen einzelnen Akteur gesendet werden, aber dieser Akteur kann intern mehrere Rückrufe haben, die an eine Nachricht gebunden sind.Nur Skripte, die Nachkommen eines Akteurs sind, können Nachrichten empfangen.

Die API hat die folgenden Methoden:

Das folgende Beispiel zeigt, wie man Actor:SendMessage() verwendet, um ein Thema zu definieren und eine Nachricht an den Absender zu senden:

Beispiel-Nachrichten-Sender

local Workspace = game:GetService("Workspace")
-- Sende zwei Nachrichten an den Arbeiter-Akteur mit dem Thema "Begrüßung"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hello World!")
workerActor:SendMessage("Greeting", "Welcome")
print("Sent messages")

Das folgende Beispiel zeigt, wie man Actor:BindToMessageParallel() verwendet, um einen Rückruf für ein bestimmtes Thema in einem parallelen Kontext auf der beendenzu binden:

Beispiel-Nachrichtenempfänger

-- Holen Sie sich den Akteur, der diesem Skript zugewiesen ist
local actor = script:GetActor()
-- Binden Sie einen Rückruf für das Thema "Begrüßung"-Nachricht
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")

Geteilte Tabelle

SharedTable ist eine tabellartige Datenstruktur, die von Skripten zugänglich ist, die unter mehreren Akteuren laufen.Es ist nützlich für Situationen, die eine große Menge an Daten beinhalten und einen gemeinsamen gemeinsamen Zustand zwischen mehreren Threads erfordern.Zum Beispiel, wenn mehrere Akteure an einem gemeinsamen Weltzustand arbeiten, der nicht im Modellgespeichert ist.

Das Senden einer gemeinsamen Tabelle an einen anderen Akteur erstellt keine Kopie der Daten.Stattdessen ermöglichen gemeinsame Tabellen sichere und atomare Aktualisierungen durch mehrere Skripte gleichzeitig.Jedes Update einer gemeinsamen Tabelle durch einen Akteur ist sofort für alle Akteure sichtbar.Gemeinsame Tabellen können auch in einem ressourceneffizienten Prozess geklont werden, der die strukturelle Freigabe verwendet, anstatt die zugrunde liegenden Daten zu kopieren.

Direkte Datenmodell-Kommunikation

Du kannst auch die Kommunikation zwischen mehreren Threads direkt mit dem Modellerleichtern, in dem verschiedene Akteure Eigenschaften oder Attribute schreiben und anschließend lesen können.Um jedoch die Thread-Sicherheit zu gewährleisten, können Skripte, die parallel ausgeführt werden, im Allgemeinen nicht auf das Modellschreiben.Die direkte Verwendung des Datenmodells für die Kommunikation kommt also mit Einschränkungen und kann Skripte dazu zwingen, häufig zu synchronisieren, was die Leistung Ihrer Skripte beeinträchtigen kann.

Beispiele

Serverseitige Raycasting-Validierung

Für ein Kampf- und Erlebnismusst du Raycasting für die Waffen deiner Benutzer aktivieren.Mit der Client-Simulation der Waffen, um eine gute Latenz zu erreichen, muss der Server den Treffer bestätigen, was Raycasts und eine gewisse Menge an Heuristiken beinhaltet, die die erwartete Zeichengeschwindigkeit berechnen und das vergangene Verhalten betrachten.

Anstatt ein einzelnes zentralisiertes Skript zu verwenden, das sich mit einem entfernten Ereignis verbindet, das Clients verwenden, um Trefferinformationen zu kommunizieren, kannst du jeden Treffer-Validierungsprozess auf der Serverseite parallel ausführen, wobei jeder Benutzercharakter ein separates Remote-Ereignis hat.

Das Server-Skript, das unter dem Charakter Actor läuft, verbindet sich mit diesem Remote-Ereignis über eine parallele Verbindung, um die relevante Logik auszuführen, um den Treffer zu bestätigen.Wenn die Logik eine Bestätigung eines Treffers findet, wird der Schaden abgezogen, was die Änderung von Eigenschaften beinhaltet, so dass er zunächst seriell ausgeführt wird.


local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Erstelle ein neues Remote-Ereignis und übergebe es dem Tool
remoteEvent.Name = "RemoteMouseEvent" -- Benennen Sie es um, damit das lokale Skript danach suchen kann
remoteEvent.Parent = tool
local remoteEventConnection -- Erstelle eine Referenz für die Remote-Ereignisverbindung
-- Funktion, die auf ein Remote-Ereignis hört
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIAL: Führen Sie Setup-Code in Serie aus
local character = player.Character
-- Ignoriere den Charakter des Benutzers beim Raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Führen Sie den Raycast parallel aus
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Wird verwendet, um den Strahl leicht zu erweitern, da die Klickposition möglicherweise leicht von dem Objekt abweicht
local lookDirection = (1 + epsilon) * (clickLocation.Position - origin)
local raycastResult = Workspace:Raycast(origin, lookDirection, params)
if raycastResult then
local hitPart = raycastResult.Instance
if hitPart and hitPart.Name == "block" then
local explosion = Instance.new("Explosion")
-- SERIAL: Der Code unten modifiziert den Zustand außerhalb des Akteurs
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Mache die Explosion nicht tödlich
explosion.Position = clickLocation.Position
-- Mehrere Akteure könnten dasselbe Teil in einem Raycast erhalten und beschließen, es zu zerstören
-- Dies ist perfekt sicher, aber es würde zu zwei Explosionen auf einmal statt zu einer führen
-- Die folgenden doppelten Überprüfungen, dass die Ausführung zuerst zu diesem Teil gelangt ist
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Zerstöre es
end
end
end
end
-- Verbinde das Signal zunächst seriell, da einige Einrichtungscode nicht parallel ausgeführt werden kann
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Serverseitige prozedurale Gelgenerierung

Um eine riesige Welt für deine Erlebniszu erstellen, kannst du die Welt dynamisch bevölkern.Die prozedurale Generierung erstellt in der Regel unabhängige Terrain-Chunks, wobei der Generator relativ komplizierte Berechnungen für die Platzierung von Objekten, den Materialeinsatz und die Voxelfüllung durchführt.Die parallele Ausführung von Generierungscode kann die Effizienz des Prozesses verbessern.Das folgende Codebeispiel dient als Beispiel.


-- Die parallele Ausführung erfordert den Einsatz von Akteuren
-- Dieses Skript klont sich selbst; das Original startet den Prozess, während die Klone als Arbeiter fungieren
local Workspace = game:GetService("Workspace")
local actor = script:GetActor()
if actor == nil then
local workers = {}
for i = 1, 32 do
local actor = Instance.new("Actor")
script:Clone().Parent = actor
table.insert(workers, actor)
end
-- Alle Akteure unter sich selbst als Eltern
for _, actor in workers do
actor.Parent = script
end
-- Weise den Akteuren an, Terrain zu generieren, indem sie Nachrichten senden
-- In diesem Beispiel werden Akteure zufällig ausgewählt
task.defer(function()
local rand = Random.new()
local seed = rand:NextNumber()
local sz = 10
for x = -sz, sz do
for y = -sz, sz do
for z = -sz, sz do
workers[rand:NextInteger(1, #workers)]:SendMessage("GenerateChunk", x, y, z, seed)
end
end
end
end)
-- Verlassen des ursprünglichen Skript, das. PL: die Skripts; der Rest des Codes wird in jedem Akteur ausgeführt
return
end
function makeNdArray(numDim, size, elemValue)
if numDim == 0 then
return elemValue
end
local result = {}
for i = 1, size do
result[i] = makeNdArray(numDim - 1, size, elemValue)
end
return result
end
function generateVoxelsWithSeed(xd, yd, zd, seed)
local matEnums = {Enum.Material.CrackedLava, Enum.Material.Basalt, Enum.Material.Asphalt}
local materials = makeNdArray(3, 4, Enum.Material.CrackedLava)
local occupancy = makeNdArray(3, 4, 1)
local rand = Random.new()
for x = 0, 3 do
for y = 0, 3 do
for z = 0, 3 do
occupancy[x + 1][y + 1][z + 1] = math.noise(xd + 0.25 * x, yd + 0.25 * y, zd + 0.25 * z)
materials[x + 1][y + 1][z + 1] = matEnums[rand:NextInteger(1, #matEnums)]
end
end
end
return {materials = materials, occupancy = occupancy}
end
-- Binden Sie den Rückruf in den parallelen Ausführungskontext ein
actor:BindToMessageParallel("GenerateChunk", function(x, y, z, seed)
local voxels = generateVoxelsWithSeed(x, y, z, seed)
local corner = Vector3.new(x * 16, y * 16, z * 16)
-- Derzeit muss WriteVoxels() in der seriellen Phase aufgerufen werden
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)

Best Practices

Um die maximalen Vorteile der parallelen Programmierung anzuwenden, beachten Sie die folgenden Best Practices, wenn Sie Ihren Codeshinzufügen:

  • Vermeiden Sie lange Berechnungen — Selbst parallel können lange Berechnungen die Ausführung anderer Skripte blockieren und Verzögerungen verursachen.Vermeiden Sie die parallele Programmierung, um eine große Menge langer, unnachgiebiger Berechnungen zu verarbeiten.

    Diagram demonstrating how overloading the parallel execution phase can still cause lag
  • Verwende die richtige Anzahl von Akteuren — Für die beste Erfüllungverwende mehr Actors.Selbst wenn das Gerät weniger Kerne als Actors hat, ermöglicht die Granularität ein effizienteres Lastausgleich zwischen den Kernen.

    Demonstration of how using more actors balances the load across cores

    Das bedeutet nicht, dass du so viele Actors wie möglich verwenden solltest.Du solltest Code immer noch in Actors auf der Grundlage von Logikeinheiten aufteilen, anstatt Code mit verbundener Logik auf verschiedene Actors zu brechen.Wenn du beispielsweise Strahlcasting-Validierung parallel aktivieren möchtest, ist es sinnvoll, 64 Actors statt nur 4 zu verwenden, auch wenn du 4-Kern-Systeme anvisierst.Dies ist wertvoll für die Skalierbarkeit des Systems und ermöglicht es, die Arbeit basierend auf der Fähigkeit der zugrunde liegenden Hardware zu verteilen.Du solltest jedoch auch nicht zu viele Actors verwenden, die schwer zu pflegen sind.