본문 바로가기
유니티

Day 29 - 락 오브젝트(동기화작업 / 코루틴이나 잡시스템과 사용 가능)

by shin0707 2024. 5. 23.
728x90

  • 주제

>> 락 오브젝트 동기화

(게임 점수 업데이트, 자원 관리에 사용)

(유니티 스레드)

 

락 오브젝트 기능과 함께 사용하면 좋은 유니티 기능

(Coroutine)


  • 공부내용

1. 락 오브젝트 동기화란?

여러 쪽에서 동시에 같은 변수를 수정하려고 할 때,

lock키워드를 사용하여 하나씩 접근하도록 질서를 지켜줄 수 있다.

 

기본예시

여러 스레드가 동시에 IncrementCounter를 호출해도 lockObject를 사용하여

한 번에 하나의 스레드만 counter를 수정 가능!

 

*스레드: 하나의 프로세스(예: 크롬) 내에서 실행되는 독립적인 작업 단위(예: 브라우저 내 탭)이다.

같은 프로세스 내의 스레드는 메모리 공간을 공유한다.

여러 스레드가 동시에 작용한다는 의미

=> 한 탭에서는 유튜브 동영상을 재생, 다른 탭에서는 인터넷 기사를 구독하는 것처럼,

크롬 내의 메모리 공간을 서로 공유하면서 동시에 실행이 가능하다는 의미.

using UnityEngine;

public class LockExample : MonoBehaviour
{
    private int counter = 0;                   // counter변수 초기화
    private object lockObject = new object();  // 락 오브젝트 초기화

    void IncrementCounter()                    // counter를 증가시키는 메서드
    {
        lock (lockObject)                      // 락 오브젝트를 사용하여 동기화 블록을 생성
        {
            counter++;
            Debug.Log("Counter: " + counter);
        }
    }

    void Start()
    {
        for (int i = 0; i < 10; i++) //최대 스레드 수 10개로 제한(성능저하 방지)
        {
            new System.Threading.Thread(IncrementCounter).Start(); // 새 스레드를 생성하고 시작
        }
    }
}

 

***

유니티에서의 스레드

: 유니티는 메인 스레드에서 대부분의 작업을 처리한다. (렌더링, 물리 연산, 게임 로직 등)

하지만, 메인 스레드에서 시간이 많이 걸리는 작업(예를 들어, 파일 입출력, 네트워크 통신, 복잡한 계산)을

수행하면 게임이 일시적으로 멈추는 버그가 발생할 수 있다.

--> 해결방안: 백그라운드 스레드를 사용

 

*백그라운드 스레드 사용법

: 'Thread' 클래스를 사용

using UnityEngine;
using System.Threading;
using System.IO;

public class BackgroundThreadExample : MonoBehaviour
{
    private void Start()
    {
        Thread backgroundThread = new Thread(ReadFile);
        backgroundThread.IsBackground = true; // 백그라운드 스레드로 설정
        backgroundThread.Start();
    }

    private void ReadFile()
    {
        // 시간 소모가 큰 작업을 백그라운드 스레드에서 수행
        string path = "path/to/your/file.txt";
        if (File.Exists(path))
        {
            string content = File.ReadAllText(path);
            Debug.Log("File content: " + content);
        }
    }
}

***


예제1 : 게임 점수 업데이트

public class ScoreManager : MonoBehaviour 
{
    private int score = 0; 
    private object scoreLock = new object(); 

    public void AddScore(int points) // 점수를 추가하는 메서드
    {
        lock (scoreLock) // 점수 락 오브젝트를 사용하여 동기화 블록 생성
        {
            score += points; // 점수를 추가
            Debug.Log("Score: " + score); // 현재 점수를 출력
        }
    }

    void Start() 
    {
        for (int i = 0; i < 5; i++) 
        {
            new System.Threading.Thread(() => AddScore(10)).Start();
        }
    }
}

예제2 : 자원 관리

플레이어가 자원을 채취하거나 사용하는 메서드에 락 오브젝트를 사용할 수 있다.

public class ResourceManager : MonoBehaviour 
{
    private int resourceCount = 100; 
    private object resourceLock = new object(); 

    public bool UseResource(int amount) // 자원을 사용하는 메서드
    {
        lock (resourceLock) // 자원 락 오브젝트를 사용하여 동기화 블록 생성
        {
            if (resourceCount >= amount) // 자원이 충분한지 확인합니다.
            {
                resourceCount -= amount; // 자원 사용
                Debug.Log("Resource used. Remaining: " + resourceCount); // 남은 자원 출력
                return true; // 자원 사용 성공 반환
            }
            else 
            {
                Debug.Log("Not enough resources.");
                return false;
            }
        }
    }

    void Start()
    {
        // 자원 사용을 시도하는 여러 스레드 시뮬레이션
        for (int i = 0; i < 3; i++)
        {
            new System.Threading.Thread(() => UseResource(30)).Start();
        }
    }
}

 

함께 사용하면 좋은 유니티 기능 1: Coroutine

 

기본예제

IEnumerator ExampleCoroutine()
{
    yield return new WaitForSeconds(1); // 1초 대기
    Debug.Log("1 second passed"); // 1초가 지났음을 로그로 출력
}

void Start()
{
    StartCoroutine(ExampleCoroutine()); // 코루틴 시작
}

 

공유 변수를 여러 코루틴에서 안전하게 수정하는 예제

---> 3개의 코루틴을 동시에 실행, sharedCounter 변수를 lock키워드를 이용해 안전하게 증가시킴.

yield return null을 사용하여 다음 프레임까지 대기.

using UnityEngine;
using System.Collections;

public class LockAndCoroutineExample : MonoBehaviour
{
    private int sharedCounter = 0; // 공유 변수
    private object lockObject = new object(); // 락 오브젝트

    void Start()
    {
        // 여러 코루틴 시작
        StartCoroutine(IncrementCounter());
        StartCoroutine(IncrementCounter());
        StartCoroutine(IncrementCounter());
    }

    IEnumerator IncrementCounter()
    {
        for (int i = 0; i < 10; i++) // 10번 반복
        {
            lock (lockObject) // 락을 사용하여 동기화
            {
                sharedCounter++; // 공유 변수 접근 및 수정
                Debug.Log("Counter: " + sharedCounter); // 변수 값 출력
            }
            yield return null; // 다음 프레임까지 대기
        }
    }
}

 

 

함께 사용하면 좋은 유니티 기능 2: Job System

 

기본예제

using Unity.Jobs;
using Unity.Collections;

struct MyJob : IJob
{
    public void Execute()
    {
        Debug.Log("Job executed"); // 작업 실행 로그 출력
    }
}

public class JobSystemExample : MonoBehaviour
{
    void Start()
    {
        MyJob job = new MyJob(); // 작업 생성
        JobHandle handle = job.Schedule(); // 작업 예약
        handle.Complete(); // 작업 완료 대기
    }
}

 

공유 변수를 여러 잡에서 수정하는 예제

---> 3개의 잡이 동시 실행, sharedCounter 변수를 안전하게 증가시킴.

JobHandle.Complete를 호출하여 잡이 완료될 때까지 대기.

NativeArray<int>를 사용하여 공유 변수를 잡 내에서 안전하게 접근할 수 있도록 함.

using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using System.Threading;

public class LockAndJobSystemExample : MonoBehaviour
{
    private int sharedCounter = 0; // 공유 변수
    private object lockObject = new object(); // 락 오브젝트

    void Start()
    {
        // 여러 잡 시작
        for (int i = 0; i < 3; i++)
        {
            IncrementCounterJob job = new IncrementCounterJob
            {
                lockObject = lockObject, // 락 오브젝트 전달
                sharedCounterPtr = new NativeArray<int>(1, Allocator.TempJob) // 공유 변수 포인터
            };
            JobHandle handle = job.Schedule();
            handle.Complete(); // 잡이 완료될 때까지 대기
            sharedCounter = job.sharedCounterPtr[0]; // 공유 변수 업데이트
            job.sharedCounterPtr.Dispose(); // 네이티브 배열 해제
        }

        Debug.Log("Final Counter: " + sharedCounter); // 최종 변수 값 출력
    }

    struct IncrementCounterJob : IJob
    {
        public object lockObject; // 락 오브젝트
        public NativeArray<int> sharedCounterPtr; // 공유 변수 포인터

        public void Execute()
        {
            lock (lockObject) // 락을 사용하여 동기화
            {
                sharedCounterPtr[0]++; // 공유 변수 접근 및 수정
            }
        }
    }
}
728x90
반응형