'허니몬의 IT 이야기'에 해당되는 글 397건

허니몬의 IT 이야기

쓰기에 따라서는 굉장히 편한 프로그램 일수도 있고....

최악의 프로그램일 수도 있습니다. ^^; 양날의 검이란 거죠.

요즘 삭제하기를 해도 삭제가 되지 않는 악성프로그램들이

많이 늘어났습니다. 이를 해결하기 위해서는 악성프로그램을

배포한 사이트에 가서 수동삭제프로그램을 일일이 찾아야 하죠.

그래서 찾아보다가... 예전에 사용해본적이 있던 녀석을 찾았습니다. ^^

Regseeker라는 녀석인데.... 프로그램 설치/제거 부분에서 삭제가 되지

않는 프로그램들을 삭제하거나 레지스트리에서 지울 수 있습니다.

레지스트리 청소 기능도 있습니다.

레지스트리는 컴퓨터를 오래 쓰면 오래 쓸수록 크기가 매우 커지면서 얽히죠.

그럴수록 속도가 떨어지는 것이 보입니다.

악성코드나 애드웨어에 의한 부분도 있지만,

레지스트리의 거대화(?)와 엉킴에 의한 문제가 있지요.

이 프로그램을 이용해서 한번 정리해보세요. ^^

실행하시면 C:regseeker 라는 폴더가 생성되고 자동으로 실행되도록

알집을 이용해서 Exe 실행파일로 만들었습니다.

^^;; 실행파일이라, 저를 못믿으시는 분은 쓰기 망설여지실건데...

ㅎㅎ 속는 셈 치고 한번 사용해보세요.

좋은 프로그램이니까요....

압축풀린 폴더에 프로그램 홈페이지에 대한 사항도 있으니까

한번 읽어보시고 직접 가보세요. 쓸만한 프로그램들 많아요. ^^

용량은 작지만... 강력합니다!!

+_+) 저도 이런 프로그램을 만들어보고 싶습니다.

허니몬의 IT 이야기

트랜잭션(Transaction) 지원 프로그래밍 언어

Erlang과 같은 함수형 언어가 병렬 수행을 다루는 한 가지 축이라면 또 다른 축은 트랜잭션 지원 프로그래밍

언어가 있다. 프로그래밍 언어의 트랜잭션 지원을 이해하기 위해서 우선 낙관적인 컨커런시 제어(Optimistic

concurrency control)라는 개념을 이해해야 한다. 자바의 java.util.Random 클래스를 살펴보자.


protected int next(int bits) {

long oldseed, nextseed;

AtomicLong seed = this.seed;

do {

oldseed = seed.get();

nextseed = (oldseed * multiplier + addend) & mask;

} while (!seed.compareAndSet(oldseed, nextseed));

return (int) (nextseed>>> (48-birs));

}


java.util.Random 클래스는 임의의 숫자를 돌려주는 유틸리티 클래스이다. 위 코드를 보면 알 수 있듯이 next()

메소드는 전혀 락을 쓰지 않고 있지만, 이 메소드는 스레드 세이프하다. 즉 여러 스레드가 동시에 next() 메소드를

부르더라도 각각 다른 값을 돌려주는 것이다. 이런 일이 어떻게 가능할까?

Random 클래스의 구현이 앞서 언급한 낙천적인 컨커런시 제어라는 개념을 적용한 예제이다. 이 말이 뜻하는

바는 다음과 같다.

(1) 스레드는 락을 잡지 않고 다른 스레드가 동시에 들어오지 않을 것이라는 낙천적인 가정하에 프로그램을 수행한다.

(2) 메소드 수행이 끝나고 나면 내가 그 동안 읽은 데이터가 다른 스레드에 변경되지 않았는지 확인한다.

(3) 변경되지 않았다면 내가 수정한 값을 모두 반영한다(commit). 이는 데이터베이스에서 말하는 트랜잭션과 같은 개념이다.


메모리 트랜잭션(Memory Transaction)

여기서 말하는 트랜잭션의 개념은 데이터베이스에서 말하는 트랜잭션과 동일하다.

(1) 트랜잭션은 일련의 메모리 오퍼레이션이 모두 수행되거나(Commit) 혹은 전혀

수행되지 않는(abort) 두가지 모드로만 동작함을 보장한다(All or nothing).


(2) 트랜잭션은 원자성(Atomicity)을 보장하는데, 쉽게 말해 일련의 메모리

오퍼레이션이 하나의 단위로 수행되는 것처럼 보인다. 즉, 외부에서 보기에는

한번에 모든 메모리 오퍼레이션이 일어나는 것처럼 보인다.


(3) 트랜잭션은 독립성(isolation)을 보장한다. 시스템은 다른 스레드는 모두 멈춰있고

오직 한 가지 스레드만 수행된다고 가정하고 코드를 수행한다. 바꿔 말해서 다른

스레드에 의해서 간섭 현상이 없음을 의미한다.


(4) 또한 트랙잭션은 프로그램이 순차적으로 실행된다는 환상을 준다. 다른 스레드가

없다는 가정 하에 프로그래밍을 하면 논리적으로 훨씬 간단한 코드를 작성할 수 있다.

물론 트랜잭션이 실제로 순차적으로 코드를 실행하지는 않는다. 순차적으로 코드를

실행하면 단일스레드 프로그램과 다를 바가 없고 멀티 코어 프로세서를 전혀 활용하지

못하게 되기 때문이다. 트랜잭션 시스템은 독립성(isolation)이나 원자성(atomicity)

등을 보장하는 범위 내에서 최대한 많은 스레드를 동시에 수행하는 방식으로 효율을 높인다.


여기서 내가 읽은 값이 다른 스레드에 의해서 변경되지 않았는지 확인하는 부분은 java.util.concurrent.atomic에

있는 클래스의 compareAndSwap() 메소드를 이용해 구현해야 한다. 메모리 값을 읽고 새로운 값을 메모리를 쓰는

동작을 하나의 인스트럭션으로 하기 위해 하드웨어의 도움을 받는 것이다. 실제로 Intel과 ARM 등 여러 CPU를

보면 atomicCompareAndSwap에 해당되는 인스트럭션이 있다. 이 인스트럭션을 이용하면 OS의 도움 없이

앞선 경우와 같이 스레드 세이프한 Random 클래스를 만들 수 있다.

자바의 Random 클래스는 이 방식으로 수정한 후에 벤치마크 결과를 보며 상당한 성능 향상이 있었음을 알 수

있다. 뮤텍스를 사용하는 방식은 뮤텍스 락을 잡을 때 OS 시스템콜을 해야 하는 반면에 이 방식은 OS의 도움 없이

스레드 세이프티를 구현하였기 ??문이다. 물론 Random 클래스 자체가 애초에 스레드 세이프한 클래스였을 필요가

있는지에 대해서는 의문을 제기할 수 있지만(스레드가 다르면 별도의 객체들을 쓰면 된다) 여기서는 그 문제는

차차하기로 하자.

앞의 예제는 낙천적인 컨커런시 제어를 atomicCompareAndSwap의 도움을 받아 구현한 사례이다. 성능 향상은

분명하지만, 여기서 문제점은 atomicCompareAndSwap을 사용해 스레드 세이프한 프로그램을 구현하는 일은

뮤텍스를 사용하는 기존 멀티스레드 프로그래밍보다 오히려 더 어렵다는데 있다. 데이터베이스처럼 트랜잭션

지원이 자동으로 되는 것이 아니라 트랜잭션을 개발자가 구현해야 하기 때문이다.

현재 프로그래밍 언어 연구자들은 프로그래밍 언어 차원에서 트랜잭션을 지원하기 위한 연구를 계속하고 있다.

현재 계측으로는 3-5년 안에는 주류 언어에서 트랜잭션 지원을 볼 수 있을 것이라는 이야기가 있다. 실제로

HSPC(High-Productivity Computing System, 고생산성 컴퓨터 컴퓨터 시스템)의 일환인 썬(Sun)의 포트리스

(Fortress), IBM의 X10, 크레이(Cray)의 샤펠(Chapel)은 모두 락 대신에 트랜잭션을 지원하는 프로그래밍 언어

컨스트럭트(Construct)를 지원하고 있다.

연구자들이 주목하는 분야는 트랜잭션 메모리(Transactional memory)라는 기술이다. 사실 트랜잭션이라는

개념은 데이터베이스 커뮤니티에서는 수십 년간 사용해왔으며 유동성이 이미 검증된 기술이다. 트랜잭션

메모리를 사용하는 프로그래밍 언어는 사용하기 편하고 규모가 커지더라도 성능 저하 없이 사용할 수 있다는

장점이 있다. 프로그래밍 언어 이론 분야에서 가장 중요한 학회인 POPL(Principles of Programming Language)

의 2006년 키노트에서 에픽 게임즈(Epic games)의 팀 스위니(Tim Sweeney)가 기존 수동 동기화 방식의

문제점을 지적하며 앞으로는 트랜잭션 지원이 사실상 유일한 대안임을 역설하였다.

아직 구체적으로 상용화되어 쓰이는 언어는 없지만 다음과 같은 가상의 예를 통해 트랜잭션을 지원하는

프로그래밍 언어가 어떤 형태인지 느껴보자.

<스레드 세이프한 복합 연산의 예>

A

move(Object key) {

synchronized(mutex) {

map2.put(key.map1.remove(key));

}

}

B

move(Object key) {

atomic {

map2.put(key.map1.remove(key));

}

}


컨트런트 해시 맵(Concurrent Hash Map)

기존 자바 콜렉션 프레임워크(Collection Framework)에서 제공하는 HashMap의 경우

스레드 세이프티를 고려하지 않고 설계되었다. 따라서 멀티스레드가 동시에 HashMap의

put()이나 remove() 메소드를 부르면 문제가 생길 수 있다. 이 경우 collection 클래스의

synchronizedMap() 메소드를 통해서 동기화된 맵을 얻어 올 수 있는데, 이렇게 얻어온

맵은 한 번에 하나의 스레드만 수행할 수 있다는 단점이 있다.

자바 5에서 추가한 java.util.concurrent 패키지는 자바 동기화와 관련된 여러 가지

유틸리티 클래스를 제공하는데, 그 중 하나가 ConcurrentHashMap()으로 얻어온 동기화

맵의 한계를 극복하고 내부적으로 여러 개의 해시 테이블을 운영하여 키가 같은 해시

테이블에 겹치지 않는 경우에는 동시에 여러 연산을 수행할 수 있도록 설계되었다.


위 프로그램은컨커런트 해시 맵(자바의 ConcurrentHashMap 클래스의 객체)에서 특정키 값을 뽑은 후에는

다른 컨커런트 해시 맵으로 옮기는 연산을 수행한다. 여기서 요구사항은 키가 두 해시 맵 중 어느 한 쪽에는

반드시 들어있어야 한다는 것이다. 즉 한 맵에서는 키를 제거하고 다른 맵에 넣어주기 전에 다른 스레드가

이를 볼 수 있어서는 안된다. 프로그램 A는 전통적인 동기화 방식을 사용한 예제이고, 프로그램 B는 트랜잭션

지원 프로그래밍 언어로 atomic이라는 키워드로 트랜잭션을 묶어준다.

A 방식과 B 방식은 문법적으로 유사해 보이지만, 실제로 큰 차이가 있다. A 의 경우 map2와 map1의 연산을

통째로 synchronized(mutex)로 묶어주었기 때문에 기존의 컨커런트 해시 맵이 제공하는규모 가변성(salability)

키가 겹치지 않는 한 여러 스레드가 동시에 해시 맵에 put(), remove()할 수 있다)을 활용하지 못하게 된다. 즉

remove() 메소드는 반드시 한 번에 한 스레드만 수행할 수 있는 것이다. 반대로 atomic으로 묶어준 프로그램

B의 경우 map1에서 뽑아 와서 map2에 넣어주는 키가 다른 스레드의 키와 상충 관계가 없으면 동시에 여러

스레드가 키를 뽑아서 넣을 수 있으므로 규모 가변성 측면에서 이득이 크다. 같은 일을 A 방식으로 하려면

훨씬 더 복잡한 프로그래밍이 필요하고 정확하게 만들기도 어렵다.

간단한 예제만 살펴봤지만 트랜잭션 메모리 지원이 차세대 프로그래밍 언어의 주요 이슈임을 분명하다.

트랜잭션이라는 검증된 기술이기에 새로운 이론적 배경을 만드는 일에 비해서 훨씬 더 실용화될 가능성이

크기도 하다. 길게 잡아도 앞으로 5-6년 안에는 트랜잭션 지원 언어가 주류로 편입하는 모습을 볼 수 있을

것이다.

이번에 소개한 기술은 당장 개발 환경에서 적용할 수 있는 기술은 아니다. Erlang의 같은 함수형 언어를

도입할 수 있는 개발 환경도 많지 않고, 프로그래밍 언어의 트랜잭션 지원은 아직까지 연구만 활발히 일어나고

있는 수준이다. 하지만 멀티 코어를 중심으로 빠르게 변화하는 프로그래밍 환경에서 병렬 프로그래밍을

용이하게 하기 위한 노력은 앞으로도 지속될 것이다. 병렬 프로그래밍을 성공적으로 이끌 궁극적인 패러다임이

무엇이 될지는 아직 명확하지 않지만 변화의 바람이 조만간 개발자들을 강타할 것은 분명하다.

=======================================================================================================

새로운 쿼드 코어가 인텔에서 소개되었습니다. 이제 점점 더 멀티스레드 처리 방식을 지원하는 소프트웨어의

개발은 더욱더 절실해지고 있다. 32비트를 넘어서 64비트의 운영체제를 지원하면서 처리속도의 비약적인 발전을

거듭해 나가고 있다.

@_@);; 내 머리는... 아직은 Hyperthreading 단계인 것 같습니다...

허니몬의 IT 이야기
병렬(Concurrent) 프로그래밍 언어 : Erlang
현재 주로 사용되는 프로그래밍 언어 C, C++, 자바 등의 프로그래밍 모델은 병렬 컴퓨팅이나 분산 시스템(Distributed
system)을 이론적으로 설명하는 수학적 모델과 상당한 격차가 있다. 이런 격차는 이론과 현실의 괴리를 가져왔고 잘
정립된 이론적 바탕을 두고도 개발자들은 뮤텍스와 컨디션 변수를 가지고 씨름하는 현실에 생기게 된 것이다.
이런 이론과 현실과의 괴리를 좁히고 병렬 프로그래밍을 전혀 다른 방식에서 접근한 언어로 프로그래밍 언어 Erlang
이 있다. Erlang은 1982년 이동통신 회사인 에릭슨(Ericsson)이 프로그래밍 언어에서 병렬 수행 지원을 가장 큰 목표로
두고 개발한 언어이다. 일련의 연구를 통해 Erlang 팀은 2가지 결론을 내렸다.
(1) 리스프(Lisp)나 프롤로그(Prolog) 같은 심볼릭(Symbolic) 언어가 훨씬 생산적이다.
(2) 초경량 프로세스(Ultra-lightweight process), 적극적 오류 회복(Activeerror recovery), 분산 메시지 전달(Distributed message passing) 같은 병렬 수행의 개념을 갖춘 언어가 병렬 프로그래밍에 훨씬 유리하다.
하지만 당시에 이런 조건을 모두 갖춘 언어가 없었으므로 새로운 언어를 디자인했는데, 그 결과로 나온 것이 바로 Erlang이다.
프로그래밍 언어를 연구하는 사람들은 함수형(Functional)언어가 병렬성 지원에 이상적이라고 말을 한다. 왜냐하면
병렬성 이론을 설명하는 데 일반적으로 수학적 함수를 많이 사용하고, 함수형 언어는 이런 수학적 함수를 직관적으로
구현하기 쉽기 때문이다. 하무형 언어는 명령형(imperative) 언어처럼 문(statement)을 실행하는 개념이 아니라 표현식
(Expression)을 계산(evaluation)하는 방식으로 프로그램을 수행한다. 덕분에 익숙하지 않은 사람은 함수형 언어가
어색하게 느껴질 수도 있지만, 이런 언어들은 매우 안정적임이 이론적으로 증명되어 있다.
Erlang은 함수형 언어로 설계되었다. 이해를 돕기 위해 피보나치 수열을 계산하는 간단한 예제를 살펴보자.

< Erlang으로 작성한 피보나치 수열>
fib(1) -> 1;
fib(2) - > 1;
fib(X) - > fib(x-1) + fib(x-2).

fib(5)를 호출하면 위에서 정의한 세가지 함수 fib(1), fib(2), fib(N) 중에 알맞은 함수를 패턴 매칭(Pattern matching)으로
찾아서 부른다. 5는 1, 2가 아니므로 fib(N)이 불린다. fib(5)는 fib(4)+fib(3)이 되고 fib(4)는 다시 fib(3)+fib(2), fib(3)은
fib(2)+fib(1)이 된다. fib(2)와 fib(1)은 1이라는 값을 가지므로 여기서 계산이 끝나게 된다. 즉 C/C++의 재귀함수(Recursive
function)가 마찬가지로 계산한다.
Erlang의 기본적인 데이터 타입은 다른 함수형 언어와 마찬가지로 튜플(tuple)과 리스트(list)이다. 더불어 아톰(atom)은
심볼, 상수(constant), 숫자, 문자열처럼 하나의 값을 말한다. 다음은 Erlang 데이터 타입의 예젱다.


atom
16
4.6

[this, is, a, ist, that, can, go, on, and, on]

{a, tuple}
{
{{word, tuple},
{definition, "a fixed length ordered collection of atoms"}},
{{world, erlang},
{definition, "a functional language optimized for concurrency"}}
}


sum([H|T]) -> sum(T) + H;

Erlang에서 리스트는 보통 패턴 매칭(Pattern matching)으로 처리한다. sum([H|T])에서 H는 머리(Head)로 리스소의
첫 번째 원소를 뜻하고 T는 꼬리(Tail)로 첫번째 원소를 제외한 리스트의 나머지를 말한다. 앞의 함수는 첫 번째
원소(H)의 값을 나머지 리스트(T의 전체합과 더하는 방식으로 전체 리스트[H|T]의 합을 계산한다. 이런 재귀 호출
방식의 계산은 함수형 언어의 전형적인 특징이다.
Erlang 언어 자체에 깊이 있는 내용은 관심있는 독자 분들은 좀 더 찾아보기를 권하며 Erlang의 병렬 프로그래밍
지원에 대한이야기로넘어가겠다. Erlang은스레드라는용어 대신에 경량프로세스(lightweight process)라는개념을
사용한다. Erlang에서 Pid=spawn(math, fact, [1234])와 같이 주면 매우 큰 팩토리얼(factorial)을 계산하는 프로세스를
생성한다.
Pid는 프로세스 간에 메시지를 주고받는 경로로 사용된다. Pid ! {hello, world}와 같이 주면 ! 연산자는 해당 Pid의
프로세스에 {hello, world} 라는 튜플을 메시지로 전달하라 의미이다. 받는 쪽에서 다음 코드처럼 receive 키워드를 통해
이 메시지를 받을 수 있다. 여기서도 함수형 언어의 전통을 따라 패턴 매칭을 사용하였다.


loop() ->
receive
{From, message} ->
io:format("Received : ~w ~n", [Message]),
loop();
stop ->
true
end.

여기서 다시 Erlang이 스레드가 아닌 프로세스라는 용어를 사용하고 있다는 점이 주목할 필요가 있다. Erlang이 말하는
경량 프로세스는 단순히 스레드를 지칭하는 또 다른 용어가 아니라 OS에서 말하는 프로세스에 가까운 특징을 가지고
있다. OS 프로세스처럼 Erlang 경량 프로세스는 서로 메모리 영역을 전혀 공유하지 않는다.
따라서 자바 멀티스레드 프로그래밍처럼 공유자원을 여러 스레드가 동시에 접근하는 일이 일어날 수 없다. 모든 데이터는
!와 receive를 통해서
명시적으로 주고받아야 한다. 덕분에 자바 프로그램에 빈번히 데드락과 레이스 컨디션 같은 상당수의 멀티스레드 버그가 Erlang에서는 이론적으로 발생할 수 없게 된다.
Erlang의 또하나의 특징은 단일배정(Single assignment)에 있다. 단일배정은 변수가 한번 초기화되고 나면 다른값으로
변화할 수 없는 수정 불가능한(immutable) 값이 됨을 의미한다. 수정 불가능한 타입은 값을 읽을 수만 있고 쓸 수는
없으므로 상당수의 병렬 수행 버그를 없앨 수 있다. 실제로 자바 멀티스레드 프로그래밍에서도 수정 불가능한 타입의
사용은 중요한 팁의 하나인데, Erlang은권고 수준이 아니라 언어 차원에서 이를 강제하고있다.
이처럼 Erlang의 병렬 수행 지원에 대해서 간략히 살펴보았는데, 여러가지 장점에도 불구하고 함수형 언어인 Erlang이
짧은 기간 안에 주류 언어로 부상하리라고 기대하지는 않는다. 다만 Erlang이 주는 교훈은 자바의 멀티스레드 방식이
아니더라도, 병렬 프로그래밍을 쉽게할 수 있는 간단한 패러다임이 있을 수 있다는 점이다. 주류 언어에 익숙한 개발자는
멀티스레드 프로그래밍이 유일한 병렬 프로그래밍이라고 생각하기 쉽다. 하지만 멀티 코어 시대에 사는 소프트웨어
개발자는 이제 좀 더 생산적인 패러다임을 생각해야 한다.
허니몬의 IT 이야기

참고로... 이글은 마소 2007년 3월에 서광열님(kwangyul.seo@gmail.com)께서 기재하신 기사를 제가 이해하는 과정입니다....

ㅡㅅ-);; 무조건 따라서 치면서 머리 속에 담는 것이지요....

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

멀티 코어시대에 대비한 소프트웨어기술


병렬(parallel/concurrent) 프로그래밍

하드웨어가 변하고 있다. 듀얼코어(Dual Core) 시스템이 일반 데스크탑에 탑재되고 있고, 프로세서가 4개인

쿼드코어(Quad Core)도 서버 머신에서 사용되기 시작했다. 고사양 서버 컴퓨터뿐만 아니라, 임베디드 시스템

에도 여러 개의 코어를 사용하는 경우가 늘고 있다. 대표적인 예제가 ARM11의 MPCore 기술인데, 1-4개의

코어를 사용할 수 있다. 하드웨어 단에서 멀티코어의 등장은 개발자들에게도 변화를 요구하기 시작했다. 하부의

여러 프로세서를 좀 더 적극적으로 활용하기 위해 멀티스레드 프로그램이 더욱 중요한 요소로 주목받기 시작한

것이다. 하지만 멀티스레드 프로그램은 작성하기 어렵고 테스트와 디버깅은 더 어렵기로 악명 높다.

멀티 코어 시대에 대비하는 소프트웨어 기술은 무엇인지 살펴보자.


멀티 코어 시대의 소프트웨어

멀티 코어 시대는 멀티스레드 프로그래밍을 요구한다. 프로세스 벤더들이 과도한 열 발생 문제 때문에 CPU클럭

경쟁에 한계를 느끼고, 멀티코어 아키텍처로 전환하기 시작한 시점부터 소프트웨어 개발자들도 이 변화에 동참

하기를 요구받고 있다. 과거 프로세서의 최적화 기술인 인스트럭션 레벨 병렬화(Instruction Level Parallelism)가

소프트웨어의 변화 없이 하드웨어의 성능 향상을 추구했다면, 현재의 멀티 코어 아키텍처는 성능 향상을 위해

소프트웨어 개발자의 적극적인 참여를 필요로 한다.

즉 과거는 하드웨어가 발전하면 소프트웨어는 가만히 앉아서 성능 향상의 이득을 누렸지만, 앞으로는 소프트웨어가

적극적으로 하드웨어의 병렬성(parellelism)을 이용하지 않는 이상 추가적인 성능향상을 기대하기 어렵다는 말이다.

이런 상황에서 전문가들은 병렬 혁명(Concurrent revolution)은 하드웨어가 아닌 소프트웨어에서 나올 것이라고

말하고 있다. 실제로 기술적인 관점에서 멀티 코어 하드웨어를 만드는 일은 크게 어렵지 않다고 한다. 문제는 현재의

프로그래밍 패러다임으로는 멀티 코어를 제대로 활용하는 멀티스레드 소프트웨어를 작성하기가 쉽지 않다는 것이다.

조금만 복잡해지면 버그 투성이가 되고 마는 멀티스레드 프로그램이기 때문이다. 하지만 그럼에도 불구하고 이제

소프트웨어 개발자는 코어가 여러 개 있다는 가정 하에 명시적으로 이를 활용하는 소프트웨어를 작성해야 한다.

앞으로는 병렬 프로그래밍은 개발자들의 선택이 아닌 필수가 될 것이다.


멀티스레드 프로그래밍의 어려움

하지만 멀티스레드 프로그래밍은 개발자의 골칫거리다. 스레드 숫자가 늘어날수록 프로그램의 복잡도는 급속히

상승한다. 멀티스레드 프로그램은 디버깅도 어렵다. 멀티스레드 프로그래밍은 프로그램 수행이 불확정적(nondeter- minstic)이다. 다시 말해서, 같은 프로그램이라도 매번 실행 때마다 미묘하게 타이밍이 바뀐다. 이런

특성 때문에 멀티스레드 고유의 버그인 데드락(Deadlock), 라이브락(Livelock), 레이스컨디션(Race condition)은

디버깅하기도 테스트하기도 쉽지 않다. 테스트할 때는 멀쩡하게 잘 되다가 실제로 배포되고 나면 말썽을 일으켜서

개발자들을 괴롭힌다.

현재 멀티스레드 프로그램은 스레드를 새로 생성하고, 뮤텍스(Mutex, mutual exclusion), 컨디션 변수(Conditional

variable)등 기초적인 요소를 이용해 작성한다. 하지만 뮤텍스나 컨디션 변수는 너무 하위 레벨의 스레드 제어 도구라서

올바른 멀티스레드 프로그램을 작성하기는 무척 어렵다. 공유 자원에 접근할 때마다 매번 뮤텍스를 잡았다 풀어야 하고,

이 과정에서 한 번이라도 뮤텍스 unlock을 빠뜨리거나 순서를 잘못 정하면 데드락이 된다. 결국 현재의 패러다임으로는

올바른 멀티스레드 프로그램을 만들 수 있기를 기대하기가 어렵다.

흔히들 자바의 등장이 멀티스레드 프로그래밍을 대중화시켰다고 한다. C/C++프로그램이 pthread 같은 외부 라이브러리

에 의존해야만 멀티스레드 프로그래밍을 할 수 있는 반면에 자바는 멀티스레드 프로그래밍을 언어에 내재시켰다. 자바는

멀티스레드 프로그래밍을 언어에 내재시켰다. 자바에서는 java.lang.Thread 클래스로 쉽게 새로운 클래스를 만들 수 있고,

synchronized 같은 키워드를 통해 모니터를 잡을 수 있다. java.nio가 나오기 전에 자바는 모든 IO가 블로킹(Blocking)이

었기 때문에 스레드의 사용이 매우 보편화되기에 이른다.

하지만 여기서 생각해 보아야 할 점은 스레드를 쉽게 만들 수 있는 것과 멀티스레드 프로그램 버그 없이 쉽게 작성할 수

있는 것 사이에는 큰 차이가 있다는 점이다. 멀티스레드 프로그래밍을 제대로 이해하지 못한 일련의 자바 개발자들은

데드락과 레이스 컨디션으로 얼룩진 엉터리 멀티스레드 프로그램을 양산해내기 시작했기 때문이다. 이런 자바 프로그램은

개발자가 테스트할 때만 잘 동작한다는 특징을 가진다.

제대로된 멀티스레드 프로그램을 작성하기 어려운 이유가 무엇일까? 현재의 멀티스레드 프로그래밍 모델을 묘사한 말

중에 "바닥에 포도 주스 쏟지 마라(Don't spill grape juice on the carpet)"는 말이 있다. 풀어서 말하면, Lock, Unlock을

제대로 하고, 컨디션 변수에 제대로 wait, signal을 날려주는 것은 절대적으로 개발자의 몫이고 여기서 발생하는 실수를

방지하기 위한 어떠한 안전장치도 제공해주지 않는다는 것이다. 포도 쥬스를 쏟지말라(뮤텍스를 제대로 써라)라는 원칙만

있지 포도 쥬스가 쏟아지는 일(데드락, 레이스 컨디션의 발생)을 막을 장치는 하나도 없는 것이다.

자바 멀티스레드 프로그래밍에 대한 새로운 지평을 연 책인 "Java Concurrency in Practice" [1] 에 대한 다음과 같은

자바 코드 예제가 나온다.

<멀티스레드 환경에서 문제가 되는 코드>

public class UnsafeSequence {

private int value;

/** Returns a unique value **/

public int getNext() {

return value++;

}

}

이 프로그램을 이용해 고유한 ID(Unique ID)를 생성한다면 단일 스레드에서는 문제없이 동작하지만 멀티스레드 환경에서는

문제가 발생한다. 두 스레드가 동시에 Value의 값을 읽은 후에 value +1을 value에 저장할 경우 두 스레드가 같은 값을

읽어갈 수 있기 때문이다. 이런 버그가 생기는 이유는 value++ 이란 코드가 원자성(atoicity)을 보장해주지 않기 때문이다.

이 코드는 하나의 명령으로 보이지만 실제로는 (1)value의 메모리 값을 읽어오는 일과 (2) value + 1을 메모리에 쓰는 2가지

일로 이루어져 있고 (1)과 (2) 사이에 컨텍스트 스위치(Context Switch)가 일어나 다른 스레드가 끼어들 여지가 있다.

멀티스레드 프로그램 버그를 만드는 또 다른 원인으로는 가시성(Visibility)이 있다. 현재의 CPU는 캐시(Cache)를 가지고

있고, 멀티코어의 경우는 각각의 CPU가 캐시를 가진다. 이런 상황에서 한 스레드가 바꾼 메모리의 값을 다른 스레드가

곧바로 볼 수 있다는 보장을 하기가 힘들다. 가시성은 한 스레드 변경한 메모리의 값이 다른 스레드에 어떤 시점에 보여야

하는지를 명확히 제어하려는 시도이다.

멀티스레드 프로그램 버그를 만드는 또 다른 원인으로는 가시성(Visibility)이 있다. 현재의 CPU는 캐시(Cache)를 가지고

있고, 멀티 코어의 경우는 각각의 CPU가 캐시를 가진다. 이런 상황에서 한 스레드가 바꾼 메모리의 값을 다른 스레드가

곧바로 볼 수 있다는 보장을 하기가 힘들다. 가시성은 한 스레드 변경한 메모리의 값이 다른 스레드에 어떤 시점에 보여야

하는지를 명확히 제어하려는 시도이다.

자바에서는 원자성과 가시성을 보장하기 위해 Synchronized 키워드를 사용한다. 문제는 자바의 Synchronized 키워드는

오용하기 무척 쉽다는 점이다. 오용 사례는 다음과 같다.

(1) 동기화해야 하는 리소스에 대해 synchronized 사용하는 것을 빠뜨린 경우

(2) 잘못된 리소스에 synchronized 를 사용한 경우

(3) synchronized 를 너무 빈번히 사용하는 경우

(1), (2)번 사례의 문제점은 쉽게 알 수 있고, (3)의 경우는 성능과 관련이 있다. 모든 리소스에 락을 걸어서 사용하면

멀티스레드 버그를 피할 수는 있다. 하지만 모든 리소스에 락을 걸면 사실상 동시에 하나의 스레드만 동작할 수 있으므로

실제로는 멀티스레드가 아닌 단?嬋볜뭇 프로그램이나 마찬기자기 되는 문제가 있다. 멀티 코어의 시대가 왔을 ??도 이런

멀티스레드 프로그래밍 패러다임을 이용해서는 하부 하드웨어의 성능을 100% 끌어내기가 힘들다. 우리가 작성한

프로그램은 데드락과 레이스 컨디션으로 오작동하는 프로그램이 되거나 멀티코어를 제대로 활용 못하는 싱글스레드에

가까운 프로그램이 되는 두 운명의 갈래에서 서성일 것이다.

To be continue....

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

내가 생각하는 내가 나아갈 방향은... 서버 + 네트워크 + 프로그래밍... 그 중심에는 멀티스레드 프로그래밍이...

중심이 되지 않을까라는 생각을 하고 있다. 아마도... 미래의 컴퓨터들은 고클럭+다중코어 체제로 들어설 것이다.

이를 효율적으로 사용할 수 있는 능력을 키운다면... 충분히 내 위치를 확보할 수 있을 거라고 생각한다.

우선은 내가 해야할 부분은... 네트워크 쪽이 될 것으로 보인다.... ^^;;

일단은 이 바닥...(ㅡㅅ-);; 사촌형이 이바닥이라고 하지말라는데... 후후~~)을 잘 디뎌서 이 세상으로 뛰쳐나갈

준비를 하고 있습니다. ^3^ 우후후후~~ 덤벼라 세상아!!?


관련링크 : 병렬(parallel/Concurrent) 프로그래밍 2/3
   병렬(parallel/Concurrent) 프로그래밍 3/3
허니몬의 IT 이야기
1 ··· 70 71 72 73 74 75 76 ··· 80
블로그 이미지

Email : ihoneymon@gmail.com 안녕하세요, 꿀괴물 입니다. ^^ 멋진 비행을 준비 하는 블로그 입니다. 만능형 인간이 되어 많은 이들에게 인정받고, 즐겁고 행복하게 살기를 간절히 원합니다!! 달콤살벌한 꿀괴물의 좌충우돌 파란만장한 여정을 지켜봐주세요!! ^^

허니몬