Parallel Luau

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

Mit dem Parallel Luau-Programmiermodell können Sie Code gleichzeitig auf mehreren Threads ausführen, was die Leistung Ihres Spiels verbessern kann. Wenn Sie Ihr Spiel mit mehr Inhalten erweitern, können Sie dieses Modell übernehmen, um die Leistung und Sicherheit Ihrer Luau-Skripte aufrechtzuerhalten.

Parallelprogrammierungsmodell

Standardmäßig werden Skripte sequenziell ausgeführt. Wenn Ihr Spiel komplexe Logik oder Inhalte hat, wie z. B. Nicht-Spieler-Charaktere (NPCs), Raycasting-Validierung und prozedurale Generierung, kann die sequenzielle Ausführung zu Verzögerungen für Ihre Benutzer führen. Mit dem Parallelprogrammierungsmodell können Sie Aufgaben in mehrere Skripte aufteilen und diese parallel ausführen. Dadurch läuft Ihr Spielcode schneller, was die Benutzererfahrung verbessert.

Das Parallelprogrammierungsmodell bietet auch Sicherheitsvorteile für Ihren Code. Durch das Aufteilen des Codes in mehrere Threads hat das Bearbeiten von Code in einem Thread keine Auswirkungen auf anderen Code, der parallel ausgeführt wird. Dies verringert das Risiko, dass ein Fehler in Ihrem Code das gesamte Spiel beschädigt, und minimiert die Verzögerung für Benutzer auf Live-Servern, wenn Sie ein Update bereitstellen.

Die Übernahme des Parallelprogrammierungsmodells bedeutet nicht, alles in mehrere Threads zu packen. Zum Beispiel setzt die serverseitige Raycasting-Validierung für jeden einzelnen Benutzer ein Remote-Event parallel, erfordert jedoch, dass der ursprüngliche Code seriell ausgeführt wird, um globale Eigenschaften zu ändern, was ein gängiges Muster für die parallele Ausführung ist.

In den meisten Fällen müssen Sie serielle und parallele Phasen kombinieren, um das gewünschte Ergebnis zu erzielen, da derzeit einige Operationen in parallel nicht unterstützt werden, die verhindern können, dass Skripte ausgeführt werden, wie z. B. das Modifizieren von Instanzen in parallelen Phasen. Für weitere Informationen über die Nutzung von APIs in parallel, siehe Thread-Sicherheit.

Code in mehrere Threads aufteilen

Um die Skripte Ihres Spiels gleichzeitig in mehreren Threads auszuführen, müssen Sie sie in logische Teile unter verschiedenen Schauspielern im Datenmodell aufteilen. Schauspieler werden durch Actor-Instanzen dargestellt, die von DataModel erben. Sie fungieren als Einheiten der Ausführungsisolierung, die die Last auf mehrere Kerne verteilen, die gleichzeitig laufen.

Schauspielerinstanzen platzieren

Sie können Schauspieler in geeignete Container setzen oder sie verwenden, um die obersten Instanztypen Ihrer 3D-Entitäten wie NPCs und Raycaster zu ersetzen, und dann entsprechende Skripte hinzufügen.

Ein Beispiel für ein Skript unter einem Schauspieler

In den meisten Situationen sollten Sie einen Schauspieler nicht als Kind eines anderen Schauspielers im Datenmodell platzieren. Wenn Sie sich jedoch entscheiden, ein Skript innerhalb mehrerer Schauspieler für Ihren spezifischen Anwendungsfall zu verschachteln, gehört das Skript dem nächstgelegenen Vorfahren-Schauspieler.

Ein Baum von Schauspielern und Skripten, der zeigt, wie ein Skript dem nächstgelegenen Schauspieler gehört

Threads desynchronisieren

Obwohl das Platzieren von Skripten unter Schauspielern ihnen die Fähigkeit zur parallelen Ausführung verleiht, wird der Code standardmäßig immer noch auf einem einzelnen Thread seriell ausgeführt, was die Laufzeitleistung nicht verbessert. Sie müssen task.desynchronize() aufrufen, eine yieldbare Funktion, die die Ausführung des aktuellen Koroutinen für die parallele Ausführung von Code aussetzt und sie bei der nächsten parallelen Ausführungsmöglichkeit wieder aufnimmt. Um ein Skript wieder in die serielle Ausführung zu wechseln, rufen Sie task.synchronize() auf.

Alternativ können Sie die Methode RBXScriptSignal:ConnectParallel() verwenden, wenn Sie einen Signal-Callback planen möchten, um Ihren Code sofort bei Auslösung parallel auszuführen. Sie müssen task.desynchronize() nicht innerhalb des Signal-Callbacks aufrufen.

Thread desynchronisieren
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Einige parallele Codes, die eine Statusaktualisierung berechnen
task.synchronize()
... -- Einige serielle Codes, die den Status von Instanzen ändern
end)

Skripte, die Teil desselben Schauspielers sind, werden immer sequenziell in Bezug aufeinander ausgeführt, sodass Sie mehrere Schauspieler benötigen. Wenn Sie beispielsweise alle parallelfähigen Verhaltensskripte für Ihren NPC in einem Schauspieler platzieren, werden sie immer noch seriell auf einem einzelnen Thread ausgeführt, aber wenn Sie mehrere Schauspieler für unterschiedliche NPC-Logik haben, wird jeder von ihnen parallel auf seinem eigenen Thread ausgeführt. Für weitere Informationen siehe Best Practices.

Paralleler Code in Schauspielern, der seriell auf einem einzelnen Thread ausgeführt wird
Paralleler Code in Schauspielern, der gleichzeitig in mehreren Threads ausgeführt wird

Thread-Sicherheit

Während der parallelen Ausführung können Sie auf die meisten Instanzen der DataModel-Hierarchie wie gewohnt zugreifen, aber einige API-Eigenschaften und -Funktionen sind nicht sicher zu lesen oder zu schreiben. Wenn Sie sie in Ihrem parallelen Code verwenden, kann die Roblox-Engine diese Zugriffe automatisch erkennen und verhindern.

API-Mitglieder haben ein Thread-Sicherheitsniveau, das angibt, ob und wie Sie sie in Ihrem parallelen Code verwenden können, wie die folgende Tabelle zeigt:

SicherheitsniveauFür EigenschaftenFür Funktionen
UnsicherKann in parallel nicht gelesen oder geschrieben werden.Kann in parallel nicht aufgerufen werden.
Parallel lesenKann in parallel gelesen, aber nicht geschrieben werden.N/A
Lokal sicherKann innerhalb desselben Schauspielers verwendet werden; kann von anderen Actors in parallel gelesen, aber nicht geschrieben werden.Kann innerhalb desselben Schauspielers aufgerufen werden; kann von anderen Actors in parallel nicht aufgerufen werden.
SicherKann gelesen und geschrieben werden.Kann aufgerufen werden.

Sie finden Thread-Sicherheitskennzeichnungen für API-Mitglieder in der API-Referenz. Bei der Verwendung sollten Sie auch berücksichtigen, wie API-Aufrufe oder Eigenschaftsänderungen zwischen parallelen Threads interagieren könnten. In der Regel ist es sicher, dass mehrere Schauspieler dieselben Daten wie andere Schauspieler lesen, aber nicht den Zustand anderer Schauspieler ändern.

Kommunikation zwischen Threads

Im Kontext des Multithreadings können Sie Skripten in verschiedenen Schauspielern weiterhin 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 Kommunikation zwischen Threads:

Sie können mehrere Mechanismen unterstützen, um Ihren Kommunikationsbedarf zwischen Threads zu berücksichtigen. Beispielsweise können Sie eine geteilte Tabelle über die Schauspieler-Nachrichten-API senden.

Schauspieler-Nachrichten

Die Schauspieler-Nachrichten-API ermöglicht es einem Skript, entweder in einem seriellen oder parallelen Kontext, Daten an einen Schauspieler im selben Datenmodell zu senden. Die Kommunikation über diese API ist asynchron, wobei der Sender nicht blockiert, bis der Empfänger die Nachricht erhält.

Beim Senden von Nachrichten über diese API müssen Sie ein Thema definieren, um die Nachricht zu kategorisieren. Jede Nachricht kann nur an einen einzelnen Schauspieler gesendet werden, aber dieser Schauspieler kann intern mehrere Rückrufe an eine Nachricht binden. Nur Skripte, die Nachkommen eines Schauspielers sind, können Nachrichten empfangen.

Die API hat die folgenden Methoden:

Das folgende Beispiel zeigt, wie Sie Actor:SendMessage() verwenden, um ein Thema zu definieren und eine Nachricht am Senderende zu senden:

Beispiel-Nachrichtensender
local Workspace = game:GetService("Workspace")
-- Senden Sie zwei Nachrichten an den Arbeits-Schauspieler mit dem Thema "Begrüßung"
local workerActor = Workspace.WorkerActor
workerActor:SendMessage("Greeting", "Hallo Welt!")
workerActor:SendMessage("Greeting", "Willkommen")
print("Nachrichten gesendet")

Das folgende Beispiel zeigt, wie Sie Actor:BindToMessageParallel() verwenden, um einen Rückruf für ein bestimmtes Thema in einem parallelen Kontext am Empfängerende zu binden:

Beispiel-Nachrichtempfänger
-- Holen Sie sich den Schauspieler, zu dem dieses Skript gehört
local actor = script:GetActor()
-- Binden Sie einen Rückruf für das Thema "Begrüßung"
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("An Nachrichten gebunden")

Geteilte Tabelle

SharedTable ist eine tabellenähnliche Datenstruktur, die von Skripten, die unter mehreren Schauspielern ausgeführt werden, zugänglich ist. Sie ist nützlich für Situationen, die eine große Menge an Daten beinhalten und einen gemeinsamen Zustand zwischen mehreren Threads erfordern. Zum Beispiel, wenn mehrere Schauspieler an einem gemeinsamen Weltzustand arbeiten, der nicht im Datenmodell gespeichert ist.

Das Senden einer geteilten Tabelle an einen anderen Schauspieler erstellt keine Kopie der Daten. Stattdessen ermöglichen geteilte Tabellen sichere und atomare Updates durch mehrere Skripte gleichzeitig. Jedes Update an einer geteilten Tabelle durch einen Schauspieler ist sofort für alle Schauspieler sichtbar. Geteilte Tabellen können auch in einem ressourcenschonenden Prozess geklont werden, der strukturelles Teilen anstelle des Kopierens der zugrunde liegenden Daten nutzt.

Direkte Datenmodellkommunikation

Sie können auch die Kommunikation zwischen mehreren Threads direkt über das Datenmodell erleichtern, bei dem verschiedene Schauspieler Eigenschaften oder Attribute schreiben und anschließend lesen können. Um jedoch die Thread-Sicherheit aufrechtzuerhalten, können Skripte, die parallel ausgeführt werden, im Allgemeinen nicht in das Datenmodell schreiben. Daher bringt die direkte Verwendung des Datenmodells zur Kommunikation Einschränkungen mit sich und kann Skripte zwingen, häufig zu synchronisieren, was die Leistung Ihrer Skripte beeinträchtigen kann.

Beispiele

Serverseitige Raycasting-Validierung

Für ein Kampf- und Battle-Spiel müssen Sie Raycasting für die Waffen Ihrer Benutzer aktivieren. Da der Client die Waffen simuliert, um eine gute Latenz zu erreichen, muss der Server den Treffer bestätigen, was das Durchführen von Raycasts und eine gewisse Menge an Heuristiken umfasst, die die erwartete Charaktergeschwindigkeit berechnen und das vergangene Verhalten betrachten.

Anstatt ein einzelnes zentrales Skript zu verwenden, das mit einem Remote-Event verbunden ist, das die Clients verwenden, um Trefferinformationen zu kommunizieren, können Sie jeden Treffervalidierungsprozess serverseitig parallel ausführen, wobei jeder Benutzercharakter ein separates Remote-Event hat.

Das serverseitige Skript, das unter dem Actor dieses Charakters ausgeführt wird, verbindet sich mit diesem Remote-Event über eine parallele Verbindung, um die relevanten Logik zur Bestätigung des Treffers auszuführen. Wenn die Logik eine Bestätigung eines Treffers findet, wird der Schaden abgezogen, was das Ändern von Eigenschaften umfasst, sodass es zunächst seriell ausgeführt wird.

local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Erstellen Sie ein neues Remote-Event und fügen Sie es dem Werkzeug hinzu
remoteEvent.Name = "RemoteMouseEvent" -- Benennen Sie es um, damit das lokale Skript danach suchen kann
remoteEvent.Parent = tool
local remoteEventConnection -- Erstellen Sie eine Referenz für die Remote-Event-Verbindung
-- Funktion, die auf ein Remote-Event hört
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SERIELL: Führen Sie Setup-Code seriell aus
local character = player.Character
-- Ignorieren Sie den Charakter des Benutzers beim Raycasting
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- PARALLEL: Führen Sie das Raycasting parallel aus
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Wird verwendet, um den Strahl leicht zu verlängern, da der Klickort möglicherweise leicht vom 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")
-- SERIELL: Der folgende Code ändert den Zustand außerhalb des Schauspielers
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Machen Sie die Explosion nicht tödlich
explosion.Position = clickLocation.Position
-- Mehrere Schauspieler könnten dasselbe Teil in einem Raycast erhalten und entscheiden, es zu zerstören
-- Das ist vollkommen sicher, würde aber zu zwei Explosionen auf einmal anstelle von einer führen
-- Die folgende Überprüfung stellt sicher, dass die Ausführung zuerst zu diesem Teil gelangt
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- Zerstören Sie es
end
end
end
end
-- Verbinden Sie das Signal zunächst seriell, da einige Setup-Codes nicht parallel ausgeführt werden können
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)

Serverseitige prozedurale Terrain-Generierung

Um eine riesige Welt für Ihr Spiel zu erstellen, können Sie die Welt dynamisch bevölkern. Die prozedurale Generierung erstellt typischerweise unabhängige Terrain-Stücke, wobei der Generator relativ komplexe Berechnungen für die Platzierung von Objekten, Materialnutzung und Voxelfüllung durchführt. Das Ausführen von Generierungscode parallel kann die Effizienz des Prozesses verbessern. Das folgende Codebeispiel dient als Beispiel.

-- Parallele Ausführung erfordert die Verwendung von Schauspielern
-- Dieses Skript klont sich selbst; das Original initiiert 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 Schauspieler unter sich parenten
for _, actor in workers do
actor.Parent = script
end
-- Weisen Sie den Schauspielern an, Terrain zu generieren, indem Sie Nachrichten senden
-- In diesem Beispiel werden Schauspieler 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 Sie das ursprüngliche Skript; der Rest des Codes wird in jedem Schauspieler 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, der im parallelen Ausführungskontext aufgerufen werden soll
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 zu nutzen, beachten Sie die folgenden Best Practices, wenn Sie Ihren Luau-Code hinzufügen:

  • Vermeiden Sie lange Berechnungen — Selbst in parallel können lange Berechnungen die Ausführung anderer Skripte blockieren und Verzögerungen verursachen. Vermeiden Sie es, parallele Programmierung zu verwenden, um ein großes Volumen an langen, nicht yieldbaren Berechnungen zu behandeln.

    Diagramm, das zeigt, wie eine Überlastung der parallelen Ausführungsphase immer noch Verzögerungen verursachen kann
  • Verwenden Sie die richtige Anzahl von Schauspielern — Für die beste Leistung verwenden Sie mehr Actors. Selbst wenn das Gerät weniger Kerne als Actors hat, ermöglicht die Granularität eine effizientere Lastenverteilung zwischen den Kernen.

    Demonstration, wie die Verwendung von mehr Schauspielern die Last auf die Kerne verteilt

    Das bedeutet nicht, dass Sie so viele Actors wie möglich verwenden sollten. Sie sollten den Code weiterhin in Actors basierend auf logischen Einheiten aufteilen, anstatt Code mit verbundenen Logik in verschiedene Actors zu zerlegen. Wenn Sie beispielsweise die Raycasting-Validierung parallel aktivieren möchten, ist es sinnvoll, 64 Actors und mehr zu verwenden, anstatt nur 4, selbst wenn Sie auf 4-Kern-Systeme abzielen. Dies ist wertvoll für die Skalierbarkeit des Systems und ermöglicht es, die Arbeit basierend auf den Fähigkeiten der zugrunde liegenden Hardware zu verteilen. Sie sollten jedoch auch nicht zu viele Actors verwenden, die schwer zu warten sind.

©2026 Roblox Corporation. Roblox, das Roblox-Logo und "Powering Imagination" gehören zu unseren eingetragenen und nicht eingetragenen Markenzeichen in den USA und anderen Ländern.