[개요]
개발을 진행하다 보면 필수적으로 사용하게 되는 테크닉이 Thread의 개념입니다. 요새는 Task로 더 편하게 사용할 수 있지만 Task 내부는 Thread로 구성되어 있으며 실질적으로 사용되는 위치에 따라서 Thread와 Task를 구분하여 사용할 줄 알아야 합니다. 좋은 기능이 있어도 장단점을 알고 적절한 곳에 사용할 수 있느냐도 개발자의 급을 나눌 수 있다고 생각하거든요.
오늘은 Thread의 대강 기념과 어떻게 하면 안전하게 사용할 수 있고 어떤 방식으로 Thread를 기다리는지 알아보겠습니다.
[Thread 정의]
Thread의 정의는 프로세스 내에서 실제로 작업을 수행하는 주체를 의미합니다. 뭐든 정의는 어렵지만 중요하기도 하지요. 정의가 어렵다면 요새 컴퓨터가 발전하면서 cpu가 점차 세분화되고 있는데 CPU >> CORE >> THREAD라고 생각하시면 됩니다.
CPU : 논리적 사고를 총괄하는 주체(집안일회사) CORE : 논리적 사고를 바탕으로 업무를 진행하는 일꾼(파트타임 직원) THREAD : 업무를 진행하기 위해 사용되는 도구 (세탁기, 로봇청소기, 냉장고 등) |
집안일이라는 프로세스를 진행시키기 위해서 집안일회사에서 파트타임직원을 파견해서 파트타임 직원이 업무를 효율적으로 진행하기 위해 여러 전자도구를 사용한다고 보시면 됩니다.
로봇청소기에게 청소해 하고있으면 직원은 다른 일을 해도 청소기는 돌아가죠. 청소기가 다 돌았으면 알림을 받으면 아 청소기가 끝났구나 하고 먼지통을 비워주면 됩니다.
즉 프로그램을 코딩할 때 연산이 오래걸리는 잡무를 여러 Thread에 분산시켜서 병렬처리를 하게 되면 속도가 더 빠르게 되는 원리입니다.
[Thread 선언과 시작]
Thread를 선언하고 시작하는 방법은 간단합니다. new 키워드로 선언하고 시작해주면 됩니다.
Do_Work는 함수로 매개변수는 존재하지 않아도 됩니다. 그냥 선언만 되어있으면 되고 Thread Start 이후에 1회 실행되는 함수입니다. Thread는 도구 개념으로 사용할 때 새로 선언해서 명령을 내려줘야 사용이 가능하다는 점 참고하시면 됩니다.
[Thread Abort 종료]
Thread를 종료하는 방법은 초기에는 Abort를 사용하고 객체를 null로 변경해주곤 했습니다. 이렇게 진행하면 중간에 어느 지점에서 Thread가 종료되는지 알 수 없을뿐더러 사용 중인 변수가 있었다면 크리티컬섹션으로 묶여서 다른 Thread에서 접근이 불가능해져 프로그램이 멈추는 현상이 발생될 수 있습니다.
이런 문제점 때문에 현재는 .NET 5부터 Abort는 사용이 금지되어 있으며 다른 방법으로 Thread를 종료해야 합니다.
Thread를 생성하고 실행한 다음 강제로 종료하려 할 경우 System.PlatformNotSupportedException이 발생됩니다. Thread abort is not supported on this platform. 메시지는 제가 테스트로 개발한 프로그램이. NET8이기 때문에 발생되는 문제입니다.
. NET5 아래이거나. NET Framework를 사용하시는 분들은 적용이 되실 수 있는데 절대 사용하면 안 되는 명령어입니다.
[Thread 안전하게 종료하는 방법]
Thread를 안전하게 종료하는 방법 중 가장 좋은 방법은 플래그(Flag)를 사용하는 방법입니다. Flag는 해당 Thread에 Bool변수를 하나 선언해서 루프를 강제적으로 탈출할 수 있도록 해주는 방법입니다.
Thread를 선언할 때 IsBackground 옵션을 설정하는 방법도 좋은 방법이지만 추후에 포스팅 할 Running 상태를 확인하기 위해서는 해당 방식보단 Flag방식을 이용하는 걸 추천드립니다.
아래 프로그램으로 추가 설명을 해봅시다.
프로그램은 간단합니다.
Thread A : 1 ~ 100까지 차례대로 출력 Thread B : 100 ~ 200까지 차례대로 출력 Create Button : Thread A, B 생성 및 시작 Abort Thread A : Thread A 종료 Abort Thread B : Thread B 종료 |
프로그램을 구성하는 변수입니다. EventWaitHandle이라는 친구를 사용하는데 Join은 Threadpool에서 사용할 수 없지만 Event는 사용할 수 있기에 Join은 몰라도 EventWaitHandle은 알아야 합니다.
객체 선언 시 뒤에 변수가 두 가지가 들어갑니다. 첫 번째 칸(true)은 Initial 상태를 의미하며 제어권을 돌려줄지 안 돌려줄지를 의미합니다. 제어권이란 프로그램을 해당 스레드가 끝날 때까지 기다릴 것이냐, 다른 일을 진행할 것이냐를 선택하는 것으로 동기 비동기 개념으로 이해하셔도 됩니다. true는 비동기를 의미하며 해당 스레드가 일할동안 안 기다리겠다입니다.
EventResetMode는 객체를 수동으로 리셋할 건지 자동으로 리셋할 건지를 선택하는데 자동으로 리셋하는 경우 한 개의 스레드만 생성되고 동기상태로 바뀌어 버립니다. 가능하면 ManualReset으로 사용하고 커스터마이징 하시는 걸 추천드립니다.
Thread를 생성하고 각각 Thread를 종료시키기 위한 Flag 세팅을 진행합니다.
Thread의 실질적인 일하는 장소입니다. 전달받은 매개변수는 무시하셔도 되고 단순하게 1 ~ 200까지 화면에 순차적으로 출력하는 업무를 진행합니다.
Thread A는 별 특징 없이 Flag가 false로 변경되면 Thread가 종료되고 Thread B는 Flag가 false로 종료되면 EventWaitHandle에 나 종료된다라는 신호를 Set 해줍니다.
프로그램은 큰 구성은 없으니 소스만 확인하셔도 충분히 따라 하실 수 있을 겁니다. 다만 주의할 점은 Flag를 사용하여 종료 시 본인이 원하는 시점에서 정상적으로 종료되는지는 추가적으로 확인이 어렵다는 점을 참고해야 합니다. 실제적으로 위 예제에서도 ThreadB 종료 Flag가 Set 되었는데 1회 더 출력된 걸 확인할 수 있습니다. Thread를 사용한 프로그래밍 할 때 반드시 점검되어야 하는 부분이며 사용에 항상 주의가 필요하겠습니다.