2008-09-08

멀티쓰레딩 어플리케이션에 관해 모든 개발자가 알아야만 하는 것들 - (1)

[주의]
    초벌 번역한 것으로 세세하게 단어와 문맥을 다듬은 글이 아닙니다.;;;;
    대략적인 내용 파악을 위한 자료로만 활용해 주세요.^^;;

[원문]
    What Every Dev Must Know About Multithreaded Apps

이 글에서 다룰 주제들
  • 멀티쓰레딩과 공유 메모리 쓰레딩 모델
  • 경함 그리고 동시 접근이 불변식을 어떻게 깨뜨리는지
  • 경함에 대한 표준적인 해결법인 잠금
  • 잠금이 필요할 때
  • 잠금의 사용법과 이에 대한 비용을 이해하기
목차
  • 쓰레드와 메모리
  • 경합(Races)
  • 락(Lock)
  • 락 프로퍼티 사용하기
  • 락은 몇 개나?
  • 읽기에서 락 사용하기
  • 락을 사용한 보호를 필요로하는 메모리
  • methodical lock
  • 데드락
  • 락의 비용
  • 동기화에 관해 훑어보기
  • 결론
10년 전에는 하드 코어 시스템 프로그래머들 만이 하나 이상의 쓰레드를 실행하는 상황에서 올바르게 코드를 작성하는 복잡한 방법을 고민했었다.  대부분의 프로그래머들은 이런 문제를 피하기 위해서 순차적인 프로그램을 작성한다. 그러나 이제는 멀티코어 프로세서 머신이 보편화 되었다. 조만간 멀티쓰레드로 구현되지 않은 프로그램은 가능한 컴퓨팅 파워의 상당 부분을 사용하지 않게 될 것이라 불리한 입장에 처할 것이다. 불행히도 멀티쓰레드 프로그램을 제대로 작성하는 것은 쉽지 않은 일이다. 프로그래머는 다른 쓰레드들이 해당 쓰레드하의 메모리를 변경할 수 있다는 개념에 익숙하지 않다. 더우기 실수라도 하게 되면 프로그램을 제대로 작동하게 하는데 대부분의 시간을 보내야 한다. 특정 조건 하에서만 겨우 버그가 명백해 질것이고, 극히 드물게 오류시점에서의 충분한 정보를 디버그용 어플리케이션을 통해 효과적으로 얻을 수 있다. 그림 1은 순차적인 방식과 멀티쓰레드 방식의 프로그램의 차이점을 요약해서 보여주고 있다. 보는바와 같이, 멀티쓰레드 프로그램을 처음에 제대로 작성하기 위해서는 정말 지불해야할 비용이 많다. 그림 1 순차적인 프로그램과 멀티쓰레드 프로그램의 특징들
사용자 삽입 이미지
이번 글은 세 가지를 보여주려고 한다. 첫 번째로 멀티쓰레드 프로그램은 그렇게 난해한 것은 아니라는 것이다. 올바른 프로그램을 작성하기 위해 기본적으로 필요한 것은 순차적 방식이나 멀티쓰레드 방식이나 동일하다: 프로그램 내의 모든 코드는 프로그램내의 다른 부분에도 필요로하는 불변식은 무엇이든 보호해야 한다. 두 번째는 이런 지침이 단순하기는 하지만, 멀티쓰레드 환경에서 지키기에는 훨씬 더 어렵다는 점이다. 순차적 환경에서는 명백한 방식이 멀티쓰레드 환경에서는 놀라울 정도로 난해하다. 마지막으로, 이런 난해한 문제를 다루는 방법을 보여주고자 한다. 이번 가이드는 프로그램 불변식을 보호하기 위한 체계적인 전략을 정리하는 것으로, 그림 2에서 볼수 있듯이 멀티쓰레드의 경우에는 좀 더 복잡하다. 멀티쓰레드를 사용하면 복잡해지는 여러가지 이유가 있는데, 이 후의 섹션에서 설명할 것이다. 그림 2 순차적인 프로그램과 멀티쓰레드 프로그램에서의 프로그래밍
사용자 삽입 이미지
쓰레드와 메모리 사실 멀티쓰레드 프로그래밍도 꽤 단순해 보인다. 순서대로 하나의 프로세싱 유닛을 실행하는 대신에 두 개 이상을 동시에 실행하면 된다. 프로세서는 실제 멀티프로세서 하드웨어이거나 타임-멀티플렉싱 싱글 프로세서이기 때문에 쓰레드라는 용어는 프로세서 대용으로 사용된다. 멀티쓰레드 프로그래밍에서의 은근 골치아픈 부분은 쓰레드간에 통신을 하는 방법 부분이다. 그림 3 공유 메모리 쓰레딩 모델 대부분에서 보여지는 멀티쓰레드 통신 모델은 '공유 메모리 모델(shared memory model)'이라고 부른다. 이 모델에서의 모든 쓰레드는 그림 3에서 볼수 있듯이 같은 공유 메모리 풀에 접근한다.이 모델은 멀티쓰레드 프로그래밍을 순차적인 프로그래밍 방식과 같은 방법으로 할 수 있다는 이점이 있다. 그러나 이 이점이 또한 가장 큰 문제이기도 하다. 이 메모리는 쓰레드안에서 사용하는 로컬 메모리(로컬로 선언된 변수 같은 것들)와 다른 쓰레드와 통신하기 위해 사용하는 메모리(전역 변수나 힙 메모리 같은것)을 구별하지 않는다. 잠재적으로 공유된 메모리는 쓰레드 로컬 메모리보다 훨씬 더 신중하게 다뤄야 하기 때문에 실수가 발생하기 쉽상이다. 경합(races) 프로세스들이 요청을 하고, 이 요청이 완료될 때마다 값이 증가하는 전역 카운터 totalRequests를 가지고 있는 프로그램이 있다고 치자. 보다시피, 이를 수행하는 코드는 순차적인 프로그램에서는 명백하다.
totalRequests = totalRequests + 1
하지만, 요청과 카운터의 갱신을 처리하는 여러 개의 쓰레드를 가진 프로그램에서는 문제가 생긴다. 컴파일러는 다음과 같은 기계어 코드로 (카운터의)증가 코드를 생성할 것이다.
MOV EAX, [totalRequests]  // load memory for totalRequests into register INC EAX                            // update register MOV [totalRequests], EAX  // store updated value back to memory
두 개의 쓰레드가 이 코드를 동시에 실행하는 상황을 가정해 보자. 그림 4에서 볼 수 있듯이, totalRequests의 같은 값을 로딩하여, 증가시킨 후, 다시 저장시킬 것이다. 이 일련의 동작이 끝나는 시점에서 두 쓰레드는 요청을 처리했지만, totalRequests의 값은 이 전과 비교했을 때 1만 증가했을 뿐이다(역주: 두 개 처리 했으니가 2가 증가하는 것이 맞다). 확실히 이것은 우리가 원한 결과가 아니다. 이런 버그를 경합이라고 부르는데, 두 쓰레드 사이의 나쁜 타이밍 문제로 발생하는 것이다. 그림 4 경합의 경우 들여다보기
이 예는 자명해 보이지만, 일반적인 문제는 실생활에서 벌어지는 경합만큼이나 복잡한 것이다. 여기에 경함이 발생할 수 있는 네 가지 조건을 나열해 보겠다.

첫 번째 조건은 하나 이상의 쓰레드가 접근할 수 있는 메모리가 존재할 때 이다. 보통 이런 메모리는 전역/스태틱 변수이거나 이런 변수로부터 접근할 수 있는 힙 메모리이다.

두 번째 조건은 이런 공유 메모리가 프로그램이 올바르게 작동하기 위해 사용되는 속성일 경우이다. totalRequests가 이런 속성으로 어떤 쓰레드가 실행되고, 값이 증가하는 어떤 경우이던 전체 횟수를 정확히 나타내야 한다. 정확한 갱신을 위해서는 갱신전에 유효성을 확인(totalRequests가 정확한 값을 가져야 하기 때문에)할 칠요가 있다.

세 번째 조건은 그 속성이 실제 갱신되는 중에 점유되지 않는 경우이다. 이 경우에는 totalRequests를 불러와서 저장하는 동안에 불변성을 만족하지 않는다.

네 번째이자 마지막인 경합의 조건은 다른 쓰레드가 불변성이 깨졌을 때 접근한 후, 이로 인해 오동작이 발생하는 경우이다.

-- 다음에 계속 --

2008-09-07

멀티쓰레딩 어플리케이션에 관해 모든 개발자가 알아야만 하는 것들 - (2)

[주의]
    초벌 번역한 것으로 세세하게 단어와 문맥을 다듬은 글이 아닙니다.;;;;
    대략적인 내용 파악을 위한 자료로만 활용해 주세요. ^^;;;

[원문]
    What Every Dev Must Know About Multithreaded Apps

지난 글...
    멀티쓰레딩 어플리케이션에 관해 모든 개발자가 알아야만 하는 것들 - (1)

----------------------------------------------------------------------------

락(Lock)

경합을 없애는 가장 일반적인 방법은 락을 사용하여 다른 쓰레드가 메모리에 접근하여 불변성을 깨는 행동을 못하게 하는 것이다. 이렇게 하면 위에 언급했던 네 가지 조건을 없애고 경합이 발생하지 않게 된다.
가장 많이 사용하는 락은 여러가지 이름으로 불린다. 모니터, 크리티컬 섹션(역주: 임계 영역이라고도 함), 뮤텍스 또는 이진 세마포어 등으로 불리지만, 이름을 불문하고 기본 기능은 동일하다. 락은 Enter와 Exit 메소드를 제공하며, 어떤 쓰레드가 Enter를 호출하게 되면, 다른 쓰레드들의 Enter 호출 시도는 Exit가 호출되기전까지 블럭(대기)이 된다. Enter를 호출했던 쓰레드가 락의 소유자이며, 소유자가 아닌 쓰레드에 의해 Exit가 호출되면 프로그래밍 에러로 간주된다. 락은 오직 하나의 쓰레드만이 주어진 시간에 코드의 특정 영역을 실행할 수 있음을 보증하는 메카니즘을 제공한다.

Microsoft® .NET Framework에서의 락은 System.Threading.Monitor 클래스에 의해 구현된다. Monitor 클래스는 인스턴스를 정의하지 않기 때문에 다소 특이하다. 이것은 락의 기능이 System.Object에 의해 효과적으로 구현되어 있기 때문인데, 그렇기 때문에 어떤 object이건 락을 사용할 수 있다. 여기에 totalRequests에 관련된 경합을 막기위해 락을 사용하는 방법을 나타내었다.

static object totalRequestsLock = new Object();  // executed at program
                                                                    // init
...
System.Threading.Monitor.Enter(totalRequestsLock);
totalRequests = totalRequests + 1;
System.Threading.Monitor.Exit(totalRequestsLock);

이 코드로 경합 문제는 수정했지만, 또 다른 문제가 발생될 수 있다. 락이 잡혀 있는 상황에서 예외(exception)이 발생하게 되면, Exit가 호출되지 않는다. 이렇게 되면 이 코드를 실행하려는 다른 쓰레드들은 영원히 블럭 상태가 된다. 많은 프로그램에서 예외는 프로그램에 치명적인 것으로 간주되기 때문에, 이런 경우가 발생하는 것은 재미없는 일이다. 그러나, 예외로부터 복구가능 하도록 하기 위해 finally 절에 Exit를 두면, 좀 더 견고해진다.

System.Threading.Monitor.Enter(totalRequestsLock);
try{
    totalRequests = totalRequests + 1;
} finally {
    System.Threading.Monitor.Exit(totalRequestsLock);
}


이 패턴은 C#이나 Visual Basic®.NET에서는 일반적인 것이다. 다음의 C#코드는 앞서 보인 try/finally와 동일한 표현이다.

lock(totalRequestsLock){
    totalRequests = totalRequests + 1;
}


개인적으로는 lock 구문을 사용하는 것을 반대한다. 어떤 면에서 이런 방식은 편리하고 간결하다. 그러나, 이는 프로그래머들이 견고한 코드를 작성하고 있다는 잘못된 편안함을 줄 수 있다. 락이 사용되는 영역은 프로그램상의 중요한 불변량이 지켜지지 않기 때문에 사용된다는 점을 기억하자. 만일 그 영역에서 예외가 발생한다면, 그 시점에 불변량도 깨지게 될 확률이 크다. 이 깨진 부분을 고치지 않고 프로그램이 계속 진행되도록 하는 것은 좋지 않은 생각이다.

totalRequests 예에서는 딱히 할만한 정리작업이 없기 때문에 사용된 lock 구문은 적당하다. 또한 lock 구문은 모든 데이터가 읽기 전용일 때에도 쓸만하다. 그러나 보통 예외가 발생하게되면 추가적인 정리작업이 수행될 필요가 있다. 이런 경우에는 어차피 try/finally 구문이 필요하기 때문에 굳이 lock 구문을 추가할 필요는 없는 것이다.

락 속성의 사용

대부분의 프로그래머는 경합과 씨름한적이 있고, 이를 방지하기 위해 락을 사용하는 방법의 간단한 예들을 보아 왔다. 그러나 추가적인 설명이 없이 이런 예제만으로는 실제의 프로그램에서 락을 효과적으로 사용하는데 필요한 중요한 이론까지 아우를수는 없다.

첫 번째 중요한 통찰은 락은 코드 영역에 대한 상호 배제(mutual exclusion: 역주-mutex도 이말을 줄인 것임)를 제공하지만, 보통 프로그래머들이 원하는 것은 메모리 영역에 대한 보호를 원한다는 점이다. totalRequests 예제에서, 원래 목적은 totalRequests(메모리 위치)를 올바른 값으로 유지하는 것이다. 그러나, 위와 같이 하게 되면 실제로는 코드의 영역(totalRequests를 증가시키는 코드 영역)에 락을 걸게된다. totalRequests를 참조하는 코드가 이 곳 뿐이기 때문에 totalRequests에 대한 상호 배제를 제공하는 셈이긴하다. 그러나 만일 다른 코드 영역에서 락을 걸지 않고 totalRequests를 갱신하게 된다면, 메모리에 대한 상호 배제를 가지지 않게 되는 셈이고, 결과적으로 코드는 경합 조건을 일으키게 된다.

이는 다음의 이론으로 연결된다. 메모리 영역에 대해 상호 배제를 제공하는 락을 위해 락을 걸지 않고서는 메모리에 기록을 할 수 없도록 해야 한다. 적절히 디자인된 프로그램이라면 상호 배제를 제공하기 위한 메모리 영역은 모두 락과 연결이 되어야 한다. 불행히도 이런 연결을 깔끔하게 만들어주는 코드상의 명확한 해법은 없으며, 이런 정보는  프로그램내의 멀티쓰레드의 동작에 관해 고민하는 모든 이에게 절대적으로 중요한 것이다.

결론적으로 모든 락은 상호 배제를 제공해야 하는 메모리의 특수 영역(예를 들어 데이터 구조체의 집합)을 기술한 문서와 연결해서 명확히 정리해 두어야만 한다. totalRequests의 예제에서 totalRequestsLock은 totalRequests 변수 하나만을 보호하는 역할을 한다. 실제의 프로그램에서 락은, 데이터 구조체나 이와 관련된 다른 구조체 또는 아예 연결될수 있는 모든 메모리와 같이 더 큰 영역을 보호할 수도 있다. 때때로 데이터 구조체의 특정 부분(해시 테이블의 버켓 체인과 같은)만을 보호할 수도 있지만, 그 영역이 뭐가 되었던 간에 프로그래머가 명시적으로 기술해야 함은 여전히 중요하다. 이런 명세를 가지고 있으면, 관련된 메모리가 갱신되기 전 락의 사용이 체계적으로 진행될 수 있다. 대부분의 경합은 관련 메모리에 접근하기전에 올바로 락을 일관적으로 걸어주지 않아 발생하기 때문에, 이 정도의 검수를 위해 시간을 들일만 하다.

각 락이 보호해야할 메모리에 대한 정확한 명세를 가지고 있다면, 어떤 영역이 서로 다른 락들로 인해 중첩으로 보호되지는 않는지 확인해야 한다. 중첩 자체가 잘못된 것은 아니지만, 이런식으로 연결된 메모리가 유용하지는 않기 때문에 피해야 한다. 두 락에서 공통으로 보호하는 메모리가 갱신되는 경우에는 어떤 일이 발생할지 생각해 보자. 어떤 락이 사용되어야 할까? 다음과 같은 가능성을 생각해 볼 수 있겠다:

아무 락이나 임의로 건다 이 방식은 더이상 상호 배제를 제공할 수 없기 때문에 안된다. 각기 다른 락을 사용하는 두 개의 쓰레드가 갱신을 하는 것이 가능해지고, 결국 동시에 같은 메모리를 갱신하게 된다.

항상 두 개의 락을 건다 이렇게 하면 상호 배제는 지원하지만, 비용이 두배나 들고 하나의 락을 사용하는것에 비해 이점이 없다.

항상 특정 하나만 골라서 락을 건다 이것은 특정 영역을 보호하는 하나의 락을 사용한다는 말과 다를게 없다.




2008-09-05

ego test

여기서 보고 나도...
음...결과가 썩 마음에 들진 않지만, 어느정도 반영하고 있는 듯..
이성과 지성의 에너지를 높이라니...충동적인 사람이라는 건가? OTL

AABAA
마음이 너무나 분주하여 
이성이 방황하는 타입
▷ 성격
강한 목적지향, 가까운 사람들에 대한 깊은 애정, 자유분방한 감정, 가만히 있으면서 이득을 보고자하는 의존성이 모두 높은 에너지를 발산하고 있기 때문에 매우 산만한 사람입니다. 그렇기 때문에 지극히 평범한 사려분별로는 그 조정이 불가능하다는 것이 결정적인 특징이자 문제점입니다. 이런 성격으로는 일년 내내 마음 편할 날이 없을 것입니다. 슈퍼맨이 아닌 이상 그렇게 산더미 같은 일들을 완벽하게 해낼 수 없으니 결국에는 모두 엉성하게 되겠죠. 차라리 안 하느니만 못한 상황이 되어버립니다. 이런 단점을 해결할 길은 상대적으로 이성과 지성의 에너지를 높이는 방법밖에 없습니다.


▷ 대인관계 (상대방이 이 타입일 경우 어떻게 하연 좋을까?)

연인, 배우자-결혼상대로서는 더할 나위 없는 타입이라 할 수 있죠. 좀 더 속을 들여다보면 대단한 거물이 될 가능성도 잠재되어 있는 사람입니다. 이성과 지성이 현저하게 증가한다면 그 잠재능력도 빛을 발하게 될 것입니다.

거러처(고객)-비즈니스 상대로서는 흠잡을 데가 없습니다.

상사 - 이런 사람은 안테나를 세우고 있는 방향이 복잡하여 그 마음을 이해하기 어렵습니다. 그러니 쓸데없는 상상이나 망상은 하지 마십시오. 성의를 가지고 상식적으로 일하면 그걸로 충분합니다.

동료, 부하직원-어떻게 될지 전혀 예측할 수 없는 상대입니다. 유심히 지켜보고 파악하십시오.
 


테스트 바로갈려면 이쪽

2008-09-04

XSS

엘레베이터 안에서 팀의 개발자들이 수군거리는 이야기를 듣고 한번 정리해야 겠다 싶어서...

* XSS(Cross Site Scripting)

1. 정의

2. 수법

3. 피해

4. 해결법




* 참고용 링크
http://blog.naver.com/jakehong/120002923127 --> 기본 개념 잘 설명
http://en.wikipedia.org/wiki/Cross-site_scripting -->

2008-08-12

갈등

잘 하고 싶은 것.
잘 할수 있는 것.

두 개가 일치하지 않을 때 피곤해진다.

아주 좋거나, 아주 나쁠때는 차라리 판단이 명쾌해진다.
괴로울 때는 바로 좋은점과 나쁜점이 비등비등해서 결과적으로 그저그런 때.

사실 살면서 겪는 대부분의 일들은 후자의 상황인 듯 하다.
그래서 선택의 기로에서서 항상 갈등하고, 고민하게 되는 듯하다.


2008-08-10

프로그램 선택권을 보장하라

박태환의 금메달 획득 소식을 몇번이고 보여주던 베이징 올림픽 프로그램이 마침내 끝났다.
다음 프로그램은 2008 베이징 올림픽이란다.




...OTL..


2008-08-05

명대사 - 01

도망쳐서 도착한 곳에 낙원이란 없는거야

                                       - 가츠-


@ 한동안 잊고 있었던...