프로그램을 제대로 돌아가게 작성하는 일은 정말 어렵다.
하지만 여러 작업을 동시에 실행하는 프로그램을 제대로 돌아가게 작성하기는 훨씬 더 어렵다.
그럼에도 불구하고 왜 작업을 동시에 실행하는 문제에 신경을 써야할까?
스레드는 자바 언어에서 피할 수 없는 특성이고, 복잡한 비동기 코드를 더 단순한 순차적 코드로 바꿔 복잡한 시스템을
단순하게 개발할 수 있게 해주기 때문이다.
초창기 컴퓨터에는 운영체제 자체가 없었다. 운영체제 없이 하드웨어 위에서 바로 실행되는 프로그램은 작성하기도 힘들었을 뿐만 아니라
한 번에 하나의 프로그램만 실행하느라 그 값비싸던 자원을 비효율적으로 사용 할 수 밖에 없었다.
영화 이미테이션 게임에 나오는 튜링머신과 같이, 배선이나 진공관을 직접 계산시마다 바꿔줘야 했기 때문에
다수의 계산을 해야 할 때는 많은 제약이 있었다.
그래서 나온 것이 현재까지도 거의 모든 컴퓨터의 방식인 프로그램을 내장하는, 폰노이만식 컴퓨터이다.
순차적 프로그래밍이 특징인 폰노이만식 컴퓨터의 프로그램을 제어하기 위해, 운영체제라는 것이 개발됐다.
운영체제는 여러 개의 프로그램을 각자의 프로세스 내에서 동시에 실행할 수 있도록 발전됐다.
프로세스는 각자가 서로 격리된 채로 독립적으로 실행되는 프로그램으로
운영체제는 프로세스마다 메모리, 파일핸들, 보안권한 등의 자원을 할당한다.
프로세스끼리의 통신은 소켓, 시그널 핸들러, 공유 메모리, 세마포어, 파일등의 다양한 통신수단이 제공된다.
여러 프로그램을 동시에 실행할 수 있는 운영체제를 개발하게 된 요인은
- 자원활용
위에서 말했듯, 현재의 거의 모든 컴퓨터는 순차적 프로그래밍 방식을 취하고 있기 때문에,
프로그램은 때로 입출력과 같이 외부 동작이 끝나길 기다려야하고 이런 처리가 끝나기 전에는 다른 일을 처리할 수 없다.
따라서 하나의 프로그램이 기다리는 동안 다른 프로그램을 실행하도록 지원하는 편이 더 효율적이다.
- 공정성
한번에 프로그램 하나를 끝까지 실행해 종료된 이후에야 다른 프로그램을 시작하는 것 보다는 더 작은 단위로
컴퓨터를 공유하는 방법이 바람직하다.
- 편의성
여러 작업을 전부 처리하는 프로그램 하나를 작성하는 것 보다, 각기 다른 일을 하나씩 처리하고 필요할 때 프로그램 간에 조율하는 프로그램을 여러 개 작성하는 편이 더 쉽고 바람직하다.
순차적 프로그래밍 모델은 사람이 생각하는 방식과 비슷해서 직관적이고 자연스럽다.
언어 명세를 보면 주어진 동작이 실행된 이후 "어떤 일이 일어나는가"가 명료하게 정의되어 있다.
라면을 끓이기 위해 찬장을 열고, 주전자 안에 물을 붓고, 안에 물이 적당히 담겼나 본다.
물이 부족하면 더 넣어 렌지 위에 얹고, 렌지를 켠 후 물이 끓을 때 까지 기다린다.
물이 끓을 때 까지 기다리는 마지막 단계는 어느 정도 비동기적인 성격이 있으며,
다시 말해 물이 데워지는 동안은 몇가지 선택이 있다.
그냥 기다릴 수도 있고, 설거지를 해도 되고, 그릇을 준비 할 수도 있다.
주전자나 토스트기를 만드는 회사는 제품이 비동기적으로 사용되는 경우가 많다는 것을 알기 때문에,
뭔가 작업이 끝나면 소리를 내도록 만드는게 보통이다.
일을 잘하는 사람들을 보면 순차적으로 할 일과 비동기적으로 할 일 간의 적절한 균형을 잘 찾아낸다.
프로그램도 마찬가지다.
자원 활용, 공정성, 편의성 등 프로세스 개념을 만들어내게 된 것과 같은 동기를 갖고 스레드가 고안됐다.
스레드로 인해 한 프로세스 안에 여러 개의 프로그램 제어 흐름이 공존할 수 있게 되었다.
스레드는 메모리, 파일 핸들과 같이 프로세스에 할당된 자원을 공유한다.
하지만 각 스레드는 각기 별도의 프로그램 카운터, 스택, 지역변수를 갖는다.
또한 프로그램을 스레드로 분리하면 멀티프로세서 시스템에서 자연스럽게 하드웨어 병렬성을 이용할 수 있다.
현대의 운영체제는 대부분 프로세스가 아니라 스레드를 기본 단위로 CPU 자원의 스케줄을 정한다.
스레드는 자신이 포함된 프로세스의 메모리 주소 공간을 공유하기 때문에
한 프로세스 내 모든 스레드는 같은 변수에 접근하고 같은 힙에 객체를 할당한다.
자바 자체에 스레드 관련 기능이 내장되어 있다는 점은 어떻게 보면 양날의 칼이라고 할 수 있다.
스레드를 사용해 작성하는 프로그램이 더 많아졌기 때문에 스레드의 안전성에 대해 잘 알아야 한다.
동기화를 충분히 해두지 않으면 여러 스레드에서 실행되는 연산의 순서가 예측하기 어렵다.
@TreadExample
public class UnsafeSequence{
private int value;
public int getNext(){
return value++;
}
}
해당 프로그램에는 두개의 스레드가 있다고 가정해보자.
A value=9 ----------> 9+1=10 --------> value=10
B ------->value=9-----------> 9+1=10---------->value=10
타이밍이 좋지 않은 시점에선 두 개의 스레드가 getNext 메서드를 동시에 호출했을 때 같은 값을 가질 가능성이 있다.
value++ 는 하나의 연산처럼 보이지만,
값을 읽고, 읽은 값에 1을 더하고, 그 결과를 다시 기록하는 연산으로 구성되어 있다.
때문에 여러 스레드에서 실행되는 연산은 서로 간에 무작위로 끼어 들 수 있다.
이를 경쟁 조건 ( race condition ) 이라고 한다.
멀티스레드 프로그램이 동작하는 모습을 예측하려면 서로 간섭하지 않도록 공유된 변수에 접근하는 시점에 적절하게 조율해야 한다.
자바에선 synchronized 라는 동기화 수단이 제공된다.
@TreadExample
public class UnsafeSequence{
private int value;
public synchronized int getNext(){
return value++;
}
}
물론 동기화로 인해 컴파일러, 하드웨어, 실행환경 각각의 명령어 실행 시점이나 순서를 조정 할 수 없게 되면
최적화 작업에 대한 부담이 줄어들겠지만 성능 향상에 악영향을 끼칠 수도 있다.
물론 단일 스레드 프로그램에서도 최적화와 안정성이 중요하지만 다중 스레드 프로그램에서는 추가적인 형태의 활동성 장애가 생길 수 있다.
단일 스레드 프로그램에서의 활동성 장애는 무한루프 등이 있고,
다중 스레드 프로그램에서는 데드락, 교착상태, 라이브락 등이 있다. ( 후에 포스팅 예정 )
'JAVA > JAVA 병렬프로그래밍' 카테고리의 다른 글
JAVA 병렬 프로그래밍 [5] - 화장실 스케쥴링 (1) | 2022.06.06 |
---|---|
JAVA 병렬 프로그래밍 [4] - Sharing Objects ( 객체 공유 ) (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [3] - 멀티 스레드로 1-100까지의 합 구하기 (0) | 2022.06.05 |
JAVA 병렬 프로그래밍 [2] - Thread Safety (0) | 2022.06.05 |