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

concurrent programming 사전 학습

금번 스터디 주제는 concurrent programming 입니다.

멀티코어 환경에서 코어 갯수가 2배로 늘어나면 성능도 2배로 늘어나기를 기대하지만, 실제로 thread가 어떤 구조로 되어있고, 어떤 자원들이 공유되며, 해당 공유 자원을 코어가 어떻게 처리하는지에 대한 정확한 이해가 없으면 이러한 성능향상은 있을 수 없습니다. 때문에 멀티코어 환경에서 프로그램의 성능을 높이려면 concurrent 한 데이터 처리 기법에 대한 이해가 반드시 필요합니다.

스터디 시작 전 미리 알아두어야 할 사전 지식에 대하여

  • 자료 링크
  • (설명이 필요한 내용은)간단한 설명

를 이 페이지에 정리해 두었으니 스터디 시작 전에 미리 공부를 해두었으면 합니다.

사전 학습 내용은 모두 java 및 jvm 내용이지만, 다른 언어/os 도 비슷비슷합니다. 하나만 제대로 알고 있으면 나머지는 쉽게 이해가능합니다.

jvm memory 구조

jvm run-time data area

dependency hell과 빌드지옥 탈출

jvm의 메모리 구조를 잘 이해하려면 java process가 어떻게 동작하는지 알아야 합니다. 근데 아이러니하게도 java process가 어떻게 동작하는지 알려면 jvm의 메모리 구조를 이해하고 있어야 합니다. 이러한 모순적 상황에서 이해의 수준을 높이려면 메모리 구조와 process 의 동작 원리를 반복적으로 여러번 공부하는 방법 밖에는 없습니다.

runtimeArea.PNG

jvm 내에서 프로세스의 흐름을 기술하기 위한 저장 영역을 run-time data area 라고 합니다. 어떤 process가 실행되어 종료될 때 까지 사용되는 모든 데이터는 이 영역에 기록됩니다.

  • program counter register : 프로그램 카운터(pc)는 현재 동작중인 프로세스/또는 쓰레드가 sleep 상태에 진입하여 CPU 점유권을 잃어버렸다가 다시 되찾을 때 현재 이 쓰레드가 어디까지 진행되었는지에 대한 기록을 남겨놓기 위한 저장공간입니다. pc register에는 현재 진행중인 instruction의 주소값이 저장됩니다.
  • jvm stack : 프로그램 내에서 method가 호출되면 그 method 를 수행하기 위해 필요한 저장공간을 제공합니다. 또한 method의 호출은 계층적으로 호출순서가 발생하므로 이 순서를 보장하여 처리하기 위해 stack 을 사용합니다. stack에 저장되는 element의 단위는 frame 이라 합니다.
  • native method stack : native method stack은 조금 특별한 영역으로, jvm의 깍두기와도 같은 예외 케이스입니다. 외부에서 실행되는 native process와의 인터페이스(jni) 를 호출하기 위한 공간입니다.
  • method area : method area는 프로그램을 실행하기 위한 메타정보(class구조, method, constructor 등등)을 저장하는 영역입니다. 특히 static 으로 선언된 변수가 이 영역에 저장된다는 사실을 기억해 두기 바랍니다.
  • run time constant pool : run time constant pool 은 method area 내부에 존재하는 영역으로, constant,  class 및 interface의 symbolic reference table 정보 등을 저장하고 있습니다.
  • heap : run time 에 동적으로 할당되는 데이터가 저장되는 영역입니다. heap에 할당된 데이터는 gc의 대상입니다.

지금부터 설명하는 내용이 중요합니다. run-time data area 내에서 어떤 영역은 프로세스 내의 모든 thread 들이 공유하는 반면, 어떤 영역은 각 thread 마다 독립적으로 생성됩니다. 여기서 thread 간에 공유되는 리소스 영역이 중요합니다. 극단적으로 말하면 모든 쓰레드 문제는 동기화 문제이며, 모든 동기화 문제는 공유 리소스로부터 발생합니다.

run-time data area 에서 다음 영역은 process 내 모든 thread가 공유합니다.

  • method area
  • run-time constant pool
  • heap

다음 영역은 thread 1개 마다 독립적으로 1개씩 생성되며, 데이터는 공유되지 않습니다.

  • program counter register
  • stack
  • native method stack

runtimeArea2.PNG

java process

process 를 간결하고 정확하게 정의하자면, “program in execution”(=실행중인 프로그램)입니다.

process 가 실행되는 순서는 다음과 같습니다.

  1. jvm startup
  2. load class
  3. link class
  4. init class
  5. create class

java process의 동작 원리를 자세하게 설명하는 것은 본 문서의 목적을 넘어서므로 무얼 보고 공부해야 하는지 링크만 제공합니다.

java program execution

 

jira-stash 연결 가이드

개요

JIRA 이슈 – stash 리파지토리 revision 간 연결 방법을 가이드한다.

 

원칙 : 1기능 = 1이슈 = 1브랜치

JIRA 이슈 – stash 리비전 연결 시 사내 방침은

1개의 (기능 or 버그) = 1개의 이슈 = 1개의 (임시)브랜치 입니다.

 

JIRA – stash 업무 플로우

업무 플로우는 다음과 같습니다.

처음에 연결만 시켜주면 그 뒤로 변경 사항 추적 연결은 JIRA 와 stash 가 알아서 합니다.

1) 이슈 생성

2) 이슈에 해당하는 브랜치 생성

3) 생성한 브랜치에서 해당 기능 개발

4) 생성한 브랜치를 develop 브랜치에 merge

5) develop 브랜치 -> master 브랜치로 merge

 

이슈 생성

다들 잘 아실테니 패스…

 

이슈에 해당하는 브랜치 생성

Create branch 를 클릭하면 아래와 같은 화면이 뜹니다.

Repository : 현재 개발중인 repository 선택

Branch type : 이슈 타입에 맞게 bugfix / feature 중 한 가지를 선택

Branch from : master 브랜치가 원칙이나 상황에 따라 develop 브랜치 선택하여도 무방

 

develop 브랜치를 선택하는 것이 유리한 경우

develop 브랜치가 너무 많이 진행되어 master / develop 간 차이가 너무 벌어진 경우

현재까지 develop 브랜치에서 진행된 내용을 반영하여야 하는 경우

Branch name : 이슈 이름이 영어인 경우 알아서 자동완성되나, 한글인 경우 이슈 키와 동일하게 기재됨. 상황에 맞게 적절히 기재할 것

 

생성한 브랜치에서 해당기능 개발

아래와 같이 작업하시면 됩니다.

git clone -> 생성한 브랜치로 check out -> 해당 기능 개발 후 commit -> push

 

JIRA 이슈와 stash 리비전 간 자동 연결이 되는 원리는 다음과 같습니다.

  • stash 서버가 커밋 로그에 기재된 이슈 번호를 JIRA 서버에서 찾아냄
  • 해당 JIRA 이슈와 커밋 리비전을 연결시킴

 

헌데, 최초에 Create branch 시 브랜치 이름을 JIRA 이슈 이름으로 자동생성하였기 때문에

pull request -> merge 시 자동으로 생성되는 commit log 로 인하여 JIRA 이슈 – stash 간 연결은

머지 시점에 자동으로 알아서 됩니다.

 

요약하면, 최초에 연결만 잘 해놓으면 머지 시점에 알아서 JIRA 이슈 – stash 리비전 간 연결이 됩니다.

 

생성한 브랜치를 develop 브랜치에 merge

 

Create pull request 클릭

 

본인이 개발한 브랜치 -> develop 브랜치 로 pull request 생성되는지 확인

 

pull request 부가정보 기입

Description : 그동안 달아뒀던 commit log 내용으로 알아서 자동생성되나, 맘에 안들면 수정해도 무방함

Reviewers : 생성된 pull request 의 코드 리뷰어를 기입. 리뷰어가 해당 pull request 를 보면서 온라인 코드리뷰를 진행할 수 있습니다.

 

코드리뷰

생성된 pull request 에서 Diff 탭 선택 -> 코드 변경 라인으로 커서 이동 -> 말풍선 클릭 -> 리뷰 내용으로 댓글을 달 수 있습니다.

 

 

pull request 한 코드에 문제가 없으면 merge / 마음에 들지 않으면 decline 을 선택할 수 있습니다.

 

merge 후 임시 브랜치 삭제

feature / bugfix 등의 브랜치는 해당 기능을 구현하기 위한 임시 브랜치입니다.

개발이 완료되어 머지하는 시점에는 삭제하는 것이 원칙입니다.

아래와 같이 체크박스를 선택하면 stash 가 알아서 삭제해 줍니다.

 

develop 브랜치를 master 브랜치에 merge

develop 브랜치는 delete 하지 않습니다. 이 외에는 임시브랜치 머지하는 것과 절차가 동일합니다.

node.js 로 front 개발하기

전에 어느 신입 사원의 “node.js 로 front 개발하겠습니다.” 라는 발언이 인구에 회자되었던 적이 있다.

나또한 javascript 알못인지라 node.js 는 서버사이드 플랫폼이기에 신입 사원이 뭔가 잘못 알고 한 말이리라 생각했었다.

 

헌데 요즘 javascript 클라이언트 개발 환경을 좀 디벼보니 정말 node.js 로 프론트를 개발한다.

정확히 말하자면, node 로 개발된 library를 배포 시 재패키징하는 방식으로 프론트 개발 환경에서 node.js 인프라를 그대로 사용하도록 돕는다.

module bundler(e.g. webpack, browserify …) 가 이런 일이 가능하도록 해주는 녀석들인데,

webpack 의 경우 부가적으로 js 외에도 css / png / coffee script 등등등 다양한 웹 리소스를 한번에 패키징 할 수 있는 기능을 제공하기도 한다.

 

암튼, 신입사원의 “node.js 로 front 개발하겠습니다.” 발언은 전혀 틀린 말이 아니며, 심지어 최신의 js 개발 트렌드를 반영한 것으로 판명 되었다.

앞으로 front 개발은 node.js 로 해라.

 

webpack

test pyramid

출처 : http://martinfowler.com/bliki/TestPyramid.html

 

이 한장의 그림이 모든걸 설명한다. unit test 를 하는 이유는 비용과 효율성 이라는 현실적인 이유 때문이다.

 

unit test 를 생략하고 서비스 또는 UI 에 대한 테스트를 진행하는 이유가 “시간이 없어서” 라면

진지하게, 그리고 솔직하게 가슴에 손을 얹고 양심에 물어봐야 한다.

“사실 나는 테스트를 하고 싶지 않은게 아닌가?”