Mit dem Parallel Luau Programmiermodell können Sie Code gleichzeitig auf mehreren Threads ausführen, was die Leistung Ihrer Erfahrung verbessern kann. Wenn Sie Ihre Erfahrung mit mehr Inhalten erweitern, können Sie dieses Modell übernehmen, um die Leistung und Sicherheit Ihrer Luau-Skripte aufrechtzuerhalten.
Parallel Programmiermodell
Standardmäßig werden Skripte sequenziell ausgeführt. Wenn Ihre Erfahrung 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 parallelen Programmiermodell können Sie Aufgaben in mehrere Skripte aufteilen und diese parallel ausführen. Dies sorgt dafür, dass Ihr Erfahrungscode schneller läuft, was die Benutzererfahrung verbessert.
Das parallele Programmiermodell bietet auch Sicherheitsvorteile für Ihren Code. Indem Sie den Code in mehrere Threads aufteilen, beeinflusst eine Bearbeitung des Codes in einem Thread nicht den anderen Code, der parallel ausgeführt wird. Dies verringert das Risiko, dass ein Fehler in Ihrem Code die gesamte Erfahrung beschädigt, und minimiert die Verzögerung für Benutzer in Live-Servern, wenn Sie ein Update bereitstellen.
Die Annahme des parallelen Programmiermodells bedeutet nicht, dass alles in mehreren Threads ausgeführt werden muss. Zum Beispiel führt die Server-seitige Raycasting-Validierung für jeden einzelnen Benutzer ein Remote-Event parallel aus, erfordert jedoch dennoch, dass der ursprüngliche Code sequenziell ausgeführt wird, um globale Eigenschaften zu ändern, was ein häufiges Muster für die parallele Ausführung ist.
Meistens müssen Sie serielle und parallele Phasen kombinieren, um das gewünschte Ergebnis zu erzielen, da derzeit einige Operationen in der Parallelität nicht unterstützt werden, die verhindern können, dass Skripte ausgeführt werden, z. B. das Modifizieren von Instanzen in parallelen Phasen. Für weitere Informationen zur Nutzung von APIs in der Parallelität siehe Thread-Sicherheit.
Code in mehrere Threads aufteilen
Um die Skripte Ihrer Erfahrung gleichzeitig in mehreren Threads auszuführen, müssen Sie sie in logische Teile unter verschiedenen Actors im Datenmodell aufteilen. Actors werden durch Actor Instanzen dargestellt, die von DataModel erben. Sie fungieren als Ausführungseinheiten, die die Last auf mehrere Kerne verteilen, die gleichzeitig laufen.
Actor-Instanzen platzieren
Sie können Actors in geeignete Container setzen oder sie verwenden, um die Oberbegriffstypen Ihrer 3D-Entitäten wie NPCs und Raycaster zu ersetzen, und dann entsprechende Skripte hinzufügen.

In den meisten Fällen sollten Sie einen Actor nicht als Kind eines anderen Actors im Datenmodell anbringen. Wenn Sie jedoch entscheiden, ein Skript innerhalb mehrerer Actors für Ihren spezifischen Anwendungsfall zu verschachteln, gehört das Skript seinem nächstgelegenen Vorfahren Actor.

Threads desynchronisieren
Obwohl das Platzieren von Skripten unter Actors ihnen die Fähigkeit zur parallelen Ausführung verleiht, wird der Code standardmäßig weiterhin auf einem einzelnen Thread sequenziell ausgeführt, was die Laufzeitleistung nicht verbessert. Sie müssen task.desynchronize() aufrufen, eine yieldbare Funktion, die die Ausführung der aktuellen Coroutine zum Ausführen von Code in paralleler Ausführung unterbricht und sie bei der nächsten Möglichkeit zur parallelen Ausführung wiederaufnimmt. Um ein Skript wieder auf die sequenzielle Ausführung umzustellen, rufen Sie task.synchronize() auf.
Alternativ können Sie die Methode RBXScriptSignal:ConnectParallel() verwenden, wenn Sie eine Signalrückruffunktion planen möchten, die Ihren Code sofort beim Triggern in paralleler Ausführung ausführt. Sie müssen task.desynchronize() nicht innerhalb des Signalrückrufs aufrufen.
Thread desynchronisieren
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- Einige parallele Code, der eine Statusaktualisierung berechnet
task.synchronize()
... -- Einige serielle Code, die den Zustand von Instanzen ändern
end)
Skripte, die zu demselben Actor gehören, werden stets sequenziell zueinander ausgeführt, sodass Sie mehrere Actors benötigen. Wenn Sie beispielsweise alle parallenfähigen Verhaltensskripte für Ihren NPC in einen Actor setzen, laufen sie dennoch sequenziell auf einem einzelnen Thread, aber wenn Sie mehrere Actors für unterschiedliche NPC-Logik haben, wird jeder von ihnen parallel auf seinem eigenen Thread ausgeführt. Für weitere Informationen siehe Best Practices.


Thread-Sicherheit
Während der parallelen Ausführung können Sie wie gewohnt auf die meisten Instanzen der DataModel-Hierarchie zugreifen, jedoch sind einige API-Eigenschaften und -Funktionen 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 eine Thread-Sicherheitsstufe, die angibt, ob und wie Sie sie in Ihrem parallelen Code verwenden können, wie die folgende Tabelle zeigt:
| Sicherheitsstufe | Für Eigenschaften | Für Funktionen |
|---|---|---|
| Unsicher | Kann nicht in der Parallelität gelesen oder geschrieben werden. | Kann nicht in der Parallelität aufgerufen werden. |
| Lesen parallel | Kann parallel gelesen, aber nicht geschrieben werden. | N/A |
| Lokale Sicherheit | Kann innerhalb desselben Actors verwendet werden; kann gelesen, aber nicht von anderen Actors in paralleler Ausführung beschreiben werden. | Kann innerhalb desselben Actors aufgerufen werden; kann nicht von anderen Actors in paralleler Ausführung aufgerufen werden. |
| Sicher | Kann gelesen und geschrieben werden. | Kann aufgerufen werden. |
Sie können Thread-Sicherheitskennzeichnungen für API-Mitglieder in der API-Referenz finden. Wenn Sie sie verwenden, sollten Sie auch berücksichtigen, wie API-Aufrufe oder Eigenschaftenänderungen zwischen parallelen Threads interagieren könnten. Normalerweise ist es sicher, dass mehrere Actors die gleichen Daten lesen, aber nicht den Zustand anderer Actors ändern.
Kommunikation zwischen Threads
Im Kontext des Multithreadings können Sie Skripte in verschiedenen Actors weiterhin die Möglichkeit bieten, 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:
- Actor-Nachrichtendienst API zum Senden von Nachrichten an einen Actor mittels Skripten.
- Geteilte Tabelle Datenstruktur zum effizienten Teilen einer großen Menge an Daten zwischen mehreren Actors mit einem gemeinsamen Zustand.
- Direkte Datenmodell-Kommunikation für einfache Kommunikation mit Einschränkungen.
Sie können mehrere Mechanismen zur Unterstützung Ihrer Kommunikationsbedürfnisse zwischen Threads verwenden. Zum Beispiel können Sie eine geteilte Tabelle über die Actor-Nachrichtendienst-API senden.
Actor-Nachrichtendienst
Die Actor-Nachrichtendienst API ermöglicht es einem Skript, sei es in einem sequentiellen oder parallelen Kontext, Daten an einen Actor 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 Actor gesendet werden, dieser Actor kann jedoch intern mehrere Rückrufe an eine Nachricht binden. Nur Skripte, die Nachfahren eines Actors sind, können Nachrichten empfangen.
Die API hat folgende Methoden:
- Actor:SendMessage() zum Senden einer Nachricht an einen Actor.
- Actor:BindToMessage() zum Binden eines Luau-Rückrufs an eine Nachricht mit dem angegebenen Thema in einem sequentiellen Kontext.
- Actor:BindToMessageParallel() zum Binden eines Luau-Rückrufs an eine Nachricht mit dem angegebenen Thema in einem parallelen Kontext.
Das folgende Beispiel zeigt, wie Sie Actor:SendMessage() verwenden, um ein Thema zu definieren und eine Nachricht auf der Senderseite zu senden:
Beispiel Nachrichtensenderlocal Workspace = game:GetService("Workspace")-- Senden Sie zwei Nachrichten an den Worker-Actor mit einem Thema "Greeting"local workerActor = Workspace.WorkerActorworkerActor: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 auf der Empfängerseite zu binden:
Beispiel Nachrichtenempfänger
-- Erhalten Sie den Actor, zu dem dieses Skript gehört
local actor = script:GetActor()
-- Binden Sie einen Rückruf für das Thema "Greeting"
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 genutzt wird, die unter mehreren Actors ausgeführt werden. Sie ist nützlich für Situationen, die eine große Menge an Daten beinhalten und einen gemeinsamen geteilten Zustand zwischen mehreren Threads erfordern. Zum Beispiel, wenn mehrere Actors an einem gemeinsamen Weltzustand arbeiten, der nicht im Datenmodell gespeichert ist.
Das Senden einer geteilten Tabelle an einen anderen Actor macht keine Kopie der Daten. Stattdessen ermöglichen geteilte Tabellen sichere und atomare Updates durch mehrere Skripte gleichzeitig. Jede Aktualisierung einer geteilten Tabelle durch einen Actor ist sofort für alle Actors sichtbar. Geteilte Tabellen können auch in einem ressourceneffizienten Prozess geklont werden, der strukturelles Teilen anstelle des Kopierens der zugrundeliegenden Daten nutzt.
Direkte Datenmodell-Kommunikation
Sie können auch die Kommunikation zwischen mehreren Threads direkt über das Datenmodell erleichtern, wobei verschiedene Actors 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, in der Regel nicht in das Datenmodell schreiben. Die direkte Nutzung des Datenmodells für die Kommunikation bringt daher Einschränkungen mit sich und kann Skripte zwingen, häufig zu synchronisieren, was die Leistung Ihrer Skripte beeinträchtigen kann.
Beispiele
Server-seitige Raycasting-Validierung
Für eine Kampf- und Kriegserfahrung müssen Sie Raycasting für die Waffen Ihrer Benutzer aktivieren. Während 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 einige heuristische Berechnungen erfordert, die die zu erwartende Charaktergeschwindigkeit berechnen und das vergangene Verhalten betrachten.
Anstatt ein einzelnes zentrales Skript zu verwenden, das mit einem Remote-Ereignis verbunden ist, das Clients verwenden, um Trefferinformationen zu kommunizieren, können Sie jeden Validierungsprozess der Treffer auf der Serverseite in paralleler Ausführung betreiben, wobei jeder Benutzercharakter ein separates Remote-Ereignis hat.
Das serverseitige Skript, das unter Actor dieses Charakters läuft, verbindet sich über eine parallele Verbindung mit diesem Remote-Ereignis, 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 Änderungen an Eigenschaften beinhaltet, sodass es zunächst sequenziell ausgeführt wird.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- Erstellen Sie ein neues Remote-Ereignis und hängen Sie es an das Werkzeug an
remoteEvent.Name = "RemoteMouseEvent" -- Benennen Sie es um, damit das lokale Skript danach suchen kann
remoteEvent.Parent = tool
local remoteEventConnection -- Erstellen Sie einen Verweis für die Remote-Ereignisverbindung
-- Funktion, die auf ein Remote-Ereignis hört
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- SEQUENZIAL: Führen Sie Setup-Code sequenziell 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 Raycast parallel aus
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- Wird verwendet, um den Ray leicht zu verlängern, da der Klickort möglicherweise leicht von dem Objekt abgetragen ist
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")
-- SEQUENZIAL: Der untenstehende Code ändert den Zustand außerhalb des Actors
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- Machen Sie die Explosion nicht tödlich
explosion.Position = clickLocation.Position
-- Mehrere Actors könnten dasselbe Teil in einem Raycast erhalten und sich entscheiden, es zu zerstören
-- Dies ist perfekt sicher, würde aber zu zwei Explosionen gleichzeitig 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öre es
end
end
end
end
-- Verbinden Sie das Signal zunächst sequenziell, da ein Teil des Setup-Codes nicht parallel ausgeführt werden kann
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
Server-seitige prozedurale Terrain-Generierung
Um eine weite Welt für Ihre Erfahrung zu schaffen, können Sie die Welt dynamisch bevölkern. Die prozedurale Generierung erstellt normalerweise unabhängige Terrain-Chunks, wobei der Generator relativ komplexe Berechnungen für die Objektplatzierung, Materialnutzung und Voxel-Füllung durchführt. Die Ausführung des Generierungscodes parallel kann die Effizienz des Prozesses verbessern. Der folgende Code dient als Beispiel.
-- Parallele Ausführung erfordert die Verwendung von Actors
-- Dieses Skript klont sich selbst; der Originale initiiert den Prozess, während die Klone als Arbeiter agieren
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 Actors unter sich selbst anbringen
for _, actor in workers do
actor.Parent = script
end
-- Weisen Sie den Actors an, Terrain zu generieren, indem Sie Nachrichten senden
-- In diesem Beispiel werden die Actors 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 Actor 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 in einem 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 bitte die folgenden Best Practices, wenn Sie Ihren Luau-Code hinzufügen:
Vermeiden Sie lange Berechnungen — Selbst in der Parallelität 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 bewältigen.

Verwenden Sie die richtige Anzahl von Actors — Für die beste Leistung verwenden Sie mehr Actors. Selbst wenn das Gerät weniger Kerne hat als Actors, ermöglicht die Granularität eine effizientere Lastverteilung zwischen den Kernen.

Das bedeutet jedoch 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 verbundener Logik auf verschiedene Actors aufzuteilen. Zum Beispiel, wenn Sie 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 ihm, die Arbeit basierend auf der Fähigkeit der zugrunde liegenden Hardware zu verteilen. Sie sollten jedoch auch nicht zu viele Actors verwenden, die schwer zu warten sind.