평행 Luau 프로그래밍 모델로 여러 스레드에서 코드를 동시에 실행할 수 있으므로 경험의 성능을 향상시킬 수 있습니다.더 많은 콘텐츠로 경험을 확장함에 따라 루아우 스크립트의 성능과 안전성을 유지하기 위해 이 모델을 채택할 수 있습니다.
병렬 프로그래밍 모델
기본적으로 스크립트는 순차적으로 실행됩니다.경험에 플레이어가 아닌 캐릭터(NPC), 레이캐스팅 유효성 검사 및 절차적 생성과 같은 복잡한 논리 또는 콘텐츠가 있는 경우 순차 실행으로 사용자에게 지연이 발생할 수 있습니다.병렬 프로그래밍 모델로 작업을 여러 스크립트로 분할하고 병렬로 실행할 수 있습니다 분할.이렇게 하면 경험 코드가 더 빠르게 실행되어 사용자 경험이 향상됩니다.
병렬 프로그래밍 모델은 코드에 안전 혜택을 추가합니다.코드를 여러 스레드로 분할하여 한 스레드에서 코드를 편집하면 병렬로 실행되는 다른 코드에는 영향을 주지 않습니다.이렇게 하면 코드에 하나의 버그가 전체 경험을 손상시키는 위험이 줄어들고 업데이트를 푸시할 때 라이브 서버의 사용자에 대한 지연이 최소화됩니다.
병렬 프로그래밍 모델을 채택하는 것은 모든 것을 여러 스레드에 넣는 것을 의미하지 않습니다.예를 들어, 서버 측 레이캐스팅 유효성 검사는 각 사용자에게 병렬로 원격 이벤트를 설정하지만 여전히 전역 속성을 변경하기 위해 초기 코드를 직렬로 실행해야 하며, 이는 병렬 실행의 일반적인 패턴입니다.
대부분의 경우 병렬 및 직렬 단계를 결합하여 원하는 출력을 얻어야 하며, 현재 병렬에서 지원되지 않는 일부 작업은 병렬 단계에서 인스턴스를 수정하는 것처럼 스크립트 실행을 방해할 수 있습니다.병렬로 API 사용 수준에 대한 자세한 정보는 스레드 안전을 참조하십시오.
코드를 여러 스레드로 분할
경험의 스크립트를 여러 스레드에서 동시에 실행하려면 데이터 모델 의 다른 액터 아래에서 논리적 덩어리로 분할해야합니다.액터는 DataModel 에서 상속된 Actor 인스턴스로 나타납니다.이들은 동시에 실행되는 여러 코어에 부하를 분산하는 실행 격리 단위로 작동합니다.
액터 인스턴스 배치
액터를 적절한 컨테이너에 넣거나 NPC 및 레이캐스터와 같은 3D 엔터티의 최상위 인스턴스 유형을 교체하고 해당 스크립트를 추가하여 사용할 수 있습니다.

대부분의 상황에서 데이터 모델에 액터를 다른 액터의 자식으로 넣으면 안됩니다.그러나 특정 사용 사례에 대해 여러 액터에 중첩된 스크립트를 배치하기로 결정하면 스크립트는 가장 가까운 조상 액터가 소유합니다.

스레드 비동기화
액터에 스크립트를 배치하면 병렬 실행 기능이 제공되지만, 기본적으로 코드는 여전히 단일 스레드에서 직렬로 실행되므로 런타임 이행향상되지 않습니다.현재 코루틴의 실행을 중단하고 병렬로 코드를 실행하여 다음 병렬 실행 기회에 재시작하기 위해 중단할 수 있는 함수 task.desynchronize()를 호출해야 합니다.스크립트를 직렬 실행으로 다시 전환하려면 task.synchronize()를 호출하십시오.
또는, 신호 콜백을 예약하여 트리거 시 즉시 코드를 병렬로 실행하려면 RBXScriptSignal:ConnectParallel() 메서드를 사용할 수 있습니다.신호 콜백 내에서 task.desynchronize()를 호출할 필요가 없습니다.
스레드 비동기화
local RunService = game:GetService("RunService")
RunService.Heartbeat:ConnectParallel(function()
... -- 상태 업데이트를 계산하는 일부 병렬 코드
task.synchronize()
... -- 인스턴스 상태를 변경하는 일부 직렬 코드
end)
동일한 액터의 일부인 스크립트는 항상 서로 관련하여 순차적으로 실행되므로 여러 액터가 필요합니다.예를 들어, NPC의 모든 병렬 활성화된 동작 스크립트를 한 액터에 넣으면 여전히 단일 스레드에서 직렬로 실행되지만, 다른 NPC 로직에 대한 여러 액터가 있는 경우 각각 자체 스레드에서 병렬로 실행됩니다.자세한 내용은 모범 사례를 참조하십시오.


스레드 안전성
병렬 실행 중에는 일반적으로 DataModel 계층의 대부분의 인스턴스에 액세스할 수 있지만, 일부 API 속성과 함수는 읽거나 쓰기에 안전하지 않습니다.병렬 코드에서 사용하는 경우 Roblox 엔진은 이러한 액세스가 발생하지 않도록 자동으로 감지하고 방지할 수 있습니다.
API 멤버에는 병렬 코드에서 사용할 수 있는지 여부와 방법을 나타내는 스레드 안전 수준이 있습니다. 다음 표에서 보여주는 것처럼:
안전 레벨 | 속성에 대해 | 함수에 대해 |
---|---|---|
위험한 | 병렬로 읽거나 쓸 수 없습니다. | 병렬로 호출할 수 없습니다. |
병렬 읽기 | 병렬로 읽을 수는 있지만 쓰지 않을 수 있습니다. | N/A |
로컬 금고 | 동일한 액터 내에서 사용할 수 있으며, 병렬로 다른 Actors에 의해 읽을 수는 있지만 기록할 수는 없습니다. | 동일한 액터 내에서 호출할 수 있습니다; 병렬로 다른 Actors에서 호출할 수 없습니다. |
안전 | 읽고 쓰기가 가능합니다. | 호출할 수 있습니다. |
API 참조에서 API 멤버의 스레드 안전 태그를 찾을 수 있습니다.사용할 때 병렬 스레드 간에 API 호출이나 속성 변경이 어떻게 상호 작용할 수 있는지 고려해야 합니다.일반적으로 여러 액터가 다른 액터와 동일한 데이터를 읽지만 다른 액터의 상태를 수정하지 않는 것이 안전합니다.
교차 스레드 통신
다중 스레드 컨텍스트에서 다른 액터의 스크립트가 서로 통신하여 데이터를 교환하고, 작업을 조정하고, 활동을 동기화할 수 있도록 허용할 수 있습니다.엔진은 교차 스레드 통신을 위한 다음 메커니즘을 지원합니다:
- 액터 메시징 스크립트를 사용하여 액터에 메시지를 보내는 API.
- 공유 테이블 데이터 구조로, 공유 상태의 여러 액터 간에 대량의 데이터를 효율적으로 공유합니다.
- 직접 데이터 모델 통신 제한된 간단한 통신을 위한.
교차 스레드 통신 요구를 수용하기 위해 여러 메커니즘을 지원할 수 있습니다.예를 들어, 액터 메시징 API를 통해 공유 테이블을 보낼 수 있습니다.
액터 메시징
액터 메시징 API는 직렬 또는 병렬 컨텍스트에서 스크립트가 동일한 데이터 모델의 액터에 데이터를 보낼 수 있도록 허용합니다.이 API를 통한 통신은 비동기적이며, 발신자가 수신자가 메시지를 받을 때까지 차단하지 않습니다.
이 API를 사용하여 메시지를 보낼 때, 메시지를 분류하기 위한 주제 를 정의해야 합니다.각 메시지는 단일 액터에만 전송할 수 있지만, 해당 액터는 내부적으로 메시지에 바인딩된 여러 개의 콜백을 가질 수 있습니다.액터의 후손인 스크립트만 메시지를 받을 수 있습니다.
API에는 다음과 같은 메서드가 있습니다:
- Actor:SendMessage() 액터에게 메시지 전송
- Actor:BindToMessage() 루au 콜백을 직렬 컨텍스트에서 지정된 주제로 메시지에 바인딩하기 위해.
- Actor:BindToMessageParallel() 루au 콜백을 병렬 컨텍스트에서 지정된 주제의 메시지에 바인딩하기 위해.
다음 예시에서는 Actor:SendMessage()를 사용하여 주제를 정의하고 발신자에게 메시지를 보내는 방법을 보여줍니다.
메시지 발신자 예시
local Workspace = game:GetService("Workspace")-- 인사"라는 주제로 작업자 액터에 메시지 두 개 보내기local workerActor = Workspace.WorkerActorworkerActor:SendMessage("Greeting", "Hello World!")workerActor:SendMessage("Greeting", "Welcome")print("Sent messages")
다음 예시에서는 수신자 측의 병렬 컨텍스트에서 특정 주제에 대한 콜백을 바인딩하기 위해 Actor:BindToMessageParallel()를 사용하는 방법을 보여줍니다.
메시지 수신기 예시
-- 이 스크립트가 부모인 액터 가져오기
local actor = script:GetActor()
-- 인사" 메시지 주제에 대한 콜백 바인딩
actor:BindToMessageParallel("Greeting", function(greetingString)
print(actor.Name, "-", greetingString)
end)
print("Bound to messages")
공유 테이블
SharedTable는 여러 액터에서 실행되는 스크립트에서 액세스할 수 있는 테이블 같은 데이터 구조입니다.많은 데이터가 관련되고 여러 스레드 간에 공유된 상태가 필요한 상황에 유용합니다.예를 들어, 데이터 모델에 저장되지 않은 공통 세계 상태에서 여러 액터가 작업할 때.
공유 테이블을 다른 액터에 전송하면 데이터의 복사본이 생성되지 않습니다.대신, 공유 테이블은 여러 스크립트가 동시에 안전하고 원자적인 업데이트를 할 수 있도록 합니다.한 액터가 공유 테이블을 업데이트할 때마다 모든 액터에게 즉시 표시됩니다.공유 테이블은 기본 데이터를 복사하는 대신 구조적 공유를 사용하는 리소스 효율적인 프로세스에서도 복제될 수 있습니다.
직접 데이터 모델 통신
데이터 모델을 사용하여 다른 액터가 속성이나 특성을 작성하고 나중에 읽을 수 있는 여러 스레드 간의 통신을 용이하게 할 수도 있습니다.그러나 스레드 안전을 유지하기 위해 병렬로 실행되는 스크립트는 일반적으로 데이터 모델에 쓸 수 없습니다.따라서 데이터 모델을 통신에 직접 사용하면 제한이 있으며 스크립트를 자주 동기화하도록 강제할 수 있으므로 스크립트의 성능에 영향을 줄 수 있습니다.
예시
서버 측 레이캐스팅 유효성 검사
전투 및 전투 경험을 위해, 사용자의 무기에 대해 레이캐스팅을 활성화해야 합니다.클라이언트가 좋은 대기 시간을 달성하기 위해 무기를 시뮬레이션하면 서버는 히트를 확인해야 하며, 이는 광선 캐스트와 예상 문자 속도를 계산하는 몇 가지 전략을 포함하고 과거 행동을 살펴봅니다.
클라이언트가 히트 정보를 전달하는 데 사용하는 원격 이벤트에 연결하는 단일 중앙화된 스크립트를 사용하는 대신, 모든 사용자 캐릭터가 별도의 원격 이벤트를 가지고 있는 서버 측면에서 각 히트 검증 프로세스를 병렬로 실행할 수 있습니다.
그 캐릭터의 Actor 아래에서 실행되는 서버 사이드 스크립트는 병렬 연결을 사용하여 이 원격 이벤트에 연결하여 히트를 확인하는 관련 논리를 실행합니다.논리가 히트 확인을 찾으면 피해가 공제되며, 속성 변경이 포함되므로 처음에는 직렬로 실행됩니다.
local Workspace = game:GetService("Workspace")
local tool = script.Parent.Parent
local remoteEvent = Instance.new("RemoteEvent") -- 새로운 원격 이벤트를 생성하고 도구에 부모로 지정
remoteEvent.Name = "RemoteMouseEvent" -- 로컬 스크립트가 찾을 수 있도록 이름을 바꾸십시오
remoteEvent.Parent = tool
local remoteEventConnection -- 원격 이벤트 연결에 대한 참조 생성
-- 원격 이벤트를 수신하는 함수
local function onRemoteMouseEvent(player: Player, clickLocation: CFrame)
-- 직렬: 직렬에서 설치 코드 실행
local character = player.Character
-- 레이캐스팅 중에 사용자의 문자를 무시하기
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = { character }
-- 병렬: 병렬로 레이캐스트 수행
task.desynchronize()
local origin = tool.Handle.CFrame.Position
local epsilon = 0.01 -- 클릭 위치가 개체에서 약간 오프될 수 있기 때문에 광선을 약간 확장하여 사용합니다
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")
-- 직렬: 아래 코드는 액터 외부의 상태를 수정합니다
task.synchronize()
explosion.DestroyJointRadiusPercent = 0 -- 폭발을 치명적이지 않게 만들기
explosion.Position = clickLocation.Position
-- 여러 액터가 레이캐스트에서 같은 부품을 얻고 그것을 파괴하기로 결정할 수 있습니다
-- 이것은 완벽하게 안전하지만 한 번에 두 폭발이 발생하는 대신
-- 다음은 실행이 먼저 이 부분에 도달했는지 확인합니다
if hitPart.Parent then
explosion.Parent = Workspace
hitPart:Destroy() -- 파괴하기
end
end
end
end
-- 일부 설정 코드가 병렬로 실행할 수 없기 때문에 처음에 직렬 신호를 연결하여 신호를 전송합니다. Connect the signal in serial initially since some setup code is not able to run in parallel
remoteEventConnection = remoteEvent.OnServerEvent:Connect(onRemoteMouseEvent)
서버 측 절차적 지형 생성
경험을 위한 광대한 세계를 만들려면 세계를 동적으로 채울 수 있습니다.절차적 생성은 일반적으로 독립적인 지형 덩어리를 생성하며, 생성기는 개체 배치, 재료 사용 및 복셀 채우기에 대한 상대적으로 복잡한 계산을 수행합니다.병렬로 생성 코드를 실행하면 프로세스의 효율성이 향상될 수 있습니다.다음 코드 샘플은 예제로 사용됩니다.
-- 병렬 실행에는 액터의 사용이 필요합니다
-- 이 스크립트는 자체를 복제하며, 원래는 프로세스를 시작하고 클론은 작업자로 작동합니다
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
-- 자체 아래의 모든 액터 부모 만들기
for _, actor in workers do
actor.Parent = script
end
-- 액터에게 메시지를 보내 지형을 생성하도록 지시
-- 이 예제에서는 액터가 무작위로 선택됩니다
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)
-- 원래 스크립트에서 나가; 나머지 코드는 각 액터에서 실행됩니다
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
-- 병렬 실행 컨텍스트에서 호출할 콜백 바인딩Bind the callback to be called in parallel execution context
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)
-- 현재, WriteVoxels()는 직렬 단계에서 호출해야 합니다
task.synchronize()
Workspace.Terrain:WriteVoxels(
Region3.new(corner, corner + Vector3.new(16, 16, 16)),
4,
voxels.materials,
voxels.occupancy
)
end)
모범 사례
병렬 프로그래밍의 최대 이점을 적용하려면 Luau 코드를 추가할 때 다음 모범 사례를 참조하십시오:
길고 복잡한 계산 피하기 — 심지어 병렬로도 길고 복잡한 계산은 다른 스크립트의 실행을 차단하고 지연을 일으킬 수 있습니다.많은 양의 길고 끈질긴 계산을 처리하기 위해 병렬 프로그래밍을 사용하지 않도록 주의하십시오.
적절한 수의 액터 사용 — 최고의 이행위해 더 많이 Actors 사용하십시오.장치의 코어가 Actors 보다 적은 경우에도 세분화로 코어 간의 더 효율적인 부하 분산이 가능합니다.
이것은 가능한 한 많은 Actors를 사용해야 한다는 것을 의미하지 않습니다.연결된 논리로 다른 논리와 코드를 중단하는 대신 논리 단위에 따라 코드를 로 분할해야 합니다.예를 들어, 병렬로 레이캐스팅 유효성 검사를 활성화하려는 경우, 4코어 시스템을 대상으로 하는 경우에도 64 및 더 많은 것을 사용하는 것이 합리적입니다.이것은 시스템의 확장성에 중요하며 기본 하드웨어의 기능에 따라 작업을 분배할 수 있게 합니다.그러나 유지 관리하기 어려운 너무 많은 Actors 을 사용해서는 안됩니다.