concurrent programming #1

왜 concurrent programming 인가?

clockspeed

2000년대 초반까지 cpu의 처리속도는 2년 마다 2배씩 증가해 왔습니다. 복잡한 연산을 하고 싶으면 돈을 더 내고 비싼 cpu를 사면 해결되는 시대였죠. 그런데 어느 순간부터 cpu의 처리속도는 더 이상 빨라지지 않고 있습니다. 회로의 집적도와 전자의 이동속도에 한계가 있기 때문에 양자컴퓨터라도 나오지 않는 이상 현재로선 cpu의 수직적인 처리속도를 높일 수 있는 마땅한 방법은 없는 듯 합니다.

Cores.png

2004년 부터는 cpu의 처리속도를 높이는 대신 병렬적으로 코어의 갯수를 늘리는 시도를 하였습니다. 그리고 10여 년이 지난 현재, 손바닥만한 핸드폰에도 예외없이 cpu는 2개 이상 들어가는 것이 당연한 시대가 되었습니다. 멀티 코어 환경이 당연한 시대에 과연 그 안에서 동작하는 프로그램은 멀티 코어 환경에 적합하도록 만들어 졌을까요?

 

thread vs process

잘 아시겠지만 다수의 컴퓨팅 유닛을 사용하는 방법은

  1. multi processing
  2. multi threading

이 두 가지 방법 뿐입니다.

그렇다면 쓰레드랑 프로세스의 차이점은 무엇일까요? 찾아보면 많이들 나오니 자세한 설명은 패스하고 주요 차이점만 서술하면

쓰레드는 프로세스에 비해

  1. 생성 비용이 낮고
  2. 리소스 공유가 쉽습니다.

runtimearea2

위 그림은 jvm의 run-time data area 인데요. java 프로세스를 하나 생성하기 위해선 위 6가지 영역(정확히는 5가지, run-time constant pool은 method area에 포함) 이 모두 새로 생성되어야 합니다. 허나 쓰레드는 초록색 박스 영역만 새로 생성하고, 빨간색 박스 영역은 부모 프로세스와 공유합니다. java 를 예로 들었지만 다른 언어나 os도 상황은 대동소이합니다.

프로세스가 서로 리소스를 공유하는 수단은 1) named pipe 2) socket 3) shared memory 4) message 등이 있습니다. 이러한 프로세스 간 통신 수단은 어플리케이션 외부 커널에서 관리되기 때문에 비용이 높고 규약 외 처리(=exception)를 잘 정의하여야 합니다.

반면 쓰레드는 프로세스에 비해 리소스를 공유하는 방법이 쉽습니다.

  1. heap 에 리소스를 할당하거나
  2. 전역 변수를 설정하거나
  3. static 변수를 설정하면

해당 리소스는 쓰레드 간 공유가 가능합니다.

java의 경우, 전역변수가 없고 static keyword 는 멤버 변수에만 할당이 가능하므로 리소스가 공유되는 경우는 다음 두 가지 뿐입니다.

  • heap에 할당된 리소스 및 레퍼런스
  • static 멤버 변수 및 레퍼런스

 

thread 문제들

멀티쓰레드 프로그램은 분명 cpu 처리속도의 한계를 극복하기 위한 좋은 대안입니다만, 개발자에게 다양한 문제를 안겨주기도 합니다. 아마도 개발 초년생에게 가장 어려운 문제가 thread 문제일겁니다. thread 문제가 어려운 이유는 무엇일까요?

  1. 재현이 어렵고
  2. 현상이 그때그때 다르고
  3. 인간의 뇌가 동시에 두 가지 사고를 할 수 없기 때문

이 아닐까 생각합니다. 허나 얼핏 복잡다단해 보이는 thread 문제에도 유형이 있으니 문제의 유형들을 분류해 보도록 합시다.

thread 문제의 양상은 크게 두 가지로 구분합니다.

  1. thread safety 문제
  2. 기대하지 않는 동작

thread safety 문제는 프로그래머가 리소스 공유에 대한 이해가 부족하기 때문에 발생합니다. 공유되는 리소스는 반드시라고 해도 좋을 정도로 대부분 동기화 문제가 발생합니다. 리소스가 공유되더라도 동기화 문제가 발생하지 않는 경우는 다음 세 가지 뿐입니다. 이 세 가지는 반드시 기억해 두길 바랍니다. 여러 가지 다른 thread 문제들은 쉽게 예측이 어렵고 해결방안이 뚜렷하지 않을 수도 있으나 thread safety 문제는 명약관화하고 해결방안이 분명합니다.

  1. 공유 리소스가 immutable 하거나
  2. critical section에 대한 동기화 보장을 하였거나
  3. instruction 이 atomic 하거나

thread safety를 보장하였는데도 어플리케이션이 기대하지 않는 엉뚱한 동작을 한다면 이는 어플리케이션 동작 환경을 고려하지 않은 설계/정책의 문제입니다. 쓰레드는 태생적으로 제어가 어렵습니다. 실행중인 쓰레드가 언제 종료될지, sleep 상태의 쓰레드가 언제 다시 작업을 재개할 지 실행중에는 알 수가 없기 때문입니다. 또한 모든 thread 는 태생적으로 다른 thread들과 리소스를 점유하기 위한 경쟁관계에 놓여있기 때문에 경쟁의 과열로 인한 문제가 발생할 가능성이 항상 존재합니다.

  1. starvation
  2. dead lock / live lock 문제
  3. aba 문제

이러한 문제들은 설계와 정책으로 해결하여야 합니다. 어플리케이션의 동작환경에 따라, 사용자의 사용 패턴에 따라 해당 문제는 발생할 수도 있고 안할 수도 있습니다. 또한 어떤 문제들은 소프트웨어 설계 시점에 원천적으로 문제의 원인을 봉쇄할 수도 있습니다. 모든 분야가 마찬가지겠지만 소프트웨어 설계와 정책은 외부 환경에 대한 고려가 필수적입니다.

 

동기화 방식과 성능 병목

monitor

critical section

mutex

semaphore

spinlock

 

reentrantlock

synchronized

 

volatile

memory transaction (hardware / software)

lock-free / wait-free

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중