dev.Log
동시성제어 - ThreadLocal 본문
<동시성 문제>
여러 쓰레드가 동시에 같은 인스턴스의 필드 값을 변경하면서 발생하는 문제를 동시성 문제라고 한다. 이런 동시성 문제는 여러 쓰레드가 같은 인스턴스의 필드에 접근해야 하기 때문에 트래픽이 적은 상황에서는 확률상 잘 나타나지 않고, 트래픽이 많아질 수록 자주 발생한다. 특히 스프링 빈처럼 싱글톤 객체의 필드를 변경하며 사용할 때 이러한 동시성 문제를 조심해야한다.
<해결 - Thread Local>
"일반적인 변수 필드"는 여러 쓰레드가 같은 인스턴스의 필드에 접근하면 처음 쓰레드가 보관한 데이터가 사라질 수 있다.
하지만, 쓰레드 로컬은 해당 스레드만 접근할 수 있는 특별한 저장소이기 때문에 같은 인스턴스의 쓰레드 로컬 필드에 접근해도 문제없다.
@Slf4j
public class ThreadLocalService {
//private String nameStore;
private ThreadLocal<String> nameStore = new ThreadLocal<>();
public String logic(String name) {
log.info("저장 name={} -> nameStore={}", name, nameStore.get());
nameStore.set(name); //이렇게 저장
sleep(1000);
log.info("조회 nameStore={}", nameStore.get());
return nameStore.get(); //값을 얻음
}
}
ThreadLocal 사용법
값 저장 : ThreadLocal.set
값 조회 : ThreadLocal.get
값 제거 : ThreadLocal.remove
"주의"
해당 쓰레드가 쓰레드 로컬을 모두 사용하고 나면 "ThreadLocal.remove"를 호출해서 쓰레드 로컬에 저장된 값을 제거해주어야한다.
제거안하면 특정상황에서 메모리 누수가 날 수 있다고 함...
쓰레드를 생성하는 비용은 비싸기때문에, 쓰레드를 제거하지 않고 보통 쓰레드 풀을 통해서 쓰레드를 재사용하는데 쓰레드로컬에 저장된 값도 살아있게된다. 또 같은 이름의 스레드가 그 스레드를 할당받게되면 정리되지않고 남아있던 데이터를 사용하게 될 수도 있다!
> 메모리 누수의 이유
쓰레드 로컬은 각 쓰레드가 자신만의 데이터 복사본을 가질 수 있도록 해주어 다른 쓰레드와의 데이터 충돌을 방지합니다. 그러나 쓰레드 로컬에 저장된 데이터는 해당 쓰레드가 종료되더라도 자동으로 GC(Garbage Collection, 가비지 컬렉션)의 대상이 되지 않습니다. 쓰레드가 종료되어도 쓰레드 로컬에 할당된 데이터는 쓰레드가 참조하는 쓰레드 로컬 객체에 계속 남아 있을 수 있고, 이는 메모리 누수를 발생시킬 수 있습니다.
public class ThreadLocalExample {
private static final ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// 쓰레드 로컬에 데이터 저장
threadLocal.set(new Object());
// 쓰레드 로컬에서 데이터 사용
Object data = threadLocal.get();
System.out.println(data);
// 메모리 누수 방지를 위해 쓰레드 로컬 데이터 제거를 주석 처리
// threadLocal.remove();
});
thread.start();
thread.join();
// 쓰레드가 종료된 후에도 threadLocal 객체는 여전히 데이터를 참조
System.gc();
System.out.println("Thread has finished but thread local data might still be present.");
}
}
C에서는?
C언어 자체는 동시성을 직접 지원하지는 않지만, 멀티스레딩 라이브러리를 사용하여 병렬 프로그래밍을 구현할 수 있고, 이 과정에서 다양한 동시성 문제가 발생할 수 있습니다.
C언어에서 멀티스레딩을 구현하는 주요 방법 중 하나는 POSIX 스레드(Pthreads) 라이브러리를 사용하는 것입니다. 스레드를 사용하면 여러 실행 경로가 동시에 수행되면서 다음과 같은 동시성 문제가 발생할 수 있습니다:
- 경쟁 상태(Race Condition): 두 개 이상의 스레드가 데이터를 동시에 읽고 쓰려고 할 때, 데이터의 최종 상태가 스레드가 실행되는 순서에 따라 달라질 수 있습니다.
- 데드락(Deadlock): 두 개 이상의 스레드가 서로가 소유한 자원의 해제를 기다리면서 영원히 대기하는 상태입니다.
- 라이브락(Livelock): 스레드들이 계속해서 서로를 피해 실행을 반복하지만, 실제로는 어떤 유용한 일도 진행되지 않는 상태입니다.
- 스타베이션(Starvation): 하나 또는 일부 스레드가 필요한 자원에 접근하지 못하고 무한정 대기하는 상태입니다.
C언어에서 이러한 동시성 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다:
- 뮤텍스(Mutex): 뮤텍스를 사용하여 자원에 대한 접근을 하나의 스레드만 할 수 있도록 제한할 수 있습니다.
- 세마포어(Semaphore): 세마포어를 사용하여 동시에 자원을 접근할 수 있는 스레드의 수를 제한할 수 있습니다.
- 조건 변수(Condition Variables): 스레드들이 특정 조건이 충족될 때까지 대기하도록 할 수 있어, 자원의 효율적인 관리가 가능합니다.
- 바리어(Barrier): 모든 스레드가 바리어 지점에 도달할 때까지 기다리도록 하여, 특정 작업의 동시 완료를 보장합니다.
이러한 도구들을 적절히 사용함으로써 C언어로 작성된 멀티스레드 프로그램의 동시성 문제를 관리하고 해결할 수 있습니다.
'BACKEND.* > JAVA' 카테고리의 다른 글
전략 패턴 (0) | 2024.05.31 |
---|---|
템플릿 메서드 패턴 (0) | 2024.05.31 |
Virtual Thread vs Thread (1) | 2024.04.10 |
Stream (0) | 2022.10.08 |
Spring vs SpringBoot (0) | 2022.10.08 |