멀티태스킹

 

1. 일상 생활에서의 멀티태스킹
2. 컴퓨터에서의 멀티태스킹
2.1. 관련 항목
3. 스타크래프트에서의 멀티태스킹


1. 일상 생활에서의 멀티태스킹


한 번에 2가지 이상의 을 동시에 처리하는 것으로 "다중작업" 또는 "다중과업화"라고도 한다.
사람에게도 멀티태스킹(여기서는 다중 업무 처리) 능력을 요구하는 직종이 많은데, 이 경우에는 그냥 여러가지 일을 떠맡기는 꼴이다.
중국 촉한의 재상 비의조선시대의 류성룡 등이 이런 멀티태스킹 능력이 뛰어났다는 일화가 남아 있다. 각각 항목에도 있지만, 그 일화가 공통적으로 바둑을 두면서 정무를 처리했다는 일화라는 게 공통점.

2. 컴퓨터에서의 멀티태스킹


행위자가 사람이 아닌 컴퓨터라는 점만 제외하면 사전적인 의미는 위의 항목과 같다.
최초의 컴퓨터는 한 번에 하나의 작업만을 처리할 수 있었다. 어떤 프로그램을 실행하고 있을 때 다른 프로그램을 실행하려면 실행 중인 프로그램의 작업이 끝나야 다른 작업을 수행할 수 있는 블로킹 방식이었기 때문. 채팅 프로그램으로 비유하면 무전기처럼 채팅해야 하는 셈. 프로세서와 운영체제 기술이 발달하면서 한 번에 여러가지 작업을 처리할 수 있게 되었다. Microsoft Windows를 예로 들자면, 동시에 여러 프로그램 창을 띄워서 쓰는 것.
현대의 운영체제는 여러가지 일을 동시에 돌릴 수 있게 하는데, 기본적인 원리는 단순하다. 한 가지 일을 하다가, 잠시 멈추고 또 다른 일을 하고, 또 멈추고 다른 일을 하다 보면 언젠가는 결국 모든 일을 마치게 된다. 이 동작이 엄청 빨라지면 한 번에 여러가지 일을 하는 것'''처럼 보이게''' 되는 것이다. 그림 여러 장을 그려놓고 빠르게 연속 재생하면 눈에 잔상이 남아 그것이 움직여 보이는 것처럼, 여러가지 동작을 엄청 빨리 하면 그것이 분신술처럼 보이게 되는 것이 바로 멀티태스킹의 원리이다. 유저 프로그램들은 마치 동시에 실행되는 것처럼 보이지만, 커널 내부에서는 운영체제가 각각의 프로그램에 CPU 사용권을 적절히 분배하는 작업이 반복되고 있다. 하드웨어 자원은 한정되어 있는데 멀티태스킹을 위해 더 많은 작업을 바쁘게 수행해야 하므로 어느 특정 프로그램 하나가 실행할 때 다른 여러 프로그램들과 함께 실행할 때가 실행하지 않을 때보다 더 느릴 수밖에 없지만, 블락킹 상태를 자주 보면서 작업하는 것보단 훨씬 나은 UX를 제공할 수 있기 때문에 멀티태스킹이 정착하게 된 것이다.[1] 보다 효율적인 멀티태스킹이 되기 위해서는 프로세스들을 적절히 분배하는 작업이 이루어져야 하는데 이러한 작업을 프로세스 스케줄링이라고 한다.
참고로 최신 리눅스 커널의 경우 보통 한 프로세스에 4ms(4/1000초)간 CPU 사용권을 부여한다고 한다. 밀리초 단위라서 짧게 느껴질지도 모르겠지만, 3~5 GHz 정도의 속도로 동작하는 요즘 일반 데스크탑용 CPU는 4ms면 1천 2백만~2천만 번 정도의 클럭 사이클을 만들어서 최소 수 백만 번 이상의 연산을 수행할 수 있다. 사람으로 치면 '''4년이라는 매우 긴 시간'''을 부여하는 것이다.
어찌 보면 사람의 약점을 이용한 원리로 보이지만, 사람의 약점만을 이용한 것은 아니다. 모든 컴퓨터 프로그램의 작업이 CPU만을 통하는 것이 아니고 CPU의 명령에 의해 작동하는 다른 장치의 작업이 처리되는데 필요한 시간도 있는데다 CPU를 거치지 않는 작업도 있기 때문에 필연적으로 멀티태스킹이 아닌 단일 작업만을 처리하는 상황이라도 CPU의 유휴 상황은 발생하게 된다. 마치 네트워크 회선과 공유기의 관계처럼 이용률을 높이는 효과도 있는 것.
하지만, 과거의 멀티태스킹은 프로세스 단위로 멀티태스킹하는 '멀티프로세싱'이었는데 이러한 방식은 하드웨어 자원의 부담이 크기 때문에 아무리 잘 스케줄링 해도 한계가 있다. 하드웨어 자원의 부담을 줄이기 위해 프로세스보다 더 작은 실행 단위인 스레드가 도입되었으며, 각 스레드들이 작업(태스크) 단위로 수행하기 때문에 스레드별로 잘 분배하도록 스케줄링 되면 멀티스레딩이 멀티프로세싱보다 더 나은 멀티태스킹 효율을 보여줄 수 있다.
여기서 멀티 CPU나 멀티코어 CPU의 경우 CPU의 코어 개수만큼 더 많은 스레드들의 작업이 동시에 이루어질 수 있다. 이렇게 멀티스레드로 작업을 수행하는 것을 줄여서 '멀티스레딩'이라고도 부르는데, 코어 1개일 때보다 더 많은 스레드로 멀티스레딩 할 수 있다고 해도 일반적으로 2~8개의 코어로는 당연히 수십~수백 개의 프로세스[2]를 모두 처리하지 못하므로 각 CPU 코어에서도 작업을 교체하는 일이 수시로 벌어진다. 물론 n개 코어에서 n개의 프로세스가 동시에 실행되긴 하므로 '''잘만 하면''' 멀티 코어를 통해 성능을 끌어올릴 수 있다. 즉, 일감을 적절히 여러 프로세스에 분배하여 돌리면 이론적으로는 여러 개의 코어를 동시에 사용할 수 있으므로 실제로 성능 향상을 가져온다. 이를 위해서는 프로그래밍 시 멀티 코어를 활용하기 위한 멀티스레드의 적절한 병행화와 병렬화 설계가 필요하다. 그렇지 않으면 코어 하나만 미칠 듯이 돌아가고 나머지 코어들은 시답잖은 프로세스들을 깨작깨작 처리할 뿐이다.
그러나 멀티스레드를 이용한 병행 처리와 병렬 처리는 직접 구현하기 까다로운데, 스레드 여러 개 만든다고 해서 사용자가 원하는 멀티스레딩이 자동으로 이루어지는게 아니므로 여러 개의 스레드들을 어떻게 동작시키고 통제할 것인지를 고민해야 하며, 그렇게 고민을 하나하나 해결하다보면 소스코드의 길이가 싱글스레드일 때보다 더 길고 복잡해질 수밖에 없다. 그런 어려움을 해소하기 위해 병행 처리용 라이브러리나 병렬 처리용 라이브러리를 이용하면 비교적 간단한 소스코드의 길이로 구현할 수 있고, 프로그래밍 언어 차원에서 이를 구현해주는 표준 라이브러리가 없을 경우 OpenMP, TBB, PPL 등의 외부 라이브러리를 가져와서 구현할 수 있다. 하지만 소스코드의 길이가 길든 짧든 적용할 수 있는 부분은 순서에 관계없는 단순한 반복 작업들을 동시에 처리할 경우에나 고려하는 정도로 한정적이다. 그리고 멀티코어 프로세서 환경을 고려하여 여러 프로세스들에게 일감을 적절히 싣더라도, 여러 개 코어를 쓴다고 해서 프로그램 실행 속도가 코어 개수만큼 빨라지지는 않는다. 가장 큰 이유는 모든 작업을 '100% 동시’에 처리할 수는 없기 때문이다. 예를 들어, A라는 작업을 수행하는 데 B라는 선행 작업의 결과가 필요하다면, 필연적으로 B를 먼저 수행한 후에만 A를 수행할 수 있다. A와 B를 같이 돌려 봤자, A는 B가 끝나기 전까지 기다릴 수밖에 없다. 때문에 작업에 따라서 멀티태스킹으로 인한 효율이 프로그램의 로직에 따라 천차만별이다.
실행 중인 프로그램(정확히는 프로세스) 내부의 여러 스레드끼리 멀티스레딩 하는 것도 넓은 의미로는 멀티스레드를 이용한 일종의 멀티태스킹으로 볼 수도 있지만 컴퓨터 멀티태스킹의 유래가 서로 다른 실행 프로그램들을 동시에 처리하는 것에서 출발했던 개념이라 프로세스 내부의 병행 처리, 병렬 처리하는 형태는 행위자인 연산 장치 입장에서는 병행 컴퓨팅와 병렬 컴퓨팅, 프로그래머 입장에서는 병행 프로그래밍과 병렬 프로그래밍이라는 용어로 따로 취급하는 편이다.
굳이 집합 기호로 표현하면 '병렬 처리[3] ⊂ 병행(동시) 처리[4] ⊂ 프로세스 내부의 멀티스레딩[5] ⊂ 운영체제 차원의 멀티스레딩 ⊆ 멀티태스킹'인 셈.[6]
또한, 멀티스레딩의 속도 증가치가 스레드 개수만큼 정비례하게 증가하지 않는 이유는 운영체제가 한 프로세스를 처리하다가 다른 프로세스로 넘어갈 때 '''컨텍스트 스위칭'''(문맥 교환)이라는 작업이 수반되면서 발생하는 '''오버헤드''' 때문이다. 즉, 바로 다른 프로세스로 바꿀 수 있는게 아니라, 중간에 작업 전환을 하는데도 시간이 필요하다는 이야기. 각각의 프로세스는 자신이 사용하고 있는 컴퓨터의 자원[7]들이 있는데 프로세스 전환 시 사용 중인 자원들도 전환해야 한다. 물론 데이터를 버릴 수는 없으니 작업 중인 데이터를 저장하고, 다른 프로세스에서 쓸 데이터를 메모리에서 가져오는데 시간이 걸린다. CPU 아키텍쳐 및 운영체제, 메모리 사용량에 따라 다르지만 대개 몇백 ns에서 몇 μs 정도의 시간이 걸린다고 보면 된다.[8] 이러한 전환 작업은 사람이 전혀 느낄 수 없을 정도로 짧고 유저 프로그램에게조차도 짧은 시간이지만, 커널 개발자나 60FPS을 뽑아야 할 정도로 퍼포먼스에 민감한 고사양 게임을 개발하는 렌더링 프로그래머에게는 꽤 비싼 작업으로 여겨지는 듯. 물론 멀티 프로세스를 기반으로 하는 서버에서나 고려해볼만한 상황이고 유저 입장에서는 찰나라고 봐도 될 정도로 짧다.[9]
물론, 서로 다른 작업을 동시에 처리하는 것이 아니라 똑같은 작업들을 동시에 처리해야 할 경우, 스레드별로 분담해서 스레드끼리 서로 영향을 주지 않고 각 스레드에게 주어진 작업만 처리하도록 프로그래밍 하면 최소한 그 구간만큼은 스레드끼리 컨텍스트 스위칭 할 필요가 없어져 오버헤드가 크지 않게 되는데 병렬 처리가 이에 해당된다.[10] 하지만 똑같은 일이라도 스레드마다 처리 속도가 들쭉날쭉하기 때문에 이를 안정적으로 동작하기 위해 가장 오래 걸리는 스레드의 처리 완료 시간에 맞춰서 배리어 락을 걸다보면 일찍 처리 완료된 스레드의 대기 시간 발생이 불가피해지므로 걸리는 시간이 스레드 개수만큼 나누어진 시간으로 되지 못 하는 단점이 있다. 대기 시간으로 인한 전체 처리 시간의 지연을 줄이기 위해 이미 처리 완료된 스레드가 처리가 더디는 스레드의 남은 일을 훔쳐서 처리해주는 '태스크 스틸링', 더 나아가면 '종속성 그래프' 방식을 통해 보완할 수 있지만 실제로 그렇게까지 활용하는 곳은 많지 않고 제대로 병렬화 해봤자 태스크 스틸링까지 구현하는 경우가 대부분이다. 프로그램에 따라 다르지만 종속성 그래프를 구현해야 할 정도는 비교적 극단적인 경우인데다 구현하기가 복잡해서 안 그래도 어려운 멀티스레딩에서 더 어려워지므로, 태스크 스틸링까지만 구현해도 CPU의 모든 코어 자원을 대부분 점유할 수 있다.
멀티태스킹에는 두 종류가 있는데, 첫 번째는 '''비선점형 멀티태스킹''', 두 번째는 '''선점형 멀티태스킹'''이다.
프로그램은 실행되면 프로세스가 되고 프로세스는 역시 여러 쓰레드를 실행시키는데 여러 프로그램에서 만들어지는 이 멀티쓰레드는 CPU라는 한정된 자원을 서로 사용하고자 경쟁 관계를 가진다. 그러므로 운영체제는 여러 쓰레드들에게 뒤죽박죽 되지 않도록 CPU의 시간을 나누어 돌아가며 실행하도록 하는데, 이 때 CPU를 차지하고 있는 쓰레드가 자신이 이제 CPU 연산이 필요 없음을 나타냈을 때에만 운영체제가 이를 회수할 수 있는 경우를 비선점형 멀티태스킹이라고 하고, 프로세스가 CPU를 차지해서 사용하더라도 운영체제가 타이머나 여타 트리거를 통해 개입하여 강제로 CPU 사용을 빼앗아 올 수 있는 경우를 선점형 멀티태스킹이라고 한다. 쉽게 말해 운영체제가 응답없는 프로세스를 강제로 죽이고 자시고 할 수 있으면 선점형, 그게 안 돼서 닥치고 리셋해야만 하는 경우라면 비선점형이다. 선점형 멀티태스킹은 스케줄러에 따라 차이가 지긴 하지만, 원론적으로는 보통 시간을 따로 정해 놓고 이 시간마다 선점(preemption)이 일어나는데, 이 시간 단위를 퀀텀 혹은 슬라이스라 한다. 퀀텀이 너무 낮으면 프로세스 바꾸는데 소모되는 리소스가 커지고, 퀀텀이 너무 높으면 반응 속도가 느려진다. 보통 빠른 타수의 사람이 키를 입력하는 속도가 약 100ms 정도이기 때문에, 퀀텀도 이정도 수준에서 좀더 낮게 결정되는 경향이 있다. 리눅스의 경우 퀀텀을 따로 설정할수도 있긴 하지만, 어차피 리눅스에서 디폴트로 사용하는 CFS 스케쥴러는 상황을 보면서 작동하기 때문에 퀀텀대로만 선점을 하지는 않는다.
현행 대부분의 OS들, 즉 Microsoft Windows, 리눅스, OS X 등은 모두 선점형 멀티태스킹인데 이유를 생각하면 당연하다. 범용 운영체제인 만큼 여러 프로그램들을 실행시켜야 할텐데 그 프로그램들은 모두 검증된 프로그램이 아니고 버그도 갖고 있다. 만약 어떤 프로그램이 이러한 이유로 자원을 가진 채로 버그에 빠지거나 자원을 독식하려 한다면 멀티태스킹이 제대로 이루어지지 않을 것이다. 다만 Windows 95에서는 16비트 프로그램에서는 비선점형 멀티태스킹으로 작동하는데 이는 하위 호환성을 위해 운영체제 구조상 어쩔 수 없었던 부분이다. 기존 16비트 시절의 Windows(1.0~3.1)가 비선점형으로 만들어졌기 때문. 참고로 OS/2는 16비트 Windows 프로그램도 선점형으로 돌린다.
프로세스의 스케줄링을 구현하는데도 여러가지 알고리즘이 존재한다. 리눅스에서는 O(1) 스케쥴러가 사용되다가 2.6.23 버전부터 CFS(Completely Fair Scheduler)가 도입되었다.
모바일 운영체제인 iOS에선 패스트 스위칭과 멀티태스킹이 공존하는 형태이다. 자세한 내용은 해당 문서 참조.

2.1. 관련 항목



3. 스타크래프트에서의 멀티태스킹


스타크래프트에서 화면지정이나 부대지정 등을 이용하여 동시에 여러가지 일을 하는 것도 멀티태스킹이라고 한다. 자세한 사항은 멀티태스킹(스타크래프트) 문서 참조.

[1] 멀티태스킹이 일반화된 지금 시점에 멀티태스킹이 불가능해서 블락킹 상태를 자주 봐야 하는 시스템에서 작업한다면 1분 1초가 급한 사람에겐 매우 답답해 하면서 때려치우고 싶을 것이다.[2] 윈도우즈의 경우 Ctrl+Alt+Del키를 눌러 작업 관리자에서, 리눅스에서는 "ps -A"명령어로 프로세스의 개수를 확인해보자. 대략 백 개가 넘어가는 프로세스들이 서로 부지런히 돌고 있음을 알 수 있다. 게다가 실제로 CPU에선 프로세스보다 작은 스레드 단위로 프로그램이 처리되므로, 동시에 처리해야하는 작업(태스크)의 개수, 그러니까 스레드의 개수는 프로세스 개수보다 훨씬 많다.[3] 단, 모든 스레드들이 처리하고자 하는 대상의 성격이 서로 같아야 한다. 즉, 비슷한 역할이어야 한다는 것.[4] 이 수준부터 동시 멀티스레딩(SMT)에 해당된다. 스레드마다 처리하고자 하는 대상이 달라도, 다시 말해서 역할이 서로 다른 스레드들도 동시에 동작하기만 하면 SMT가 성립된다. 하드웨어가 어떤 형태인지는 정해진 것이 아니며, 인텔의 하이퍼스레딩은 코어 하나에 스레드 2개가 동작할 수 있도록 구현된 양방향 SMT일 뿐이다. AMD의 라이젠 시리즈부터 도입된 SMT도 양방향 SMT를 가리킨다.[5] 프로세스 내부의 멀티스레딩이지만 병행 처리가 아닌 경우에는 임시 멀티스레딩이 있다. SMT를 지원하지 않는 싱글코어 CPU에도 구현 가능한 멀티스레딩이지만, 스레드들이 짧은 시간 단위로 번갈아 수행하기 때문에 실행하는 순간은 싱글스레드로 동작하는 것과 다를 바 없다. 시분할 멀티스레딩이라고도 부르며, 동시성이 없기 때문에 '가짜 멀티스레딩'이라고 부르는 이유이기도 하다.[6] 멀티태스킹이 좀 더 넓은 범위인 것은 멀티스레딩이 아닌 멀티프로세싱도 멀티태스킹에 속하기 때문이다.[7] CPU 레지스터, 메모리 페이지 테이블, 파일 디스크립터 테이블 등등.[8] 출처 : http://wiki.osdev.org/Context_Switching. 펜티엄 4에서 하드웨어 스위칭이 995 ns정도로 측정되었다.[9] 여기서 유저는 엔드 유저 뿐만이 아니라 퍼포먼스를 최우선으로 둘 필요가 없는 애플리케이션 레벨 프로그래머도 포함이다.[10] 병행 처리는 서로 다른 작업들을 동시에 처리하는 형태이므로 서로 다른 작업을 맡은 스레드들을 잘 통제하기 위해 컨텍스트 스위칭이 병렬 처리에 비해 상대적으로 많을 수밖에 없다. 그 대신 병렬 처리에 비하면 적용할 수 있는 범위가 다양한데, 처리해야 할 작업들이 서로 같든 다르든 동시에 처리하는 조건만 충족하면 되기 때문. 동시 처리 조건조차 만족하지 않고 실행 순간마다 스레드 1개씩 번갈아 가면서 처리하면 임시 멀티스레딩이 된다.