잡을 만들고 성공적으로 실행하려면 다음을 수행해야 합니다.
IJob
인터페이스를 구현합니다.Schedule
메서드를 호출합니다.Complete
메서드를 호출하면 됩니다.Unity에서 잡을 생성하려면 IJob
인터페이스를 구현합니다.IJob
구현을 사용하여 실행 중인 다른 잡과 병렬로 실행되는 단일 잡을 예약할 수 있습니다.
IJob
에는 하나의 필수 메서드가 있습니다.워커 스레드가 해당 잡을 실행할 때마다 Unity에서 호출하는 Execute
입니다.
또한 잡을 생성 시 다른 메서드에서 해당 잡을 참조할 때 사용해야 하는 JobHandle
을 생성할 수도 있습니다.
중요:잡 내에서 읽기 전용이 아니거나 변경 가능한 정적 데이터에 액세스하는 것에 대한 보호 기능은 없습니다.이러한 종류의 데이터에 액세스하면 모든 안전 시스템을 우회하여 애플리케이션 또는 Unity 에디터에 크래시가 발생할 수 있습니다.
Unity가 실행되면 잡 시스템이 예약된 잡 데이터의 사본을 생성하여 둘 이상의 스레드가 동일한 데이터를 읽거나 쓰는 것을 방지합니다.잡이 완료된 후에는 NativeContainer
에 기록된 데이터만 액세스할 수 있습니다.이는 잡에서 사용하는 NativeContainer
의 사본과 원본 NativeContainer
오브젝트가 모두 동일한 메모리를 가리키기 때문입니다.자세한 내용은 스레드 세이프 타입 문서를 참조하십시오.
잡 시스템이 잡 대기열에서 잡을 선택하면 단일 스레드에서 Execute
메서드를 한 번 실행합니다.일반적으로 잡 시스템은 배경 스레드에서 잡을 실행하지만 대기 상태가 되면 메인 스레드를 선택할 수 있습니다.따라서 프레임 아래에서 완료할 수 있도록 잡을 설계해야 합니다.
잡을 예약하려면 Schedule
을 호출하십시오.그러면 잡이 잡 대기열에 추가되고, 잡 시스템은 모든 해당 종속성이 완료되면(있는 경우) 잡을 실행하기 시작합니다.일단 예약된 작업은 인터럽트할 수 없습니다.메인 스레드에서는 Schedule
만 호출할 수 있습니다.
팁:잡에는 메인 스레드에서 잡을 즉시 실행하기 위해 Schedule
대신 사용할 수 있는 Run
메서드가 있습니다.이 메서드를 디버깅 목적으로 사용할 수 있습니다.
Schedule
을 호출하고 잡 시스템이 잡을 실행한 후에는 JobHandle
에서 Complete
메서드를 호출하여 잡의 데이터에 액세스할 수 있습니다.코드에서 가능한 한 마지막에 Complete
을 호출하는 것이 가장 좋습니다.Complete
을 호출하면 메인 스레드는 잡에서 사용 중이던 NativeContainer
인스턴스에 안전하게 액세스할 수 있습니다.또한 Complete
을 호출하면 안전 시스템에서 상태가 클린업됩니다.그렇게 하지 않으면 메모리 누수가 발생합니다.
다음은 두 개의 부동 소수점 값을 더하는 잡의 예입니다.이것은 IJob
을 구현하고, NativeArray
를 사용하여 잡의 결과를 얻으며, 그 안에서 잡의 구현과 함께 Execute
메서드를 사용합니다.
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
// Job adding two floating point values together
public struct MyJob :IJob
{
public float a;
public float b;
public NativeArray<float> result;
public void Execute()
{
result[0] = a + b;
}
}
다음 예제는 메인 스레드에서 잡을 예약하는 데 MyJob
잡을 기반으로 합니다.
using UnityEngine;
using Unity.Collections;
using Unity.Jobs;
public class MyScheduledJob :MonoBehaviour
{
// Create a native array of a single float to store the result.Using a
// NativeArray is the only way you can get the results of the job, whether
// you're getting one value or an array of values.
NativeArray<float> result;
// Create a JobHandle for the job
JobHandle handle;
// Set up the job
public struct MyJob :IJob
{
public float a;
public float b;
public NativeArray<float> result;
public void Execute()
{
result[0] = a + b;
}
}
// Update is called once per frame
void Update()
{
// Set up the job data
result = new NativeArray<float>(1, Allocator.TempJob);
MyJob jobData = new MyJob
{
a = 10,
b = 10,
result = result
};
// Schedule the job
handle = jobData.Schedule();
}
private void LateUpdate()
{
// Sometime later in the frame, wait for the job to complete before accessing the results.
handle.Complete();
// All copies of the NativeArray point to the same memory, you can access the result in "your" copy of the NativeArray
// float aPlusB = result[0];
// Free the memory allocated by the result array
result.Dispose();
}
}
필요한 데이터가 확보되는 즉시 잡에서 ’Schedule’을 호출하고 결과가 필요할 때까지 ’Complete’을 호출하지 않는 것이 가장 좋습니다.
중요도가 상대적으로 낮은 잡은 중요도가 높은 잡과 경쟁하지 않는 프레임의 일부에 예약할 수 있습니다.
예를 들어 한 프레임의 끝과 다음 프레임의 시작 사이에 실행 중인 잡이 없고 한 프레임의 지연이 허용되는 기간이 있는 경우, 프레임이 끝날 때 잡을 예약하고 다음 프레임에서 그 결과를 사용할 수 있습니다.또는 다른 잡으로 인해 해당 전환 기간이 포화 상태이고 프레임의 다른 곳에 활용도가 낮은 기간이 있는 경우, 대신 해당 기간에 잡을 예약하는 것이 더 효율적입니다.
프로파일러를 사용하여 Unity에서 작업이 완료되기를 기다리는 위치를 확인할 수도 있습니다.메인 스레드의 WaitForJobGroupID
마커가 이를 나타냅니다.이 마커는 해결해야 하는 데이터 종속성이 어딘가에 도입되었음을 의미할 수 있습니다.JobHandle.Complete
을 찾아 메인 스레드를 대기시킬 데이터 종속성이 있는 위치를 추적하십시오.
스레드와는 다르게 잡은 실행을 산출하지 않습니다.잡이 시작되면 해당 워커 스레드는 다른 잡을 실행하기 전에 잡을 적용하여 완료합니다.따라서 오래 실행되는 잡은 시스템의 다른 잡에 비해 완료하는 데 시간이 오래 걸리는 잡을 제출하는 대신 서로 의존하는 작은 잡으로 분할하는 것이 가장 좋습니다.
잡 시스템은 일반적으로 여러 개의 잡 종속성 체인을 실행하므로 오래 실행되는 잡을 여러 개로 나누면 여러 개의 잡 체인이 진행될 수 있습니다.대신 잡 시스템이 오래 실행되는 잡으로 가득 차면 모든 워커 스레드가 완전히 소모되어 독립적인 잡의 실행이 차단될 수 있습니다.이렇게 하면 메인 스레드가 명시적으로 대기하는 중요한 잡의 완료 시간이 밀려나서 메인 스레드에 불필요한 지연이 발생할 수 있습니다.
특히 오래 실행되는 IJobParallelFor
잡은 잡 배치 크기에 맞게 가능한 한 많은 워커 스레드에서 실행하려고 의도적으로 시도하기 때문에 잡 시스템에 부정적인 영향을 미칩니다.긴 병렬 잡을 분할할 수 없는 경우, 잡을 예약할 때 잡의 배치 크기를 늘려서 오래 실행되는 잡을 선택하는 워커 수를 제한하는 것이 좋습니다.
MyParallelJob jobData = new MyParallelJob();
jobData.Data = someData;
jobData.Result = someArray;
// Use half the available worker threads, clamped to a minimum of 1 worker thread
const int numBatches = Math.Max(1, JobsUtility.JobWorkerCount / 2);
const int totalItems = someArray.Length;
const int batchSize = totalItems / numBatches;
// Schedule the job with one Execute per index in the results array and batchSize items per processing batch
JobHandle handle = jobData.Schedule(result.Length, totalItems, batchSize);