본문 바로가기
생각

메인 스레드, 커스텀 스레드, 데몬 스레드 테스트 찍먹일기 : multiThread & concurrency 2

by 6161990 2024. 10. 9.



Thread main 은 언제 만들어진걸까? 프로세스가 실행되려면 최소한 하나의 스레드는 존재해야한다고 이전 포스팅에서 설명했다. 왜냐면 정말 일을 doing 하는 건 스레드이기 때문이다. 자바에서는 실행시점에 main 스레드가 만들어지도록 설계되었고 main 스레드가 main() 메서드를 실행한다. 이걸 주관하는 건 JVM이다.

그럼 또 다른 스레드 Thread-0 은 어디서 튀어나온 애일까? TwiceThread 같은 사용자 정의 스레드를 생성하고 start() 메서드를 호출하면, JVM은 새로운 스레드를 생성한다. 그리고 스레드 생성 차례에 맞게 Thread-0, Thread-1 같은 순서로 이름붙인다. 그래서 Thread-0 이 응애하고 출력된 것.

사진을 보면 start() 를 호출했는데 내부적으로 run() 되었다. 각각 스레드의 생성과 실행이다. start() 를 호출했는데 왜 run() 되는 걸까? JVM 은 새로운 스레드가 시작되면 자동으로 run() 메서드를 호출한다. 이 호출은 JVM의 스케쥴러에 의해 관리된다. run() 메소드를 호출하는건 Thread-0 이다. 결과적으로 run() 은 start() 가 호출되는 Main 스레드와 다른 독립적인 스레드에서 작업을 수행한다. 그림으로 스택 프레임을 함께 설명해보면 다음과 같다. 




스택 프레임은 함수나 메서드가 호출될 때 생성되는 데이터 구조다. 함수의 실행에 필요한 곁다리들을 저장한다. 곁다리 중 하나가 this 참조자다. 어떤 인스턴스의 메소드를 호출하는지 참조하기위해, 스택 안에 해당 메소드 참조값을 저장해둔다. 그게 바로 this. 이들은 함수 호출 스택 Stack 에서 관리된다. 한편, 스레드가 너무 많이 생성되어 스택 프레임이 너무 많이 쌓일 경우 스택의 크기를 초과하게 되어 발생하는 현상이 그 유명한 Stack Overflow. 재귀함수가 종료 조건없이 호출될 때 이런 문제가 발생한다. main 스레드는 main() 메서드의 스택 프레임을 스택에 올리면서 시작하고, Thread-0 스레드는 run() 메서드의 스택 프레임을 스택에 올리면서 run() 메서드를 시작한다. run() 을 직접 호출하면 경고가 뜨는 이유가 여기 숨어있다. 

Calls to 'run()' should probably be replaced with 'start()'



run() 메소드를 직접 호출하면 그건 Main 메서드에서의 실행이다. 새로운 스레드가 생성되지않는다는 건데, 그건 멀티스레딩하지 않기 때문에 경고문이 뜨는거다.  "멀티하게 하려고 직접 커스텀 스레드 생성했는데 왜 그걸 같은 스레드에서 실행하려고 하니 이 바보야?" 같은 의미를 정중하게 하고 있다고할까..

질문이 하나 더 생겼다. 실행할 때 마다 Thread-0 의 위치는 왜 달라질까? CPU의 마음이 매번 달라지기 때문이다. 여러번 말하지만, 각 스레드의 실행은 CPU에 의해 관리된다. CPU 가 MainThreadTwiceThread 를 언제 스위칭할지 모른다. 그리고 한 스레드가 얼마동안 실행되는지도 모른다. 한 스레드가 완전히 다 동작하고 나서 다른 스레드가 시작될 수 있고, 서로 번갈아가면서 수행할 수도 있다. 이것 역시 CPU 마음. CPU 코어가 n 개라면 동시 병렬 수행을 할 수도 있다. 이렇게 많은 가능성에 따라 스레드의 실행 시점이 달라지기 때문에 Thread-0의 출력이 매번 달라지게 된다. 이것이 CPU 의 process scheduling ! 

 JVM 의 스레드 생성과 CPU 의 process scheduling 이 조금 난해했다. 스레드의 실행과 관리는 두 단계에서 이루어진다. JVM이 스레드를 생성하고 관리하고 CPU는 스레드의 실제 실행을 처리하고, 실행 중인 스레드를 전환하는 역할을 맡는다. JVM의 스케줄러는 스레드를 관리하고, CPU의 스케줄러는 실행을 제어하는 구조로 각 역할이 있다. 

지금까지 내가 만든 스레드를 특정짓자면 "사용자 스레드" 다. 왜 user thread 일까. 스레드를 생성하고 처리하는 데에 관련된 주체가 "사용자" 이기 때문이다. 유저에 의해서 시작되고 종료된다는 말이다. 일반적으로 애플리케이션의 핵심 로직을 처리한다. 이와 반대로, 부가적인 로직을 처리할 수 있는 스레드를 "데몬 스레드" 라고 한다. 데몬은 그리스 신화에서 신과 인간의 중간적 존재하는 인물이다. 사용자와 컴퓨터간의 중간적 역할을 맡고 있다는 건가? 그건 잘 모르겠지만, 암튼 유령같긴하다. 백그라운드에서만 보조적인 작업을 수행하고, 모든 사용자 스레드가 종료되면 데몬 스레드도 소리소문없이 사라진다. 자신의 본 분을 다하지 못한 상태일지라도 일단 사라진다. 실험해보았다. 







메인 스레드의 역할은 DaemonThread 의 스레드 생성이었다. 역할을 다했기에 종료되었고 이와 동시에 데몬 스레드도 종료되어버렸다. 자신이 데몬인지도 대답하지 못한 채... JVM은 데몬 스레드의 실행 완료를 기다리지 않고 종료되기 때문이다. 데몬 스레드가 아닌 모든 스레드가 종료되면, 자바 프로그램도 종료된다. 

이런 애가 왜 존재해야할까? 스택 오버 플로우에서 데몬 스레드 활용사례를 찾아보았다. 무려 16년 전에 시작된 퀘스쳔이다. 

https://stackoverflow.com/questions/190010/daemon-threads-explanation




사용 예제 중 하나는 자동저장이다. 내가 지금 블로그 포스팅을 하면서 중간 중간 글이 저장되는 건 데몬 스레드 덕분일 수 있다는 거다. 아마 티스토리도 데몬 스레드를 이용하지 않을까 싶다. 왜냐면 블로그 포스팅을 하다가 실수로 창을 꺼버린 적이 있는데, 작성해두었던 글이 임시 저장되어있지 않았던 경우가 많았다. 네이버는 잘 되있던데... 다른 예제는 garbage collection !! 주로 힙 메모리를 대상으로 사용하지 않는 객체들을 청소하는데, 딱 데몬 스레드에서 할만한 일이다. 

 

자바는 JVM 이 메모리 관리와 스레드 관리를 자동으로 처리해주지만, C 에서는 프로그래머가 직접 메모리와 스레드 관리를한다. C 언어에서는 데몬 스레드 개념이 없는 이유다. 제어력이 높은 언어이기때문에 백그라운드에서 동작하는 스레드가 없다. 


메인 스레드에 대해서도 여기 저기 찾아보고 커스텀 스레드, 데몬 스레드도 테스트 해보았다. 스레드를 만드는 방법은 Thread 를 상속하는 것 말고 Runnable 을 구현해도된다. 뭔 차인지 까먹어서 직접 실험해보려고 생각중이다. 그리고 많은 스레드를 동작시켰을 때 순서를 어떻게 제어할 수 있을지 고민해보려고 한다.