원글 : http://kldp.org/node/295


사용자 수준 쓰레드와 커널 수준 쓰레드의 차이에 대해서
설명해 주실수 있으시겠습니까?

각각 장단점이 있다고 하는데,
잘 이해가 되질 않네요.
사용자 수준은 문맥교환의 오버해드가 없다,
커널 수준은 사용자 수준 보다 효율적일 수 있다..
라고 하는데 잘 이해가 가질 않네요..

그리고 현재 리눅스에서는 두가지 쓰레드 모델이 모두 지원 되나요?

<FORM id=nodevote_form action=/node/295 method=post>
» ollllo의 블로그 | 868번 읽힘
</FORM>

일반적으로 많이 나와있는 내용이라 찾아보시면 자료들이 많을겁니다만..

간단하게 생각해보면, (저도 자세히는 모르지만 -_-)

user thread는 process내부에 여러개로 구성될 수 있고, 한 process내에서
같은 리소스를 가지고 다른 user thread와 통신할 수 있으니 당연히 context switching의 overhead가 없죠.

kernel thread는 user thread에서 LWP 자료구조를 통해 연결되는데,
보통 kernel scheduler에 의해 cpu를 할당받아 사용하지만, 직접 cpu를 할당 받아서 사용할 수도 있으니 당연히 효율(?)적일 수도 있겠죠.. 쿨럭.. :shock:

사용자 쓰레드와 커널 쓰레드

인용:
사용자 수준은 문맥교환의 오버해드가 없다,

문맥교환은 컨텍스트 전환(context switch)을 잘못 번역한 말입니다. 잘못된
용어로 이해를 하면 나중에 더 헷갈릴 우려가 있기 때문에... :)

사용자 쓰레드 방식이 커널 쓰레드보다 오버헤드가 적은(없지는 않음) 이유는
쓰레드간을 전환할 때마다 커널 스케줄러를 호출할 필요가 없기 때문입니다.
커널 스케줄러로 진입하려면 프로세서 모드를 사용자 모드에서 커널 모드로
전환해야 하는데, 이때 사용자쪽 하드웨어 레지스터를 전부 저장시키고, 커널
레지스터를 복구하고, 기타 등등...의 수많은 작업이 밑에서 일어납니다.
따라서 사용자 모드와 커널 모드를 많이 왔다갔다 할 수록 성능은 급격하게
떨어지는 것이죠. 사용자 쓰레드는 쓰레드 스케줄러가 사용자 모드에만 있기
때문에 그런 오버헤드는 발생하지 않습니다.

인용:
커널 수준은 사용자 수준 보다 효율적일 수 있다..

그런데 사용자 쓰레드의 결정적인 단점은 프로세스내의 한 쓰레드가 커널로
진입하는 순간 나머지 쓰레드들도 전부 정지된다는 점입니다. 커널이 쓰레드의
존재를 알지 못하므로 불가피한 현상입니다. 쓰레드가 커널로 진입할 때는
write(), read(), ...같은 시스템 호출을 부를 때인데, 시스템 호출 길이가
짧아서 바로 리턴할 때는 문제가 없지만 연산이 길어지면 상당한 문제가
됩니다. 전체 프로세스의 응답성이 확~ 떨어지죠.

그리고 커널 쓰레드를 쓰면 멀티프로세서를 활용할 수 있다는 큰 장점이
있습니다. 사용자 쓰레드는 CPU가 아무리 많더라도 커널 모드에서 쓰레드
단위로 스케줄이 안되므로 각 CPU에 효율적으로 쓰레드를 배당할 수 없는
문제가 있습니다(프로세스 단위로만 배당이 되므로).

그래서 리눅스, 윈도, 솔라리스를 비롯한 대부분의 운영체제는 사용자 쓰레드를
쓰지 않고 커널 쓰레드, 또는 커널/사용자 쓰레드 혼합 방식을 쓰는 추세입니다.

인용:
그리고 현재 리눅스에서는 두가지 쓰레드 모델이 모두 지원 되나요?

예, 모두 지원됩니다.

쓰레드 방식은 크게 세가지가 있습니다. 이중 사용자 쓰레드는 요즘들어
리눅스에서는 별로 쓰는 사람이 없는 것 같고, 1-on-1 커널 쓰레드 방식인
glibc내의 LinuxThreads가 가장 널리 쓰입니다. 최근에는 이걸 더 발전시킨
NPTL(Native POSIX Threading Library)가 활발히 개발중에 있습니다.
커널/사용자 쓰레드 혼합 방식인 NGPT(Next Generation POSIX Threads)
도 IBM의 지원하에 최근 2.2.0까지 나와 있습니다. 둘간의 차이를 여기서
설명하기엔 좀 긴 것 같고, 전자가 후자보다 훨씬 간단한 반면, 후자의 성능이
전자보다 다소 높은 것으로 알려져 있습니다. 여담입니다만, NPTL이 처음
나왔을 때 우리 것이 NGPT보다도 성능이 훨씬 좋다고 자랑(?)을 했더니
NGPT팀이 자극을 받았나 봅니다. NGPT 홈페이지에 가면 새버전(2.2.0)
성능이 LinuxThreads는 물론 NPTL도 능가한다고 큼지막하게 써놨습니다.

인용:
kernel thread는 user thread에서 LWP 자료구조를 통해 연결되는데

중요한 것은 아닙니다만...

LWP는 경량 프로세스(lightweight process)의 준말로, 솔라리스에서(그리고
최근의 NetBSD에서) 내부적으로 쓰는 말입니다. 리눅스, 윈도, FreeBSD에선
그냥 thread라고 합니다.

--
http://www.kr.netbsd.org/~junyoung

쓰레드 패키지 아케텍쳐

참조 하세요. 이 글 쓰느라 시간 좀 들였습니다.

Thread-Package Architectures는 크게 4가지가 있습니다. User-level threads, Kernel-level threads, multiplexed threads, kernel-supported user-level threads

[User-level threads]

User-Level threads는 응용 프로그램과 Link/Load가 되는 라이브러리로 구현되어집니다. 이 라이브러리에 동기화, 스케줄링 기능을 모두 담고 있습니다. 커널에서는 아무런 지원을 해주지 않으며, 커널이 보기에는 단지 그냥 Single process일뿐입니다. 프로세스마다 런타임 라이브러리의 Copy가 호출되므로 스케줄링 정책을 프로세스마다 달리 취할 수 있으며, 각 Thread마다 time quantum을 소모할 필요 없고, 런타임 라이브러리가 context를 유지하기때문에 switching을 할 필요가 없습니다. 그래서 User-Level Threads는 빠르고, 매우 효율적입니다. 그러나 장애가 꽤 있습니다.

1. Blocking System Calls
Blocking function이란 처리가 완료되지않으면 return되지 않는 함수인데, 만약 특정 Thread에서 Blocking이 되어 버리면, 전체 process가 Blocking이 되어버립니다. 이런 이유로 운영체제가 제공하는 non-blocking 함수들만 사용해야 하며, 사용 빈도가 높은 함수(read,select,wait,...)들은 해당 함수의 non-blocking 버젼으로 대체해야할 필요가 있습니다.
2. Shared System Resources
동기화나 Locking없이 Thread끼리 공유하는 변수(드러나지 않고 감춰져 있는 경우)가 있을때, 그 Thread가 thread-safe하지 않으면 Overwrite되는 문제가 생길 수 있습니다. 이 이유로 사용할 함수는 재진입이 가능해야합니다. User-Level뿐만아니라 Kernel-Level 함수까지 모두.
3. signal Handling, Thread Scheduling
User-Level에서 이것을 구현하기란 상당히 어렵습니다. Timeslice를 다루기 위해 Hardware Clock 인터럽트를 보통의 방법으로는 받지 못합니다. 선점형(Preemptive) 스케줄링을 하기 위해서는 커널로 부터 Time Siganl을 받는 함수를 등록해두어야 하며, Timer Alarm Siganl을 다루는것은 다른 시그널을 다루는 것보다 아주 어럽습니다.
4. Multiprocess Utilization
하나의 프로세스에서 Time을 공유하고 있기때문에 여러개의 CPU를 동시에 사용할 수는 없습니다.

정리 - 구현상의 어려움과 복잡성 그리고, 몇가지 장애에도 불구하고, concurrency와 efficiency의 이득을 가져다 줍니다.

[Kernel-level threads]

Kernel-level에 있는 Threads는 독립적으로 스케줄되므로 특정 Thread에서의 Blocking이 process로 전파되지 않습니다. 그래서 Blocking System Calls를 이용할수 있습니다. 또한 각 Threads끼리 Signal을 주고 받을 수 있습니다.
Kernel-level threads는 특별히 고려할만한 장애를 가지고 있지는 않습니다. 물론 마찬가지로 Thread-Safe해야 하지만, OS 개발자들은 대개의 표준 라이브러리를 Thread-Safe하게(재진입해도 문제없겠끔) 만들기에 User-level threads보다 통상적으로 보다덜 말썽을 피웁니다.

Kernel-level threads는 안정성에 비해서 너무 느리다는게 큰 단점입니다. 바로 Thread Context-Switch 때문입니다. 마로비치의 연구에 의하면 10배정도 느리다고 합니다.

[multiplexed threads]

User-level threads와 Kernel-level threads를 섞은 방법입니다. User-level thread(줄여서 Thread)는 LWP(가벼워진 프로세스, Lightweight Processes)에 의해 multiplex됩니다. 커널은 LWP를 스케줄링/실행하고, LWP는 대기중인 thread를 골라서 실행합니다. thread는 하나의 LWP에서 실행이 되어지며, Time slice가 바뀔때 LWP도 바뀌어질 수 있습니다. 프로그래머는 thread를 사용할 수도 있고, LWP를 직접 사용할수 있고, 둘 모두를 동시에 사용할 수도 있습니다.

User-level threads처럼 작동하면서 Hardware Parallelism과 Blocking calls에 대처할 수 있으며, Context-Switch을 많이 하지 않습니다.

이것의 장애는, LWP가 Blocking이 되면 이 LWP가 가진 몇개의 Thread도 동시에 Blocking이 됩니다. 또한 LWP의 Context-Switch 비용은 Kernel-level threas보다 결코 싸지 않습니다. 또한 다중 CPU에서 효율적일려면 각 Thread는 각각 다른 LWP에 할당되어 있어야 합니다. 각 Thread의 LWP 할당과 LWP의 CPU 할당은 별개로서 이루어지기 때문에, 각 Thread가 서로다른 CPU에 할당될려면 이렇게 작동하겠끔 하는 매커니즘이 따로 존재하여야합니다.

이러한 이유로 인해 LWP에 의한 multiplexed threads는 궁극의 해결책은 못됩니다. 이상적으로, Kernel-level threads의 결정적인 단점인 Thread Context-Switch를 user-level threds만큼 빠르게만 한다면 그것으로서 모든 장애는 해결됩니다. 물론 이 방법은 있지만 여전히 문제점을 가지고 있습니다. 아래 방법입니다.

[Scheduler Activation(kernel-supported user-level threads)]

이 방법은 User-level threads들을 위한 특별한 지원을 kernel이 해주는겁니다. 그 부분은 Scheduler Activation이라 합니다. User-level threads 라이브러리는 커널에게 프로세스를 요구할때와 양도할때를 알려줍니다. 그러면 커널의 Scheduler Activation은 이것을 커널에 의한 프로세스 주소 공간으로 할당된 Virtual Processor로 표현합니다. 여기서 스케줄링과 Blocking 감지가 이루어지며, Granted processor,Preemptive thread,Blocked,Unblocked등등의 Event를 User-level의 런타임 시스템에게 알려줍니다.

개념적으로 이 방법이 가장 확실합니다만 이것은 단 한가지, 그러나 치명적일 수도 있는, 약점이 있습니다.

커널과 라이브러리 코드가 효율적인 교신을 위해 같은 주소 공간을 공유하기 때문에 예외적으로 높은 신뢰를 가지고 있어야 하며 bug-free이어야합니다. 이 방법은 개념적으로 비정상적인 작동(의도적인 비정상적 작동 포함)과 버그에 대해서 강건함(robust)을 가지지 못합니다.

SunOS는 multiplexed threads를 지원합니다. 그외(WindowsNT,MACH,OS/2)는 Kernel-level threads를 지원합니다. 이 책에서는 MACH가 Kernel-level threads를 지원한다고 되어 있지만 어떤 Web site에 가니 MACH도 LWP도 지원하는걸로 되어 있군요. 이것이 multiplexed threads를 지원한다라는건지 아님 User-level threads의 구현상의 한방법인지는 모르겠습니다. 어쩌면 둘은 말만 다를뿐 별로 차이가 없는 것일지도 모르겠고.

Linux에서 작동되는 User-level threads는 아래와 같습니다.
Bare-Bones Threads,CLthreads,DCEthreads,FSU Pthreads,JKthread,LinuxThreads,LWP,NThreads (Numerical Threads),PCthreads,Provenzano Pthreads,QuickThreads,RexThreads.
http://www.sftw.umac.mo/resource/LDP/FAQ/Threads-FAQ/ThreadLibs.html에서 퍼왔습니다.
소스도 있으니 훝어 보는 것도 좋겠습니다.

인용:
Thread-Package Architectures는 크게 4가지가 있습니다. User-level threads, Kernel-level threads, multiplexed threads, kernel-supported user-level threads

분류가 약간 잘못 되었습니다. 쓰레드 모델은 크게

    user-level threads (1-on-N)
    kernel supported threads (1-on-1)
    multi-level (userland/kernel hybrid) threads (M-on-N)

3가지가 있고, 스케줄러 액티베이션은 마지막 M:N 모델의 일종입니다. 순수
사용자수준 쓰레드의 대표적인 것으로 GNU Pth, FreeBSD libc_r이 있고,
1:1 커널 쓰레드는 리눅스의 glibc/LinuxThreads, 윈도의 고유 쓰레드
등이 있습니다. M:N 쓰레드는 솔라리스 예전 버전의 "순수" M:N 모델이
있고, 최근 솔라리스 7,8에서는 스케줄러 액티베이션을 사용합니다(버전
9부터는 다시 1:1 쓰레드가 기본 쓰레드로 사용됩니다). 그리고 NetBSD의
스케줄러 액티베이션과 FreeBSD의 커널 스케줄러 엔티티도 M:N 방식입니다.

쓰레드 패키지 아키텍쳐

음..

좋습니다. 이제 제가 님의 분류가 일리가 있는건지 한번 분석해보고 싶군요.

할 말은 이미 충분히 많지만, 제가 님의 비판을 하기에 앞서, 님의 분류에서 개념이 제대로 설명이 안된걸 추측할 수가 없습니다. 1 on N과 1 on 1, M on N의 정확한 개념 또는 정의를 알려주세요. 가장 중요한걸 말 안하고 이러이러한게 있다고만하니 비판조차 하기가 힘듭니다.

그리고 제 글에 대해서 분류가 틀렸다고 하니, 제가 쓴 글의 내용이나 알고 이런 소릴 하는건지 의문도 들고, 그러나, 머 어쨌거나, 용기가 대단하게 느껴진다기 보다 참으로 무모하다라는 생각이듭니다. 하룻강아지가 범 무서운줄 모르는 것같은 그런 거 말이죠.

참고로 제가 쓴 글은 제가 습득한 지식을 기반으로 작성된게 아니라 MultiThreaded Programming - Thuan Q.Pham,Pankaj K.Garg의 7장 Thread-Package Architectures를 요약한겁니다. 맨끝부분 MACH와 Linux에서 사용 가능한 사용자-레벨에서의 Thread Package만 제가 조사해서 추가했을뿐입니다. 못믿겠으면 윈서 보고 대조해보세요.

아무튼 1 on N과 1 on 1, M on N의 개념을 정확히 밝혀 주시기 바랍니다.
기대하겠습니다.

인용:
할 말은 이미 충분히 많지만, 제가 님의 비판을 하기에 앞서, 님의 분류에서 개념이 제대로 설명이 안된걸 추측할 수가 없습니다. 1 on N과 1 on 1, M on N의 정확한 개념 또는 정의를 알려주세요. 가장 중요한걸 말 안하고 이러이러한게 있다고만하니 비판조차 하기가 힘듭니다.

글쎄요, 쓰레드에 관해 잘알고 계신 것처럼 말씀하시는 분이 1-on-N이나
M-on-N이 무엇인지 모른다고 하시니...이해가 안되지만, 아무튼,

커널쪽에서 보면 하나의 쓰레드고 사용자쪽에서 보면 여러 개의 쓰레드인
모델을 1-on-N(1대N) 모델이라고 합니다. 사용자레벨 쓰레드를 이렇게
표현하죠. 두번째로, 커널쪽과 사용자쪽의 쓰레드가 1대1로 대응하는 모델을
1-on-1 이라고 합니다. 그리고 마지막으로 커널 쓰레드의 개수와 사용자
쓰레드의 개수가 1대1 대응관계를 이루지 않는 모델을 M-on-N 모델이라고
합니다. LWP나 스케줄러 액티베이션이 모두 M대N 모델입니다.

인용:
그리고 제 글에 대해서 분류가 틀렸다고 하니, 제가 쓴 글의 내용이나 알고 이런 소릴 하는건지 의문도 들고, 그러나, 머 어쨌거나, 용기가 대단하게 느껴진다기 보다 참으로 무모하다라는 생각이듭니다. 하룻강아지가 범 무서운줄 모르는 것같은 그런 거 말이죠.

답변 내용중에 잘못된 점이 있으면 누구나 지적을 할 수 있는 것 아닙니까?
저는 단지 "분류가 약간 잘못되었다"고 말씀드렸을 뿐인데, 과잉반응을
하시는군요. :(

참고로 전 리눅스의 clone(2) 시스템 호출(LinuxThreads의 핵심이죠)을
NetBSD로 이식하는 작업에 참여했고, Wine 프로젝트를 위해 NetBSD
쓰레드 지원 작업을 하기도 했습니다. 물론 그둘은 모두 NetBSD와
Wine에 공식적으로 포함되어 있으니 제가 무엇을 했는지는 직접 코드를
보고 확인하셔도 좋습니다. 저보고 "하룻강아지 범 무서운 줄 모른다"는
Paladin님은 쓰레드로 무엇을 해보셨습니까?

인용:
참고로 제가 쓴 글은 제가 습득한 지식을 기반으로 작성된게 아니라 MultiThreaded Programming - Thuan Q.Pham,Pankaj K.Garg의 7장 Thread-Package Architectures를 요약한겁니다. 맨끝부분 MACH와 Linux에서 사용 가능한 사용자-레벨에서의 Thread Package만 제가 조사해서 추가했을뿐입니다. 못믿겠으면 윈서 보고 대조해보세요.

못믿는 게 아니라 Paladin님이 책을 제대로 이해하지 못한 걸 지적한 것
뿐입니다. 책에서 스케줄러 액티베이션에 별도의 절을 할애해서 설명하고
있는 이유는 멀티레벨 쓰레드의 문제점을 해결하는 데 있어 현재까지 알려진
가장 훌륭한 해결책이기 때문에 그렇습니다.

쓰레드 패키지 아키텍쳐

여전하군요.

개념/정의 질문을 한 이유는, 제가 님의 엉터리 논리의 저변까지 꿰뚫어 볼 수는 없는지라 재확인겸해서 한 질문인데, '그것도 모르냐'는 식으로 버릇없게 한술 더 뜨는군요. 게다가 여전히 개념과 일관성없는 단어 사용등 엉망이고. 논리적인 면에서도, 커널쪽에서 보면 몇개 사용자쪽에서 보면 몇개라는 식의 분류를, 그럴듯 하지도 않음에도 불구하고, 계속 M:N Model로 만들어서 몰고가고 있고.

사용자-레벨에서의 쓰레드는 커널과는 완전히 무관하고, 커널이 보는건 이 쓰레드들을 담고 있는 하나의 프로세스뿐입니다. 그래서 님의 논리를 억지로 갖다 붙힌다하더라도, 커널이 보는 사용자-레벨의 쓰레드는 1개가 아니라 0개입니다.

커널-레벨 쓰레드는 커널-레벨에서 돌아가는 쓰레드를 말하는거지만, 님의 논리대로 사용자 레벨에서 봐도("사용자레벨에서 본다"라는 이 말자체가 문제이기 하지만) 쓰레드 1개가 돌고 있다고 봐도 무관한지라 1 on 1이라고 우기면 못 봐줄건 없습니다.

LWP를 이용한 Multiplexing은 님의 논리와는 전혀 다르지만 M:N으로 넣어줄 수는 있습니다. 그러나 커널에서 보면 M개, 사용자 레벨에서 보면 N개가 아니라, LWP M개, Thread N개가 실재로 동시에 존재하는 겁니다.

그리고 안타깝게도 스케줄러 액티베이션은 M:N이 아니군요. 이것은 사용자-레벨의 스레드를 위해 커널에서 지원(가상 프로세스, 이전 글 참조)을 해주는 것이지 어떤 M:N 관계가 있지는 않습니다.

""단지 "분류가 약간 잘못되었다"고 말씀드렸을""

제가 쓴 글은 책에 있는거고 제가 그 책의 분류와 내용에 아주 잘 이해하고 공감을 하는지라 머가 잘못되었다고 해서 흥분할 일은 없습니다. 다만 님이 말도 안되는 모델을 내세우며 "이게 맞아"라고 하니 뭔가 좀 정신이 들겠끔 해드리고 싶더군요. 님이 먼저 쓴 글은 그냥 무시(비하한다는 뉘앙스는 없고 'ignore'임)하였으나 (답장 안달았음) 님이 제 글에 답장을 다니 그냥 무시하고 지나갈 수가 없었습니다. 잘못된 지식이 한참 배우는자들에게 전달되면 참으로 피곤한 세상이 되는지라 미연에 막을 필요가 있었습니다.

""참고로 전 리눅스의 clone(2) 시스템 호출(LinuxThreads의 핵심이죠)을
NetBSD로 이식하는 작업에 참여했고, Wine 프로젝트를 위해 NetBSD
쓰레드 지원 작업을 하기도 했습니다. 물론 그둘은 모두 NetBSD와
Wine에 공식적으로 포함되어 있으니 제가 무엇을 했는지는 직접 코드를
보고 확인하셔도 좋습니다""

한 일(Porting,..)이 그다지 대단해 보이지도 않는데다, 전 님을 신뢰하지 않습니다. NetBSD에 대해 뭔가를 강조하지 말았으면 합니다. 솔직히 님을 보면 NetBSD까지 신뢰가 가지 않습니다.

"못믿는 게 아니라 Paladin님이 책을 제대로 이해하지 못한 걸 지적한 것
뿐입니다. 책에서 스케줄러 액티베이션에 별도의 절을 할애해서 설명하고
있는 이유는 멀티레벨 쓰레드의 문제점을 해결하는 데 있어 현재까지 알려진
가장 훌륭한 해결책이기 때문에 그렇습니다."

말도 안되는건 끝까지 이어져 있군요. 지적이라니! 이해하지 못한걸 지적한다라니 어이가 없음.

따끔한 충고 한마디하죠.

계속 말안되는 말만해대면 무식함이 만천하에 드러나니 NetBSD를 위해서라도 자중하세요. 아님 진짜 제대로 설득을 보겠다고 하면, 해당 논리(M on N Model)에 대한 구체적인 논문 또는 자료를 하나라도 증거로서 제시해주든가.

실력은 고사하고 기본적 예의부터 상당히 없으신 것 같군요. 첨보는 사람한테 하룻강아지 범무서운 줄 모른다고 무례한 말씀을 하실 때부터 알아봤어야 하는 건데, 좋습니다. 저도 님같은 분께는 별로 예의를 차리지 않겠습니다.

인용:
사용자-레벨에서의 쓰레드는 커널과는 완전히 무관하고, 커널이 보는건 이 쓰레드들을 담고 있는 하나의 프로세스뿐입니다. 그래서 님의 논리를 억지로 갖다 붙힌다하더라도, 커널이 보는 사용자-레벨의 쓰레드는 1개가 아니라 0개입니다.

혹시 "single threaded"란 표현은 들어보셨습니까? 뭐, 들어본 적이 없으니까쓰레드가 0개라는 반론을 하시겠지만요.

1-on-1이니 M-on-N이니 하는 표현은 제가 만든 것도 아니고 쓰레드를 제대로 공부해 본 사람이라면 당연히 알고 있는 용어입니다. 또는 적어도 쓰레드 프로젝트 웹사이트에 한번이라도 방문해 본 적이 있다면 그 개념을 확인하기 위해 저한테 질문같은 건 하지 않겠지요? NGPT니 NPTL이니 하는 게 무슨 얘긴지도 물론 모르시겠고요.

인용:
제가 쓴 글은 책에 있는거고 제가 그 책의 분류와 내용에 아주 잘 이해하고 공감을 하는지라 머가 잘못되었다고 해서 흥분할 일은 없습니다. 다만 님이 말도 안되는 모델을 내세우며 "이게 맞아"라고 하니 뭔가 좀 정신이 들겠끔 해드리고 싶더군요. 님이 먼저 쓴 글은 그냥 무시(비하한다는 뉘앙스는 없고 'ignore'임)하였으나 (답장 안달았음) 님이 제 글에 답장을 다니 그냥 무시하고 지나갈 수가 없었습니다. 잘못된 지식이 한참 배우는자들에게 전달되면 참으로 피곤한 세상이 되는지라 미연에 막을 필요가 있었습니다.

어줍잖게 책 한두번 읽어보고 쓰레드에 관해 마치 전문가인양 행세하지 마시기 바랍니다.

이전 글을 다시 읽어보니 SunOS가 multiplexed thread라는 철지난 얘기를 어디서 주워들어 써놓으셨군요. 솔라리스 2.6부터는 기존 LWP에다 스케줄러 액티베이션을 구현해서 쓰고 있는데, 이건 multiplexed thread입니까, kernel supported user-level thread입니까? 헷갈리시죠? 덧붙여 스케줄러 액티베이션이 어떤 원리로 어떻게 동작하는지 설명을 부탁드려도 될까요?

인용:
한 일(Porting,..)이 그다지 대단해 보이지도 않는데다, 전 님을 신뢰하지 않습니다. NetBSD에 대해 뭔가를 강조하지 말았으면 합니다. 솔직히 님을 보면 NetBSD까지 신뢰가 가지 않습니다.

초등생 수준의 논리적 사고력을 가진 분 같군요. "저는 님처럼 모르는 걸 아는 체 하는 사람을 보면 사람자체까지 신뢰가 가지 않습니다." :D

제가 그다지 대단하지 않은 일을 하는 동안 님은 쓰레드로 무슨 일을 해봤습니까? 한번도 프로그램을 짜본 적도 없으면서 이러쿵 저러쿵 떠드는 거 아닌지 의심스럽습니다.

인용:
계속 말안되는 말만해대면 무식함이 만천하에 드러나니 NetBSD를 위해서라도 자중하세요. 아님 진짜 제대로 설득을 보겠다고 하면, 해당 논리(M on N Model)에 대한 구체적인 논문 또는 자료를 하나라도 증거로서 제시해주든가.

하늘에 해가 떠있는 걸 증명하란 소리로 들리는군요. 님이 단 한번이라도 쓰레드 분야의 논문을 읽어본 적이 있다면 그런 걸 증거로 제시하란 요구는 못하실 텐데요. 혹시 Usenix라고 들어보셨습니까? Siteseer는요? 못들어 보셨으면 Google이라고 괜찮은 검색 엔진이 있는데 거기서 한번 검색해 보시기 바랍니다.

아니면 아무 쓰레드 프로젝트 사이트에 "어떤 멍청이가 M-on-N 모델이란 쓰레드가 있다고 우기는 데 나는 믿을 수가 없다. 그게 도대체 무슨 소리냐"고 질문을 던져보시는 것도 괜찮을 듯 합니다.

Re: 쓰레드 패키지 아키텍쳐

저녁을 먹느라 아랫 부분에 대한 지적을 빠뜨렸습니다.

Paladin 씀:
커널-레벨 쓰레드는 커널-레벨에서 돌아가는 쓰레드를 말하는거지만, 님의 논리대로 사용자 레벨에서 봐도("사용자레벨에서 본다"라는 이 말자체가 문제이기 하지만) 쓰레드 1개가 돌고 있다고 봐도 무관한지라 1 on 1이라고 우기면 못 봐줄건 없습니다.

LWP를 이용한 Multiplexing은 님의 논리와는 전혀 다르지만 M:N으로 넣어줄 수는 있습니다. 그러나 커널에서 보면 M개, 사용자 레벨에서 보면 N개가 아니라, LWP M개, Thread N개가 실재로 동시에 존재하는 겁니다.

그리고 안타깝게도 스케줄러 액티베이션은 M:N이 아니군요. 이것은 사용자-레벨의 스레드를 위해 커널에서 지원(가상 프로세스, 이전 글 참조)을 해주는 것이지 어떤 M:N 관계가 있지는 않습니다.

대단히 말도 안되는 주장을 정색을 하고 말씀하시기에, 바라시던 논문에서 한 부분 인용해서 보여드리겠습니다:

Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System, 2002 씀:
Since there are advantages and disadvantages of both the N:1 and 1:1 thread implementation models, it is natural to attempt to combine them to achieve a balance of the costs and benefits of each. These hybrids are collectively known as ``N:M'' systems, since they map some number N of application threads onto a (usually smaller) number M of kernel entities. They are also known as ``two-level'' thread systems, since there are two parties, the kernel and the user parts of the thread system, involved in thread operations and scheduling. There are quite a variety of different implementations of N:M thread systems, with different performance characteristics. N:M thread systems are more complicated than either of the other models, and can be more difficult to develop, debug, and use effectively. Both AIX and Solaris use N:M thread systems by default.

In a N:M thread system, a key question is how to manage the mapping of user threads to kernel entities. One possibility is to associate groups of threads with single kernel entities; this permits concurrency across groups but not within groups, reaching a balance between the concurrency of N:1 and 1:1 systems.

The scheduler activations model put forward by Anderson et al. is a way of managing the N:M mapping while maintaining as much concurrency as a 1:1 thread system.

스케줄러 액티베이션을 직접 구현한 사람이 스케줄러 액티베이션이 N:M 모델이라는데, 교과서만 한두번 읽어보고 쓰레드를 잘 안다고 자부하는 사람은 그게 아니라고 합니다. 더 망신당하실까봐 1-on-1 모델에 대한 인용은 생략하도록 하겠습니다.

혹시라도 논문에 kernel entities라고 되어 있지 kernel threads라고 되어 있지는 않지 않느냐는 생트집은 잡지 마시기 바랍니다. Solaris와 NetBSD에서는 kernel entity를 LWP라고 부르고, 리눅스, 윈도, FreeBSD에서는 thread라고 부르는 것 뿐입니다.

질문에 대한 답글은 아니지만,
바로 위의 글들이 눈살을 찌프리게 하기에 제3자의 입장에서 몇자적습니다.
토론이 아니라 논쟁이 된듯합니다. :(
제3자의 입장에서야 도움이 되는 글이 많으니 흥미로운 글들이지만,
두분이 서로 감정이 달아오른듯 싶군요. 후후....

다음글을 읽으시고 이성을 찾으시기 바랍니다.
http://arteeth.com/photo/semiology.htm

쓰레드 패키지 아키텍쳐

여전히 여전하군요.

"실력은 고사하고 기본적 예의부터 상당히 없으신 것 같군요. 첨보는 사람한테 하룻강아지 범무서운 줄 모른다고 무례한 말씀을 하실 때부터 알아봤어야 하는 건데, 좋습니다. 저도 님같은 분께는 별로 예의를 차리지 않겠습니다"

이전에는 예의를 차렸던가요? ㅎㅎㅎ (이제는 비웃음입니다)
엉터리 논리를 끝까지 밀어부치는 그 신념 높이 살만합니다만 그만큼 님의 한계는 다가오는 셈이겠죠.

싱글 쓰레드라...

이제와서 "프로세스가 단일 쓰레드이다"라는 주장같은거 하는거라면 뒷구멍으로 도망치지마라고 한방 날리겠습니다. 이런 뒷구멍을 미리 막지 못한 저의 불찰이겠지만. 프로세스와 쓰레드는 분명하게 다른겁니다. 이것도 이전 글에 미리 말하려고 했으나 설마 이렇게까지 나갈줄은 몰랐군요.

"1-on-1이니 M-on-N이니 하는 표현은 제가 만든 것도 아니고 쓰레드를 제대로 공부해 본 사람이라면 당연히 알고 있는 용어입니다. 또는 적어도 쓰레드 프로젝트 웹사이트에 한번이라도 방문해 본 적이 있다면 그 개념을 확인하기 위해 저한테 질문같은 건 하지 않겠지요? NGPT니 NPTL이니 하는 게 무슨 얘긴지도 물론 모르시겠고요."

솔직히 NGPL,NPTL같은 이따위 것들 모릅니다. NGPL,NPTL이 쓰레드 일반 이론을 대체할만큼 대단한건지는 제가 판단을 일단 유보하죠. 얼마나 대단한 것이길래 그것이 마치 모든 쓰레드의 일반 이론인양 주장하는건지는 좀 더 두고 보죠.

그러나 저는 님이 저런거 안다고 어디서 줏어 들어다고 까지 표현을 않겠습니다. 이 시점에서 님이 써둔 글을 다시금 한번 보세요.

"이전 글을 다시 읽어보니 SunOS가 multiplexed thread라는 철지난 얘기를 어디서 주워들어 써놓으셨군요. 솔라리스 2.6부터는 기존 LWP에다 스케줄러 액티베이션을 구현해서 쓰고 있는데, 이건 multiplexed thread입니까, kernel supported user-level thread입니까? 헷갈리시죠? 덧붙여 스케줄러 액티베이션이 어떤 원리로 어떻게 동작하는지 설명을 부탁드려도 될까요? "

철지난 얘기를 줏어 들어다라니 참으로 어이가 없군요. 제가 말한건 책의 내용을 요약했다고 분명히 말했습니다. 요약(summary)이 무슨 뜻인지 모르면, 국어 사전(요약) 또는 영어 사전(Summary) 보고 의미를 파악하기 바랍니다.

LWP에다 스케줄러 액티베이션을 썩어놓으면 먼가 라는 질문, 이런 허접한 질문 만드느라 애썼습니다. 답을 말해드리죠. kernel supported and multiplexed user-level thread입니다.

그리고 크게 4개로 나눈다고 하는 것은, 모든 패키지 아키텍쳐가 님의 M:N모델 분류처럼 어느 한곳에 속해야함을 강제하진 않습니다. 님의 논리라면 이런 질문할 법도 하기에 답변을 해드렸습니다. 참고로 원칙적으로 보면 Kernel-Level, User-Level Threads 두개고 나머지는 하이브리드 형태입니다.

"" 초등생 수준의 논리적 사고력을 가진 분 같군요. "저는 님처럼 모르는 걸 아는 체 하는 사람을 보면 사람자체까지 신뢰가 가지 않습니다." ""

ㅎㅎㅎ, 유치원생같은 수준 낮은 표현, 조금 수준 높은 제가 참죠.

"제가 그다지 대단하지 않은 일을 하는 동안 님은 쓰레드로 무슨 일을 해봤습니까? 한번도 프로그램을 짜본 적도 없으면서 이러쿵 저러쿵 떠드는 거 아닌지 의심스럽습니다."

이런 의심을 한다고한들 님에게 도움이 되는 것은 무엇인가요? 상대가 무너지면 그냥 자기 논리가 인정받는건가요? ㅎㅎㅎ, 그런 기대를 한다라는 것 자체가 자신의 한계를 간접 노출하는 셈입니다. 그리고 생각을 해보세요. 머리가 있다면. 제가 경험이나 지식이 없다면, Pham과 Garg의 책 내용을 요약하는게 가능했을까요? 전 리눅스를 비롯한 Open Source가 대중화되기전부터 윈도우에서 Socket + Multi-Thread 프로그래밍을 한 사람입니다. 기회만 된다면(경제적 문제만 해결되면) 그리고 원한다면 쓰레드 패키지를 Porting같은 짓꺼리 안하고 아예 이론대로 구현해보일 수도 있습니다.

"하늘에 해가 떠있는 걸 증명하란 소리로 들리는군요. 님이 단 한번이라도 쓰레드 분야의 논문을 읽어본 적이 있다면 그런 걸 증거로 제시하란 요구는 못하실 텐데요. 혹시 Usenix라고 들어보셨습니까? Siteseer는요? 못들어 보셨으면 Google이라고 괜찮은 검색 엔진이 있는데 거기서 한번 검색해 보시기 바랍니다."

님의 어슬픈 M:N model이 과연 님이 말하는 논리대로 존재하고 있는건지 의심스러워서 자료를 요구한겁니다. 이 요구가 "하늘의 해가 떠 있는걸 증명하라"는 것처럼 황당한 요구였나요? 이런 단순한 요구가 그렇게 어려운 요구였다니, 결과적으로 님의 그 한계가 또 간접 노출되는 셈이죠.
그리고 어디서 뭘보고 저러는건지를 제가 찾아서 파악해야하나요? 귀찮아서 그렇겐 못하겠소. 제가 자료를 찾지 못해서 그런 요구를 했다라니 계속 이런 유치한 발상을 하면 할수록 님의 신뢰도는 점점 추락하는셈입니다.

"저녁을 먹느라 아랫 부분에 대한 지적을 빠뜨렸습니다."

'밥 빨리 쳐먹어'라고 말하고 싶은 욕구가 가득합니다만, 이런 말은 쓰지 않은걸로 하자면 또 하룻강아지에 대한 속담처럼 오바을 할테죠. 그러지 말기 바랍니다. 이 표현에 대해서만 이미 글은 썻지만 사과도 첨부합니다. 사과를 첨부한다라는 말은 "병주고 약주냐 이 자식아"라고 말하면 기꺼이 받아 들이겠다라는 말입니다.

"대단히 말도 안되는 주장을 정색을 하고 말씀하시기에, 바라시던 논문에서 한 부분 인용해서 보여드리겠습니다"

이젠 아예 책의 내용 자체를 부정하기 시작하는군요. Pham과 Garg의 책을 신뢰하지 않는 모양인데. 그렇다면 탄넨바움의 책은 어떤가요?

"스케줄러 액티베이션을 직접 구현한 사람이 스케줄러 액티베이션이 N:M 모델이라는데, 교과서만 한두번 읽어보고 쓰레드를 잘 안다고 자부하는 사람은 그게 아니라고 합니다. 더 망신당하실까봐 1-on-1 모델에 대한 인용은 생략하도록 하겠습니다. "

이제서야 그 정체가 드러났군요.

"Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System"이라.

스케줄러 액티베이션은 1991년 앤더슨에 의해 창조되어져습니다. 논문을 알려드리죠.

"Scheduler Activations : Effective Kernel Support for User-Level Management of Parallelism" - Anderson,Bershad,Lazowska,Levy, 1991, proceedings of the 13th Symposium on Operating Principles, pp 95-109

저도 이 논문을 직접 읽지는 않았습니다. 허나 Pham과 Garg의 책에 있는 스케줄러 액티베이션은 이 논문을 보고 참조/요약한겁니다. 그리고 윌리엄도 원자자의 내용을 확뒤집었다면 굳이 스케줄러 액티베이션이라고 말할 필요가 없었겠죠.

그리고 인용한 것도 짚고 넘어가야겠죠.

""Since there are advantages and disadvantages of both the N:1 and 1:1 thread implementation models, it is natural to attempt to combine them to achieve a balance of the costs and benefits of each. These hybrids are collectively known as ``N:M'' systems, since they map some number N of application threads onto a (usually smaller) number M of kernel entities. They are also known as ``two-level'' thread systems, since there are two parties, the kernel and the user parts of the thread system, involved in thread operations and scheduling. There are quite a variety of different implementations of N:M thread systems, with different performance characteristics. N:M thread systems are more complicated than either of the other models, and can be more difficult to develop, debug, and use effectively. Both AIX and Solaris use N:M thread systems by default. In a N:M thread system, a key question is how to manage the mapping of user threads to kernel entities. One possibility is to associate groups of threads with single kernel entities; this permits concurrency across groups but not within groups, reaching a balance between the concurrency of N:1 and 1:1 systems.
The scheduler activations model put forward by Anderson et al. is a way of managing the N:M mapping while maintaining as much concurrency as a 1:1 thread system. ""

""혹시라도 논문에 kernel entities라고 되어 있지 kernel threads라고 되어 있지는 않지 않느냐는생트집은 잡지 마시기 바랍니다. Solaris와 NetBSD에서는 kernel entity를 LWP라고 부르고, 리눅스, 윈도, FreeBSD에서는 thread라고 부르는 것 뿐입니다"""

황당하군. 쭉읽어 보고나니, 문제가 바로 드러나는데, 그런데!! 마지막 단락에서, 명백히 드러난 자기 잘못을 인정하기는 커녕 '생트집 하지 말라'니. 궁지에 몰린 쥐처럼 보이는군요. 적반하장이란 고사성어와 방귀 뀐 놈이 도려 성낸다라는 속담도 떠오르고.

kernel entity에 LWP도 있다라고 하면 인정해주겠지만, kernel entity를 LWP라고 부른다니 이걸 말이라고 하는건지. 한술 더 떠서 다른 OS에서는 thread라고 부른다라니!!

ㅎㅎㅎ(허탈)

제3자를 위해서 좀 더 붙이죠.

위 글에 분명하게 드러난건 님이 이전에 말한, 바로 이때 써먹을려고 뒷구멍을 막아둔 것임, 커널에서 보면 M개 쓰레드, 사용자 레벨에서 보면 N개 쓰레드라는 것은 분명한 허구임이 밝혀졌습니다. 이런말 한적 없다고 생각되면 님이 쓴 글 다시 읽어 보기 바람.

N:1 and 1:1 thread implementation models의 정의는 없지만, N:M systems이라 불리는건 N개의 어플이케이션 쓰레드가 N개의 커널 앤티티로 매핑(Map)되에 이것들의 짬뽕(Hybrid)이다라고 하므로 N:1 and 1:1이 무엇인지는 유추됩니다.

User-Level이 N:1인 경우는 N개의 쓰레드가 동시에 접근할 수 있는 커널의 앤티티가 결국 하나라는것이지 커널이 보는 쓰레드가 하나라는 것이 아닙니다. 커널이 보는건 단 하나의 프로세스이며 단일 프로세스는 커널 자원(또는 앤티티)에 한번에 하나씩 차례로 접근(또는 호출)을 하죠. Kernel-Level이 1:1인 경우는 커널-레벨의 쓰레드는 각각 독립적으로 커널 앤티티에 접근하기에 쓰레드:커널앤티티가 1:1인 겁니다.

다시 정리하자면 커널 앤티티란 커널이 보는 쓰레드가 아니라 커널이 제공하는 함수나 공유 변수 또는 기타 자원을 의미하죠. 사실 이렇게 말하기도 머하고, 앤티티는 그냥 엔티티입니다.

자 오늘은 이쯤해두죠. 내일은 여전할런지 아님 좀 성숙해질런지 두고보겠습니다.

두분 글 잘 보고 있습니다..^^

위의 두분의 토론을 보고 많은 걸 배우네요..

두분께 감사드립니다.

본론은

팔라딘님 글이 너무 '직설적'이네요

처음보는 분한테 '하룻강아지 범 무서운 줄 모르는...'이라는 말은

너무 과격합니다.

조금 더 예의를 갖춰서 토론을 시작한다면 팔라딘님의

지식이 더욱 빛나겠죠? :D

Re: 쓰레드 패키지 아키텍쳐

이글을 읽고 계실 많은 분들께 험악하게 싸우는 모습을 보여드려 죄송하다는 말씀을 드리고 싶습니다. 그렇지만 이렇게 싸워가는 가운데 상대방을 이기기 위해 공부도 하고, 나름대로의 논리도 개발하다보면, 그리고 여러분은 중간 입장에서 누구 말이 옳고 그른지 판단하시다 보면, 결과적으로 모두에게 그리 나쁠 것은 없다는 생각이 들기도 합니다. 그러니 "이 사람들 뭐 이렇게 쓸데없이 핏대 올려가며 싸우냐"고 생각하시기보다, 그냥 편한 연속극이나 코메디( :!: ) 프로 시청하시듯이 감상( :?: )해 주시면 감사하겠습니다.

Paladin 씀:
싱글 쓰레드라...

이제와서 "프로세스가 단일 쓰레드이다"라는 주장같은거 하는거라면 뒷구멍으로 도망치지마라고 한방 날리겠습니다. 이런 뒷구멍을 미리 막지 못한 저의 불찰이겠지만. 프로세스와 쓰레드는 분명하게 다른겁니다. 이것도 이전 글에 미리 말하려고 했으나 설마 이렇게까지 나갈줄은 몰랐군요.

쯧쯧, 그 한방은 님에게나 날리시죠. 책 한권 달랑 읽어놓고 아는 척 하려니 그런 말이 있는 것조차도 모르셨군요. 제가 Google이라고 괜찮은 검색 엔진있다고 말씀 안드렸던가요? 거기서 single thread multi thread라고 검색하면 몇개나 나오는지 개수부터 세어보시죠. 남들은 그런 표현을 쓰레드 개념이 나올 때부터 줄곧 써왔는데 자기가 배운 책에 안나오면 전부 엉터리라니...쩝, 할말을 잃게 만듭니다.

인용:
솔직히 NGPL,NPTL같은 이따위 것들 모릅니다. NGPL,NPTL이 쓰레드 일반 이론을 대체할만큼 대단한건지는 제가 판단을 일단 유보하죠. 얼마나 대단한 것이길래 그것이 마치 모든 쓰레드의 일반 이론인양 주장하는건지는 좀 더 두고 보죠.

허! 딱 걸렸습니다. 님이 "쓰맹"이란 사실을요. 리눅스나 오픈 소스 시스템 프로그래밍 쪽에선 NGPT와 NPTL이 요즘 대단한 관심사인데 그걸 모른다구요? 지금 우리가 이렇게 싸우고 있는 걸 구경하고 계신 분들도 모두 잘 알고 계시는 것인데 그걸 모른다고 말씀하시나요? 차라리 리눅스란 운영체제를 들어본 적이 없다고 말씀하시지 그래요? 모르면 "이따위것들"로 치부해 버릴 수 있는 용기에 감탄이 절로 나옵니다.

인용:
철지난 얘기를 줏어 들어다라니 참으로 어이가 없군요. 제가 말한건 책의 내용을 요약했다고 분명히 말했습니다. 요약(summary)이 무슨 뜻인지 모르면, 국어 사전(요약) 또는 영어 사전(Summary) 보고 의미를 파악하기 바랍니다.

옛날 책을 보고 철지난 얘기를 하시길래 지적한 겁니다. 혹시나 해서 아마존에서 찾아봤더니 그책은 "Multithreaded Programming with Windows NT" (1995년 12월 출간)이나 "Multithreaded Programming with Win32" (1998년 11월) 둘중 하나겠더군요. 그런데 정말 황당하기 짝이 없는 건, 스케줄러 액티베이션은 7장에서 142페이지와 143페이지에 걸쳐 달랑 한 페이지로 요약이 되어 있다는 점이었습니다. 결국 한페이지 읽어 놓고 스케줄러 액티베이션이 어쩌고 저쩌고하는 전문가 행세를 하셨던 것이었습니다. 전문가가 된다는 게 이렇게 간단하고 쉬웠던 것인가요? 뜨아~ :oops:

인용:
LWP에다 스케줄러 액티베이션을 썩어놓으면 먼가 라는 질문, 이런 허접한 질문 만드느라 애썼습니다. 답을 말해드리죠. kernel supported and multiplexed user-level thread입니다.

하하하, 지구상의 어느 책에도 없는 괴상망칙한 표현을 멋대로 만들어내시는군요. 말씀하신대로라면 쓰레드 모델이 4개가 아니라 다섯 개 아닙니까? 그러면 FreeBSD의 커널 스케줄러 엔티티는 kernel supported and multiplexed user-level thread with system-level contention added 모델이라고 하면 맞나요? 제가 잘 몰라서 그런 것이니 설명 부탁드립니다.

인용:
그리고 크게 4개로 나눈다고 하는 것은, 모든 패키지 아키텍쳐가 님의 M:N모델 분류처럼 어느 한곳에 속해야함을 강제하진 않습니다. 님의 논리라면 이런 질문할 법도 하기에 답변을 해드렸습니다. 참고로 원칙적으로 보면 Kernel-Level, User-Level Threads 두개고 나머지는 하이브리드 형태입니다.

허, 제가 3개라고 그랬더니 아니라고 해놓고 나머지는 하이브리드 형태라니 무슨 말씀이세요?

인용:
ㅎㅎㅎ, 유치원생같은 수준 낮은 표현, 조금 수준 높은 제가 참죠.

참아주셔서 감사합니다. :D 그런데 위에 보면 한방 날리고 싶은, 밑에 보면 쳐먹어, 뭐 이런 표현을 적어 놓으셨던데 그런 건 수준높은 분들이 사용하는 표현인가보죠? 죄송합니다. 저는 수준이 낮아서 그런 표현은 도저히 머리에 떠오르질 않았습니다. 게다가 처음보는 사람한테 무모하다, 하룻강아지다, 알고나 이런 소릴 등등의 표현을 사용하시는 걸 보니 담력과 문학적 감각 또한 탁월하십니다.

인용:
이런 의심을 한다고한들 님에게 도움이 되는 것은 무엇인가요? 상대가 무너지면 그냥 자기 논리가 인정받는건가요? ㅎㅎㅎ, 그런 기대를 한다라는 것 자체가 자신의 한계를 간접 노출하는 셈입니다. 그리고 생각을 해보세요. 머리가 있다면. 제가 경험이나 지식이 없다면, Pham과 Garg의 책 내용을 요약하는게 가능했을까요? 전 리눅스를 비롯한 Open Source가 대중화되기전부터 윈도우에서 Socket + Multi-Thread 프로그래밍을 한 사람입니다. 기회만 된다면(경제적 문제만 해결되면) 그리고 원한다면 쓰레드 패키지를 Porting같은 짓꺼리 안하고 아예 이론대로 구현해보일 수도 있습니다.

저를 신뢰하지 않는다고 먼저 말씀하신 쪽은 누구였는지 모르겠군요(저는 NetBSD와 Wine 두 개의 예를 들었는데 Wine은 쏙 빼고 NetBSD만 싸잡아 매도한 이유가 뭔지도 궁금해지네요). 그렇게 신뢰하지 않아서 님에게 도움된 것은 무엇이었나요? 경험이나 지식이 그렇게 풍부한 분이 1:1, M:N이 억지라고 하고, NGPT, NPTL, single threaded를 들어본 적이 없다고 지금 말씀하시나요? 그리고 신빙성에 지극히 의심이 가는 주장은 삼가시기 바랍니다. 리눅스를 비롯한 오픈 소스가 대중화되기 전이면 1993,4년일 텐데, 윈도 3.1에서 멀티 쓰레드 프로그래밍을 해보셨나요? 아니면, 제 기억에 1995,6년에조차도 멀티 쓰레드 프로그램이 윈도상에 그렇게 많지 않았던 걸로 기억하는데, 그때 벌써 멀티 쓰레드 프로그래밍을 하신 분이 국내에 계셨다니 놀라움을 금할 수 없군요. :o 아, 그리고 무슨 "기회가 된다면" 같은 하나마나 한 말씀도 안하셔도 됩니다. 저도 기회만 된다면, 경제적 문제만 해결된다면, 윈도보다, 리눅스보다 1억의 1조배는 훌륭한 OS를 만들려고 계획중입니다. 다른 모든 분들도 시간만 나면, 경제적 문제만 해결되면 제가 만들 프로그램보다 곱의 제곱으로 엄청나게 훌륭한 프로그램들을 만들려고 구상하고 계실 겁니다.

인용:
님의 어슬픈 M:N model이 과연 님이 말하는 논리대로 존재하고 있는건지 의심스러워서 자료를 요구한겁니다. 이 요구가 "하늘의 해가 떠 있는걸 증명하라"는 것처럼 황당한 요구였나요? 이런 단순한 요구가 그렇게 어려운 요구였다니, 결과적으로 님의 그 한계가 또 간접 노출되는 셈이죠.
그리고 어디서 뭘보고 저러는건지를 제가 찾아서 파악해야하나요? 귀찮아서 그렇겐 못하겠소. 제가 자료를 찾지 못해서 그런 요구를 했다라니 계속 이런 유치한 발상을 하면 할수록 님의 신뢰도는 점점 추락하는셈입니다.

M:N 모델이 제 논리대로 존재하고 있냐고요? 제가 인용한 논문 안읽으셨습니까? 하늘의 해는 하느님의 논리대로 존재하고 있습니까?

인용:
'밥 빨리 쳐먹어'라고 말하고 싶은 욕구가 가득합니다만, 이런 말은 쓰지 않은걸로 하자면 또 하룻강아지에 대한 속담처럼 오바을 할테죠. 그러지 말기 바랍니다. 이 표현에 대해서만 이미 글은 썻지만 사과도 첨부합니다. 사과를 첨부한다라는 말은 "병주고 약주냐 이 자식아"라고 말하면 기꺼이 받아 들이겠다라는 말입니다.

허, 수준이 좀 높은 분들은 다른 사람 밥먹을 때 "쳐먹어"라고 말하고 싶은 욕구를 느끼기도 하나 봅니다. 뭐 좋습니다. 수준이 높아서 그런건데 제가 뭐라 할 수 있나요.

인용:
이젠 아예 책의 내용 자체를 부정하기 시작하는군요. Pham과 Garg의 책을 신뢰하지 않는 모양인데. 그렇다면 탄넨바움의 책은 어떤가요?

책을 엉뚱하게 요약해서 오해하셔 놓고는 제가 책 내용 자체를 부정한다니요.

인용:
이제서야 그 정체가 드러났군요.

"Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System"이라.

스케줄러 액티베이션은 1991년 앤더슨에 의해 창조되어져습니다. 논문을 알려드리죠.

"Scheduler Activations : Effective Kernel Support for User-Level Management of Parallelism" - Anderson,Bershad,Lazowska,Levy, 1991, proceedings of the 13th Symposium on Operating Principles, pp 95-109

꽈당~. 마이크로소프트웨어 2002년 6월호에서 제가 쓴 글의 참고문헌에 무슨 논문이 나열되어 있는지 확인해 보시죠. 물론 마이크로소프트웨어란 잡지가 있는지조차도 모르고 계실 가능성이 높다고 예상합니다만.

인용:
저도 이 논문을 직접 읽지는 않았습니다. 허나 Pham과 Garg의 책에 있는 스케줄러 액티베이션은 이 논문을 보고 참조/요약한겁니다. 그리고 윌리엄도 원자자의 내용을 확뒤집었다면 굳이 스케줄러 액티베이션이라고 말할 필요가 없었겠죠.

하하하, 제가 그 논문에 있는 내용 물어볼까 겁이 나셨나봐요. 미리 안읽었다고 선수를 치시는 걸 보면요(퀴즈: 업콜이 뭡니까?). 그런데 스케줄러 액티베이션에 관해 논문 한편도 제대로 안읽고 이렇다 저렇다 논평을 하십니까. "내용을 확 뒤집었다면"이란 뜬금없는 가정은 어떻게 설정하셨는지도 설명해 주시면 감사하겠습니다.

인용:
황당하군. 쭉읽어 보고나니, 문제가 바로 드러나는데, 그런데!! 마지막 단락에서, 명백히 드러난 자기 잘못을 인정하기는 커녕 '생트집 하지 말라'니. 궁지에 몰린 쥐처럼 보이는군요. 적반하장이란 고사성어와 방귀 뀐 놈이 도려 성낸다라는 속담도 떠오르고.

자기 잘못은 절대 인정안하시는군요. 분명히 스케줄러 액티베이션은 M:N 모델이 아니라고 주장하시지 않았습니까? "The scheduler activations model put forward by Anderson et al. is a way of managing the N:M mapping while maintaining as much concurrency as a 1:1 thread system."의 어느 부분이 해석하기에 곤란합니까?

인용:
kernel entity에 LWP도 있다라고 하면 인정해주겠지만, kernel entity를 LWP라고 부른다니 이걸 말이라고 하는건지. 한술 더 떠서 다른 OS에서는 thread라고 부른다라니!!

같은 논문에서 한 부분 더 인용해 드리죠:

Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System, 2002 씀:
Most of the systems where scheduler activations has been implemented to date (Taos, Mach, and the Mach-inspired Digital Unix) have kernels where a user process is built out of a set of kernel entities that each represent an execution context. This fits well with scheduler activations, where a single process can have several running and blocked execution contexts.

...

Therefore, the first stage in implementing scheduler activations was separating process context from execution context in NetBSD. This was a slow but largely mechanical undertaking. The parts of the classic BSD struct proc that related to execution context were relocated to a new structure, struct lwp (LWP for ``light-weight process'', following Solaris and others).

각각의 커널 엔티티는 실행 컨텍스트를 나타내고, 실행 컨텍스트는 커널 내에서 struct lwp로 표현됩니다. 이제 무슨 뜻인지 아시겠습니까. 커널 엔티티가 무슨 대단하고 설명하기 힘든 존재로 오해하고 계셔서 드리는 말씀입니다.

인용:
위 글에 분명하게 드러난건 님이 이전에 말한, 바로 이때 써먹을려고 뒷구멍을 막아둔 것임, 커널에서 보면 M개 쓰레드, 사용자 레벨에서 보면 N개 쓰레드라는 것은 분명한 허구임이 밝혀졌습니다. 이런말 한적 없다고 생각되면 님이 쓴 글 다시 읽어 보기 바람.

아예 하지도 않은 말을 조작까지 하시는군요. 제가 쓴 그대로 다시 인용할까요: "커널쪽에서 보면 하나의 쓰레드고 사용자쪽에서 보면 여러 개의 쓰레드인 모델을 1-on-N(1대N) 모델이라고 합니다. 사용자레벨 쓰레드를 이렇게 표현하죠. 두번째로, 커널쪽과 사용자쪽의 쓰레드가 1대1로 대응하는 모델을
1-on-1 이라고 합니다. 그리고 마지막으로 커널 쓰레드의 개수와 사용자 쓰레드의 개수가 1대1 대응관계를 이루지 않는 모델을 M-on-N 모델이라고 합니다. LWP나 스케줄러 액티베이션이 모두 M대N 모델입니다." 제 글의 어느 부분에 "커널에서 보면 M개 쓰레드, 사용자 레벨에서 보면 N개 쓰레드"라고 되어 있나요? 1-on-N 모델의 설명과 혼동이 되셨다면 더 명확하게 정정하겠습니다. "커널 쪽의 하나의 쓰레드와 사용자쪽의 여러 개의 쓰레드가 대응하는 모델을 1-on-N라 한다"

인용:
N:1 and 1:1 thread implementation models의 정의는 없지만, N:M systems이라 불리는건 N개의 어플이케이션 쓰레드가 N개의 커널 앤티티로 매핑(Map)되에 이것들의 짬뽕(Hybrid)이다라고 하므로 N:1 and 1:1이 무엇인지는 유추됩니다.

하늘의 해를 유추를 통해 발견하다니! :shock:

인용:
User-Level이 N:1인 경우는 N개의 쓰레드가 동시에 접근할 수 있는 커널의 앤티티가 결국 하나라는것이지 커널이 보는 쓰레드가 하나라는 것이 아닙니다. 커널이 보는건 단 하나의 프로세스이며 단일 프로세스는 커널 자원(또는 앤티티)에 한번에 하나씩 차례로 접근(또는 호출)을 하죠. Kernel-Level이 1:1인 경우는 커널-레벨의 쓰레드는 각각 독립적으로 커널 앤티티에 접근하기에 쓰레드:커널앤티티가 1:1인 겁니다.

1:1의 의미가 커널-레벨 쓰레드와 커널 엔티티가 1대1로 접근하기 때문이라니, 기절초풍할 이론입니다! 아까전엔

인용:
1 on 1이라고 우기면 못 봐줄건 없습니다

이라고 1:1 모델을 부정하시더니 그새 1:1의 의미를 유추를 통해 깨달으셨나봐요? 제가 학교 다닐 때 찍기만 하면 틀리는 친구가 있었는데, 님은 하는 유추마다 어찌 답을 비켜가십니까? :D

인용:
자 오늘은 이쯤해두죠. 내일은 여전할런지 아님 좀 성숙해질런지 두고보겠습니다.

수준높으신 분이 내일은 또 어떤 수준 높은 문구로 저를 공격하실지 기대가 됩니다.

이 이야기가 더이상 늘어나면 서로 감정이 상하는 것은 물론이고, 제3자들도 내용을 파악하기가 더욱 어려워질 테니 앞으로 Paladin님과 방준영님 모두 한 개씩의 글을 더 올리실 수 있는 기회를 드리고 이 주제에 대해 누구도 더이상 답글을 달 수 없도록 설정을 바꾸도록 하겠습니다. :?

보통 이 정도 단계에서 계속해서 댓글을 달게 되면 실질적인 정보교환은 갈수록 어려워지고 처음의 논의와는 완전히 다른 방향으로 가게 되는 것을 많이 보아 왔지요.... :roll:

두분께서 올려주신 지식은 분명 많은 사람들에게 좋은 정보가 될 수 있을 것입니다. 두분께서는 서로 흥분을 가라앉히시고 부디 좋은 모습으로 이번 주제를 마무리하실 수 있기를 바랍니다. :mrgreen:

감사합니다....

쓰레드 패키지 아키텍쳐

현재 코 길이가 피노키오를 능가하는지라 코를 납작하게 하는 방법으로는 도저히 안되겠군요. 톱으로 잘라내도 다시 자라날테니 이젠 그냥 콧구멍 파는 모습이나 지켜보죠. 머 드러날건 다 드러났으니 손으로 코를 아둥바둥 가린다고 한들 그 코가 감춰지는 것도 아닐테니. 그런데 그런 우스꽝스러운 모습을 즐거워하는 독자들도 있을테니 최대한 가려보기 바랍니다.

책은 MultiThread Programming with win32-Prentice Hall,1999입니다. 한권의 출판된 책은 인터넷상에서 떠도는 막연한 자료와는 그 신뢰성의 정도가 다르죠. 머 사실 신뢰성도 책나름이겠지만 이 책을 읽어 보면 알겠지만 운영체제 책에서 언급하지 않는 구체적인 부분이 많이 들어 있죠.

그런데 이걸 철지난 책이라 하니 기가 차는군요. 이 말은 내용도 구식이라는 말같은데 정말 어이없음.

그리고 single threaded (끝의 -ed가 중요, 놓칠수 없는 부분임)라고 해놓고선, 혀차면서 발뺌하는 것 좀 보게나! 정말 기가 차는군. 아예 소설가로 전업하는게 나을듯하군요.

그리고 난 Google보단 yahoo.com 쓴다네. yahoo.com도 나올만한 자료 다 나오니 참 쓸데없는 의심같은건 하지 말기 바람. 유치원생도 아니고 이게 뭐야 대체. 독자를 위해 이젠 수준 좀 높입시다.

NGPT,NPTL 오래 써먹는군. "커널 앤티티"의 개념도 주먹구구식으로 이해한 사람이 저걸 어디까지 이해하고나 있을런지. 그 분야에 새끼 발가락이나 하나 걸치고 있다고 누가 좀 좋게 봐줄거라고 생각했나? 어림없는 일일세. 코난의 엄지 발가락이라면 모를까.

앤더슨의 "스케줄러 액티베이션" 논문을 구해서 직접 보니, 구구절절한 상세 매커니즘은 구현하는 사람에게나 필요한 것이지 그것의 전체적인 방법론은 책에서와 같이 한페이지로 충분해보이는군요.

"결국 한페이지 읽어 놓고 스케줄러 액티베이션이 어쩌고 저쩌고하는 전문가 행세를 하셨던 것이었습니다. 전문가가 된다는 게 이렇게 간단하고 쉬웠던 것인가요"

정말이지 꼴갑하는군요. 다시 생각해보니 소설가보단 국회의원이 더 나을 것 같군요.

"그러면 FreeBSD의 커널 스케줄러 엔티티는 kernel supported and multiplexed user-level thread with system-level contention added 모델이라고 하면 맞나요?"

글쎄요. 님의 의식 구조가 상식적이라면야 한번 고려해볼 수도 있겠으나 그렇지 않은 것 같아서리. 어떻든 님 맘대로 생각하세요. 그리고 내가 지어낸 그 말, 그걸 왜 그렇게 말했는지 다시금 생각해보는게 어때요? 누가 자기 비꼬는데, 그것도 이해 못하고, 토를 다는 꼴이 애처롭습니다. 님의 수준이 1차원보다는 높을줄 알았는데 그것도 아니군요.

"허, 제가 3개라고 그랬더니 아니라고 해놓고 나머지는 하이브리드 형태라니 무슨 말씀이세요? "

님을 국회 의원으로 보내고 나서 여의도를 날려버리고 싶군요. 3개, 4개같은게 중요한거였나요? 참나.

커널과 유저 레벨 쓰레드가 있고, 나머지는 하이브리드로 섞어 놓은거고, 그리고 그 하이브리드중 대표적인거 2개를 합쳐서 크게는 4가지인 것입니다. 누군가가 이미 만들어 놓은 두가지의 대표적인 하이브리드를 능가하는 뭔가를 만들면 그 결과 "크게는 5가지"가 되는 것이죠. 이런 말이 님의 의식속으로 침투가 될런지 의문이지만, 님의 의식구조에 담긴 M:N 모델로 보면 무엇이든간에 3개속에 담길 수 밖에 없다는걸 알기에 설득은 생략하죠.

그리고 오해할가봐 말하는건데, M:N 모델이 아니라고 해도 커널 앤티티와 쓰레드간의 바인딩 구조는 오래전부터 알고 있었으니 참고하세요. 커널 앤티티를 "커널에서 보는 쓰레드"로 만들어 사람을 오해의 늪으로 끌어 들이고도 자신의 잘못을 인정하지 않기는 커녕 상대를 깔아 뭉게는데 여념없는 그 집요함에 눌릴 제가 아니지만 독자를 위해 추가했습니다.

"M:N 모델이 제 논리대로 존재하고 있냐고요? 제가 인용한 논문 안읽으셨습니까? 하늘의 해는 하느님의 논리대로 존재하고 있습니까?"

전 M:N 모델을 이해합니다. 그러나 님의 말에 있는 M:N 모델에 대한 언급은 앞에 글에서 지적했듯이 오류가 있습니다. 편안하게 살려면 오류를 인정하고 오해를 풀게 하는게 상호지간 좋을겁니다.

"허, 수준이 좀 높은 분들은 다른 사람 밥먹을 때 "쳐먹어"라고 말하고 싶은 욕구를 느끼기도 하나 봅니다. 뭐 좋습니다. 수준이 높아서 그런건데 제가 뭐라 할 수 있나요."

수준이 높아 봐야 초등학생이죠. 님은 유치원생입니다. 독자분들은 최소 중학생이상일테니 누가 누구를 걱정을 상황은 못됩니다. 상황 파악 좀 하기 바랍니다.

"책을 엉뚱하게 요약해서 오해하셔 놓고는 제가 책 내용 자체를 부정한다니요."

어이가 없군. 코가 이젠 자기 코보다 길어졌겠군. 양심에 찔리지도 않나. 인간 아니 나무가 불쌍합니다. 이러다 인간못될 수도 있어요.

"마이크로소프트웨어 2002년 6월호에서 제가 쓴 글의 참고문헌에 무슨 논문이 나열되어 있는지 확인해 보시죠. 물론 마이크로소프트웨어란 잡지가 있는지조차도 모르고 계실 가능성이 높다고 예상합니다만. "

내가 10년전 초보일때 즐겨 보았던 잡지가 마이크로소프트웨어, 프로그래밍 세계인데, 이걸 들고서 "이것도 모르냐"라고 하니 참 우습군. 마이크로소프트웨어 별로 도움이 안되서 안본지도 몇년 되었는데 이런 잡지에 쓴 글이 머가 그리 대단하다고 이러는건지. 원참. 무료로 아니 돈을 준다해도 안봅니다.

"하하하, 제가 그 논문에 있는 내용 물어볼까 겁이 나셨나봐요. 미리 안읽었다고 선수를 치시는 걸 보면요(퀴즈: 업콜이 뭡니까?). 그런데 스케줄러 액티베이션에 관해 논문 한편도 제대로 안읽고 이렇다 저렇다 논평을 하십니까. "내용을 확 뒤집었다면"이란 뜬금없는 가정은 어떻게 설정하셨는지도 설명해 주시면 감사하겠습니다."

이놈보게. 물어 봐라 이 자식아, 버릇없는 새끼, 도저히 못참겠네. 참는 것도 한계가 있지.
퀴즈에 있는 upcall이란 유저-레벨에서는 커널 함수를 호출하지만 커널 레벨에서는 유저-레벨의 함수를 호출못하는거다. 이유는 유저 함수에서 다시 커널 함수 호출하면 순환 호출(resursion)에 의해 엉망이 될테니.
기껏 묻는다는게 이정도 수준밖에 안되나 이 자식아. 좀 더 어려운거 물어보지? 내가 진짜 모르는걸 하나만 물으면 내가 니 긴 코를 감춰주마.

"자기 잘못은 절대 인정안하시는군요. 분명히 스케줄러 액티베이션은 M:N 모델이 아니라고 주장하시지 않았습니까? "The scheduler activations model put forward by Anderson et al. is a way of managing the N:M mapping while maintaining as much concurrency as a 1:1 thread system."의 어느 부분이 해석하기에 곤란합니까? "

말도안되는 M:N model을 말해놓아, 그 기준대로 말할 수 밖에 없었는데, 이제 내 글만 틀렸다라고 하니 지나가는 똥개가 웃겠다 이 새끼야.

"Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System"에 대해서는 인용하지 마라. 이 새끼야. 이미 다 읽었다. 너의 그 허구와는 달리 잘 써놨더군.

"각각의 커널 엔티티는 실행 컨텍스트를 나타내고, 실행 컨텍스트는 커널 내에서 struct lwp로 표현됩니다. 이제 무슨 뜻인지 아시겠습니까. 커널 엔티티가 무슨 대단하고 설명하기 힘든 존재로 오해하고 계셔서 드리는 말씀입니다"

니 맘대로 생각하고 니 맘대로 찌걸이다 가거라.

"(전략)라고 되어 있나요? 1-on-N 모델의 설명과 혼동이 되셨다면 더 명확하게 정정하겠습니다. "커널 쪽의 하나의 쓰레드와 사용자쪽의 여러 개의 쓰레드가 대응하는 모델을 1-on-N라 한다"

진짜 핵심인 이 부분을, 구렁이 담넘어가듯, 마치 별거 아닌 것처럼 넘어 갈려는 이 교활함, 정말 진짜 상종을 못하겠다. 시팔 똥밟은 셈 치고, 난 그만할란다.

보통의 경우에는 글을 한번 더 읽어 보고, 오타 수정하고 좀 표현이 너무 지나치다 싶으면 편집을 하는데, 이제는 그럴 기분도 아니다. 그냥 난 이쯤해서 글을 접는다.

내가 이성을 잃고 반말했으니, 너도 반발해도 된다. 실컷 하고 가거라.

그리고 부탁하지만 담에 내 글에 절대 댓글 달지 말거라.

참, 그리고 독자 여러분들께는 죄송합니다. 그냥 좋은 부분만 기억해주세요.

위에 두분의 피터지는 thread에 대한 이야기를 보면서 thread에 대한 생각도 정리하고 나름대로 많이 생각해볼수 있었습니다.(으으윽 두통이)
개인적으로 보기에는 용어상의 차이점 때문에 그런것 같은데..
책마다 혹은 os마다 thread나 process혹은 task같은 용어들이 조금이 다르게 표현되는데요.
두분의 대화가 다른 thread를 공부하는 사람들에게는 도움이 될듯한대요
설명하는듯한 대화가 되면 좋을것 같아서 부탁드립니다.
두분다 잘 아시는것 같고 os나 책에서의 표현이 다른것 때문에 발생하는 문제인것 같습니다.
그다지 큰 문제는 아니라고 생각하는데..
부탁드립니다.

Re: 쓰레드 패키지 아키텍쳐

Paladin 씀:
현재 코 길이가 피노키오를 능가하는지라 코를 납작하게 하는 방법으로는 도저히 안되겠군요. 톱으로 잘라내도 다시 자라날테니 이젠 그냥 콧구멍 파는 모습이나 지켜보죠. 머 드러날건 다 드러났으니 손으로 코를 아둥바둥 가린다고 한들 그 코가 감춰지는 것도 아닐테니. 그런데 그런 우스꽝스러운 모습을 즐거워하는 독자들도 있을테니 최대한 가려보기 바랍니다.

관리자인 권순선님께서 딱 한번 더 글쓸 기회를 주신다고 했으니 저도 이번이 마지막입니다.

인용:
책은 MultiThread Programming with win32-Prentice Hall,1999입니다. 한권의 출판된 책은 인터넷상에서 떠도는 막연한 자료와는 그 신뢰성의 정도가 다르죠. 머 사실 신뢰성도 책나름이겠지만 이 책을 읽어 보면 알겠지만 운영체제 책에서 언급하지 않는 구체적인 부분이 많이 들어 있죠.

제 글의 어느 부분에 "인터넷상에서 떠도는 막연한 자료"가 인용되어 있던가요? 스케줄러 액티베이션을 한페이지로 요약한 책을 다시 머릿속으로 요약해서 허튼 주장을 반복하는 것은 님입니다.

인용:
그런데 이걸 철지난 책이라 하니 기가 차는군요. 이 말은 내용도 구식이라는 말같은데 정말 어이없음.

제가 쓴 글을 똑바로 읽으시죠. 어느 책이나 시간이 가면 내용중 일부가 더 이상 현실이 아니게 되는 것이 당연한 거 아닌가요. 1970년대 나온 프로그래밍 책을 읽고 "쓰레드가 뭐냐. 그런 건 세상에 없다"고 궤변을 늘어놓는 꼴입니다.

인용:
그리고 single threaded (끝의 -ed가 중요, 놓칠수 없는 부분임)라고 해놓고선, 혀차면서 발뺌하는 것 좀 보게나! 정말 기가 차는군. 아예 소설가로 전업하는게 나을듯하군요.

Uresh Vahalia의 Unix Internals란 책의 딱 한 문장만 인용하죠(이 책은 유닉스 운영체제를 다룬 최고의 책중 하나로 평가받고 있지만, 뭐 그것도 "이따위것들" 정도로 매도하신다면 할 말 없습니다):

Uresh Vahalia, Unix Internals, 1996 씀:
The traditional UNIX process has a single thread of control.

뭐, 이 책 보시면 아시겠지만 님이 펄쩍 뛰는 "single threaded process"같은 표현도 자주 나옵니다.

인용:
그리고 난 Google보단 yahoo.com 쓴다네. yahoo.com도 나올만한 자료 다 나오니 참 쓸데없는 의심같은건 하지 말기 바람. 유치원생도 아니고 이게 뭐야 대체. 독자를 위해 이젠 수준 좀 높입시다.

yahoo.com 검색 엔진이 Google이란 건 모르고 계셨겠죠. :roll: (하하하, 갑자기 난감해지세요?) 웹서핑을 하질 않으니 하기사 알 턱이 없으시겠지만. "난 Google보다 yahoo.com쓴다" -> 이달의 코메디상감입니다.

인용:
NGPT,NPTL 오래 써먹는군. "커널 앤티티"의 개념도 주먹구구식으로 이해한 사람이 저걸 어디까지 이해하고나 있을런지. 그 분야에 새끼 발가락이나 하나 걸치고 있다고 누가 좀 좋게 봐줄거라고 생각했나? 어림없는 일일세. 코난의 엄지 발가락이라면 모를까.

NGPT, NPTL 모르고 쓰레드를 논한다니, 공자 맹자 모르고 중국 철학 논하는 꼴이네요. 책 한권 내내 우려먹는 분은 누구였더라...:oops: 이번에는 Open Software Foundation에서 출간한 Mach 3 Kernel Principles에서 한문장 인용할게요:

Keith Loepere, Mach 3 Kernel Principles, 1992 씀:
The thread is a fairly light-weight entity.

하하하, 뜨끔하시죠. 뒤에 한두 문장 더 읽어 보면 님이 부정하시는 "single thread"란 표현도 바로 등장합니다.

인용:
앤더슨의 "스케줄러 액티베이션" 논문을 구해서 직접 보니, 구구절절한 상세 매커니즘은 구현하는 사람에게나 필요한 것이지 그것의 전체적인 방법론은 책에서와 같이 한페이지로 충분해보이는군요.

이제사 읽어보셨군요. 근데 저번 글에서 "저도 직접 읽어보진 않았다"고 하셨는데, 거기서 "도"는 누굴 뜻하는 겁니까? 님이 인용하신 책의 저자들이나 제가 인용한 논문의 저자(Nathan Williams), 그리고 저는 모두 그 논문을 읽어 봤는데 도대체 누가 "도"에 속하는 건지...?

인용:
글쎄요. 님의 의식 구조가 상식적이라면야 한번 고려해볼 수도 있겠으나 그렇지 않은 것 같아서리. 어떻든 님 맘대로 생각하세요. 그리고 내가 지어낸 그 말, 그걸 왜 그렇게 말했는지 다시금 생각해보는게 어때요? 누가 자기 비꼬는데, 그것도 이해 못하고, 토를 다는 꼴이 애처롭습니다. 님의 수준이 1차원보다는 높을줄 알았는데 그것도 아니군요.

제가 비꼴려고 한말이 님이 또 토를 다시는군요. "울트라 수퍼 하이퍼 메가톤 어쩌고 저쩌고..."가 생각나서 말씀드린 거였습니다. :P

인용:
커널과 유저 레벨 쓰레드가 있고, 나머지는 하이브리드로 섞어 놓은거고, 그리고 그 하이브리드중 대표적인거 2개를 합쳐서 크게는 4가지인 것입니다. 누군가가 이미 만들어 놓은 두가지의 대표적인 하이브리드를 능가하는 뭔가를 만들면 그 결과 "크게는 5가지"가 되는 것이죠. 이런 말이 님의 의식속으로 침투가 될런지 의문이지만, 님의 의식구조에 담긴 M:N 모델로 보면 무엇이든간에 3개속에 담길 수 밖에 없다는걸 알기에 설득은 생략하죠.

자기가 한말도 자기가 잊어버리십니까. 그대로 인용을 해드릴까요:

인용:
그리고 안타깝게도 스케줄러 액티베이션은 M:N이 아니군요. 이것은 사용자-레벨의 스레드를 위해 커널에서 지원(가상 프로세스, 이전 글 참조)을 해주는 것이지 어떤 M:N 관계가 있지는 않습니다.

누가 스케줄러 액티베이션이 M:N 모델이 아니라고 했더라...저도 기억이 잘 안나네요. :D 또 하나 더 하죠:

인용:
다만 님이 말도 안되는 모델을 내세우며 "이게 맞아"라고 하니 뭔가 ...

첨에는 1:N, 1:1, M:N 모델이 말도 안되는 모델이라고 하다가 이제 슬쩍 인정을 하시는군요.

인용:
그리고 오해할가봐 말하는건데, M:N 모델이 아니라고 해도 커널 앤티티와 쓰레드간의 바인딩 구조는 오래전부터 알고 있었으니 참고하세요. 커널 앤티티를 "커널에서 보는 쓰레드"로 만들어 사람을 오해의 늪으로 끌어 들이고도 자신의 잘못을 인정하지 않기는 커녕 상대를 깔아 뭉게는데 여념없는 그 집요함에 눌릴 제가 아니지만 독자를 위해 추가했습니다.

제가 위에서 인용한 Mach 3 책이나 한번 정독을 해보시면 entity를 어떤 의미로 쓰는지 쉽게 알 수 있으실텐데요. 별것도 아닌 용어를 무슨 대단한양 떠벌리시기는.

인용:
전 M:N 모델을 이해합니다. 그러나 님의 말에 있는 M:N 모델에 대한 언급은 앞에 글에서 지적했듯이 오류가 있습니다. 편안하게 살려면 오류를 인정하고 오해를 풀게 하는게 상호지간 좋을겁니다.

"말도 안되는 모델"이라면서요? 설명을 해달라면요? 금방 이해를 하셨나요?

인용:
수준이 높아 봐야 초등학생이죠. 님은 유치원생입니다. 독자분들은 최소 중학생이상일테니 누가 누구를 걱정을 상황은 못됩니다. 상황 파악 좀 하기 바랍니다.

제가 사람을 잘 봤군요. "초등생 수준의 논리적 사고력"을 가진 분이라는걸요. 지금 초등생에 카운터 펀치를 열심히 날리고 있는 중이니 저는 "중학생" 수준은 될 것 같습니다. :wink:

인용:
내가 10년전 초보일때 즐겨 보았던 잡지가 마이크로소프트웨어, 프로그래밍 세계인데, 이걸 들고서 "이것도 모르냐"라고 하니 참 우습군. 마이크로소프트웨어 별로 도움이 안되서 안본지도 몇년 되었는데 이런 잡지에 쓴 글이 머가 그리 대단하다고 이러는건지. 원참. 무료로 아니 돈을 준다해도 안봅니다.

"초보일 때 즐겨 보았던 잡지" -> 이달의 두번째 코메디상감입니다. 마소가 무슨 "PC 사랑"과 같은 종류의 책인줄 아시는군요. 한번도 안 읽어본 게 바로 들통났습니다.

인용:
이놈보게. 물어 봐라 이 자식아, 버릇없는 새끼, 도저히 못참겠네. 참는 것도 한계가 있지.
퀴즈에 있는 upcall이란 유저-레벨에서는 커널 함수를 호출하지만 커널 레벨에서는 유저-레벨의 함수를 호출못하는거다. 이유는 유저 함수에서 다시 커널 함수 호출하면 순환 호출(resursion)에 의해 엉망이 될테니.
기껏 묻는다는게 이정도 수준밖에 안되나 이 자식아. 좀 더 어려운거 물어보지? 내가 진짜 모르는걸 하나만 물으면 내가 니 긴 코를 감춰주마.

허라, 이제 아주 쌍욕을 대놓고 하시는군요. 욕설을 퍼부으실려면 글 첨부터 하시지 헷갈리게 존대말과 반말을 섞어 쓰고 그러십니까? 뭐 아무튼, 님의 답은 아주 정확히 틀렸습니다. 완전 거꾸로 얘길 하시는군요. Nathan Williams 논문에서 인용하죠:

Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System, 2002 씀:
Upcalls are the interface used by the scheduler activations system in the kernel to inform an application of a scheduling-related event.

이번에는 Anderson 논문에서 인용:

Anderson, T., et al., Scheduler Activations: Effective Kernel Support for the User-Level Management of Parallelism. 1991 씀:
When a program is started, the kernel creates a scheduler activation, assigns it to a processor, and upcalls into the application address space at a fixed entry point.

사용자 함수에서 커널 함수를 호출하는 걸 "시스템콜"이라고 합니다. 업콜은 커널 함수에서 사용자 함수를 호출하는 겁니다. 하하하, 제가 만든 함정에 딱 빠졌습니다(차라리 대답안했으면 망신 안당했을 텐데~). 정말 님은 "이 정도 수준"도 안되는군요. :roll:

인용:
말도안되는 M:N model을 말해놓아, 그 기준대로 말할 수 밖에 없었는데, 이제 내 글만 틀렸다라고 하니 지나가는 똥개가 웃겠다 이 새끼야.

어, 저는 M:N 모델에 대해 길게 설명도 안했는데, 어디서 말이 안되었습니까?

인용:
"Williams, N., An Implementation of Scheduler Activations on the NetBSD Operating System"에 대해서는 인용하지 마라. 이 새끼야. 이미 다 읽었다. 너의 그 허구와는 달리 잘 써놨더군.

논문 읽느라 주말에 놀지도 못하고 고생좀 하셨겠습니다.

인용:
니 맘대로 생각하고 니 맘대로 찌걸이다 가거라.

누가 그러더군요. 인터넷에서 헛소리로 욕하는 사람한테는 "반사!"라고 말해주면 된다고요. :lol:

인용:
진짜 핵심인 이 부분을, 구렁이 담넘어가듯, 마치 별거 아닌 것처럼 넘어 갈려는 이 교활함, 정말 진짜 상종을 못하겠다. 시팔 똥밟은 셈 치고, 난 그만할란다.

보통의 경우에는 글을 한번 더 읽어 보고, 오타 수정하고 좀 표현이 너무 지나치다 싶으면 편집을 하는데, 이제는 그럴 기분도 아니다. 그냥 난 이쯤해서 글을 접는다.

내가 이성을 잃고 반말했으니, 너도 반발해도 된다. 실컷 하고 가거라.

이성을 잃은 건 처음부터 알아봤습니다. 저는 아무리 흥분해도 그정도까지는 안된다고 생각을 하는데요. 근데 웃기는 건, 갑자기 홍길동전이 생각나는 건 왜일까요? 홍판서가 "길동아, 이제 호부호형 하거라. 실컷 하거라"하는데, 길동이가 "싫사옵니다. 제가 왜 호부호형을 하옵니까?" 이러면 홍판서가 얼마나 황당하겠습니까.

인용:
그리고 부탁하지만 담에 내 글에 절대 댓글 달지 말거라.

제 생각에 이 정도로 망신을 당했으면 Paladin이란 id로는 더이상 글 못 쓸 거 같습니다. 그러니 절대 댓글 달 수도 없겠지요. 그리고 쓴다해도 제가 댓글 달든 말든 무슨 상관입니까. 애시당초 제가 님의 잘못쓴 내용을 지적한 게 열이 받으셨죠?

음, 님이 쓴 내용에 마지막 한가지 더 망신을:

인용:
User-Level이 N:1인 경우는 N개의 쓰레드가 동시에 접근할 수 있는 커널의 앤티티가 결국 하나라는것이지 커널이 보는 쓰레드가 하나라는 것이 아닙니다. 커널이 보는건 단 하나의 프로세스이며 단일 프로세스는 커널 자원(또는 앤티티)에 한번에 하나씩 차례로 접근(또는 호출)을 하죠. Kernel-Level이 1:1인 경우는 커널-레벨의 쓰레드는 각각 독립적으로 커널 앤티티에 접근하기에 쓰레드:커널앤티티가 1:1인 겁니다.

같은 주제를 놓고 NPTL의 개발자인 Ulrich Drepper와 Ingo Molnar는 뭐라고 했을까요:

Drepper, U. and Molnar I., The new Native POSIC Threading Library, 2002 씀:
One valid possibilty is the 1-on-1 model of the old implementation where each user-level thread has an underlying kernel thread.

이제 1:1이 무슨 뜻인지 이해가 되십니까? 두 저자는 님만 빼고 남들은 전부 다 아는, 리눅스 쪽에선 세계 최고의 개발자들입니다.

인용:
참, 그리고 독자 여러분들께는 죄송합니다. 그냥 좋은 부분만 기억해주세요.

뭐, 맞는 내용이 있어야 좋은 부분을 기억하지 않겠습니까. :P 아, 한가지, 님덕분에 예전에 읽은 책과 논문들을 다시 꺼내어 검토해본 것이 이번 flamewar의 값진 소득이었습니다. 그점에 감사드립니다.

관전평(?)

팔라딘님은 이론(근거)보다 감정을 내세우시고,
방준영님은 논리적 근거와 이론으로 압도하는군요.

결과적으로, 방준영님에게 한표드립니다.
(팔라딘님의 글이 아무리 옳다하더라도(?) 감정을 앞세우면 좋은 인상을 못줄겁니다.)
좋은글 잘 읽었습니다.
저도 열심히 공부해야겠네요... :D

ps. 이글이 올려질라나...?

이번 논의를 마무리하면서....

관리자 권한을 이용하여 강제로 이번 논의를 중단시키게 된 점 대단히
유감스럽습니다만, 글이 계속될 경우 더욱 문제가 커질 수 있을 것이라
판단되어 어쩔 수 없이 답글을 달 수 없도록 설정을 바꾸었습니다.

서로 지식과 정보를 나눔에 있어 기본적인 네티켓은 꼭 지켜 주시기를
바랍니다. 아무리 넓고 깊은 지식이라 할지라도 상대방을 무시하거나
비하하는 태도나 욕설 등이 섞이게 되면 많은 사람들의 눈살을 찌푸리게
하고 인격을 의심하게 만듭니다.

인터넷의 익명성을 악용하여 기본적인 네티켓을 지키지 않는 사용자는
앞으로 상황에 따라 글쓰기 제한이나 강제 퇴출 등의 극단적인 조치를
취할 수도 있습니다. 물론 이런 일이 생기지 않도록 서로 존중하는 태도를
가지는 것이 가장 좋은 일이겠지요? :) 8) :wink:

블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

v1.1.1

POSIX 쓰레드로 멀티 쓰레드 프로그래밍하기

옮긴이: 차현진(terminus@kldp.org)
원 본: http://users.actcom.co.il/~choo/lupg/tutorials/multi-thread/multi-thread.html

차례

  1. 시작하기 전에(Before We Start)...
  2. 쓰레드가 뭔데 그걸 쓰죠?(What Is a Thread? Why Use Threads)
  3. 쓰레드 만들고 없애기(Creating And Destroying Threads)
  4. 뮤텍스로 쓰레드 동기화하기(Synchronizing Threads With Mutexes)
    1. 뮤텍스가 뭐죠?(What Is A Mutex?)
    2. 뮤텍스 만들고 초기화하기(Creating And Initializing A Mutex)
    3. 뮤텍스 걸고 풀기(Locking And Unlocking A Mutex)
    4. 뮤텍스 없애기(Destroying A Mutex)
    5. 뮤텍스 사용법 - 완전한 예제(Using A Mutex - A Complete Example)
    6. 굶어죽기와 데드락(Starvation And Deadlock Situations)
  5. 세련된 동기화 - 조건 변수(Refined Synchronization - Condition Variables)
    1. 조건 변수가 뭐죠?(What Is A Condition Variable?)
    2. 조건 변수 만들고 초기화하기(Creating And Initializing A Condition Variable)
    3. 조건 변수에 시그널 보내기(Signaling A Condition Variable)
    4. 조건 변수 기다리기(Waiting On A Condition Variable)
    5. 조건 변수 없애기(Destroying A Condition Variable)
    6. 실제 상황의 조건 변수(A Real Condition For A Condition Variable)
    7. 조건 변수 사용법 - 완전한 예제(Using A Condition Variable - A Complete Example)
  6. "Private" thread data - Thread-Specific Data
    1. Overview Of Thread-Specific Data Support
    2. Allocating Thread-Specific Data Block
    3. Accessing Thread-Specific Data
    4. Deleting Thread-Specific Data Block
    5. A Complete Example
  7. 쓰레드 취소와 끝내기(Thread Cancellation And Termination)
    1. 쓰레드 취소하기(Canceling A Thread)
    2. 쓰레드 취소 상태 설정하기(Setting Thread Cancellation State)
    3. 취소 위치(Cancellation Points)
    4. 쓰레드 청소 함수 설정하기(Setting Thread Cleanup Functions)
    5. 쓰레드 끝내기 동기화(Synchronizing On Threads Exiting)
    6. 쓰레드 떼어내기(Detaching A Thread)
    7. 쓰레드 취소 - 완전한 예제(Threads Cancellation - A Complete Example)
  8. 쓰레드를 이용한 사용자 인터페이스 프로그래밍(Using Threads For Responsive User Interface Programming)
    1. 사용자 인터페이스 - 완전한 예제(User Interaction - A Complete Example)
  9. 멀티 쓰레드 어플리케이션에서 비시스템 라이브러리 쓰기(Using 3rd-Party Libraries In A Multi-Threaded Application)
  10. 쓰레드를 지원하는 디버거 쓰기(Using A Threads-Aware Debugger)

시작하기 전에(Before We Start)...

이 튜토리얼은 여러분에게 POSIX 쓰레드(pthread)를 이용한 멀티 쓰레드 프로그램에 익숙해지게 하고 쓰레드의 특징들이 실제 프로그램에서 어떻게 쓰이는지 보여줄 것입니다. 라이브러리가 정의해 놓은 여러가지 툴들을 설명하고 그것들을 어떻게 쓰는지, 또한 프로그래밍 문제를 해결하기위해 실제로 어떻게 적용시키는지를 보여줄 것입니다. 이 글을 읽으려면 병렬 프로그래밍(혹은 멀티 프로세스) 개념을 알고 있어야 합니다. 안 그러면 개념 잡기가 약간 힘들 것입니다. 각 튜토리얼은 "직렬" 프로그래밍에만 익숙한 독자들을 위해 이론적 배경 지식과 용어들을 설명 하면서 시작할 것입니다.

독자들이 X나 모티프 같은 비동기적인 프로그래밍 환경에 익숙하다고 가정을 하고 진행하겠습니다. 이런 환경에 익숙하다면 멀티 쓰레드 프로그래밍 개념을 이해하기 쉽습니다.

POSIX 쓰레드를 말할 때 항상 나오는 질문은 "어떤 POSIX 쓰레드 표준안을 써야 할 것인가?"입니다. 쓰레드 표준은 지난 몇 년간 계속 수정중이기 때문에 서로 다른 함수들, 서로 다른 디폴트 값, 서로 다른 뉘앙스의 여러 구현들이 있습니다. 본 튜토리얼은 리눅스 시스템의 커널 레벨 LinuxThreads 라이브러리 0.5 버전을 사용했기 때문에 다른 시스템, 다른 버전의 pthread를 쓰는 프로그래머들은 문제 발생시 해당 시스템의 매뉴얼을 참고해야 할 것입니다. 몇몇 예제들은 블러킹 시스템 콜을 쓰기 때문에 유저 레벨 쓰레드 라이브러리에서는 동작하지 않을 것입니다 (더 많은 정보를 보려면 우리 웹 사이트의 parallel programming theory tutorial을 참고하세요).
앞에서 얘기 했듯이 여기 나오는 예제들은 리눅스 이외의 다른 시스템에서도 동작하도록 노력을 했습니다(솔라리스 2.5).


쓰레드가 뭔데 그걸 쓰죠?(What Is a Thread? Why Use Threads)

쓰레드는 프로세스와 비슷합니다. 자신의 스택을 가지고 주어진 코드를 실행합니다. 하지만 진짜 프로세스와는 다르게 메모리를 다른 쓰레드와 공유합니다(프로세스는 자신만의 메모리 공간을 가지고 있습니다). 쓰레드 그룹은 한 프로세스 안에서 실행되는 모든 쓰레드를 나타내고, 메모리를 공유하기 때문에 전역 변수와 힙 메모리, 파일 디스크립터 등등을 공유합니다. 또한 같은 쓰레드 그룹의 쓰레드들은 병렬적으로 실행됩니다(즉, 시간을 잘라서 사용을 하는데 프로세서가 여러개라면 진짜 병렬로 동작합니다).

보통의 순차적인 프로그램 대신 쓰레드 그룹을 사용하면 몇 가지 일을 동시에 할 수 있는 장점이 있습니다. 따라서 어떤 이벤트에 대해 즉각적으로 반응을 할 수 있습니다 (예를 들면, 한 쓰레드는 사용자 인터페이스를 처리하고 다른 쓰레드는 데이타베이스 쿼리를 처리한다고 하면, 아주 엄청난 양의 쿼리가 들어와 바쁜 경우에도 사용자 입력에 대해 반응하고 처리할 수가 있습니다).

프로세스 그룹대신 쓰레드 그룹을 사용했을 때의 장점으로는 쓰레드간 컨택스트 스위치(context switching)가 프로세스간 컨택스트 스위치보다 훨씬 빠르다는 것입니다(컨택스트 스위칭이란 현재 돌고 있는 쓰레드나 프로세스에서 다른 쓰레드나 프로세스로 옮겨 가는 것을 말합니다). 또한, 보통 두 쓰레드간 통신을 두 프로세스간 통신보다 빠르고 쉽게 구현 할 수 있습니다.

다른 한 편으로는 한 그룹안의 모든 쓰레드들은 같은 메모리 영역을 사용하기 때문에 한 쓰레드가 메모리를 잘 못 건드리면 다른 쓰레드들에 영향이 미칠 수 있습니다. 프로세스에서는 운영체제가 프로세스를 다른 프로세스로부터 보호해 주기 때문에 쓰레드같은 영향은 없습니다. 프로세스의 다른 장점으로, 서로 다른 프로세스는 서로 다른 시스템(머신)에서 각각 돌 수 있다는 것입니다. 쓰레드는 보통 한 시스템에서 돌아야 합니다.


쓰레드 만들고 없애기(Creating And Destroying Threads)

멀티 쓰레드 프로그램이 실행을 시작하면 main()을 실행시키는 하나의 쓰레드만이 존재하게 됩니다. 이 완전한 쓰레드는 자신의 쓰레드 ID를 갖습니다. 새 쓰레드를 만들려면 pthread_create() 함수를 써야 됩니다. 어떻게 쓰는지 보시죠.


 #include <stdio.h> /* 표준 I/O 루틴 */ #include <pthread.h> /* pthread 함수와 데이타 스트럭쳐 */ /* 새 쓰레드가 실행시킬 함수 */ void* do_loop(void* data) { int i; int i; /* 숫자를 찍을 카운터 */ int j; /* 지연용 카운터 */ int me = *((int*)data); /* 쓰레드 구분 숫자 */ for (i=0; i<10; i++) { for (j=0; j<500000; j++) /* 지연 루프 */ ; printf("'%d' - Got '%d'\n", me, i); } /* 쓰레드 없애기 */ pthread_exit(NULL); } /* 보통의 C 프로그램처럼 main에서 시작합니다. */ int main(int argc, char* argv[]) { int thr_id; /* 새 쓰레드용 쓰레드 ID */ pthread_t p_thread; /* 쓰레드 구조체 */ int a = 1; /* 1번 쓰레드 구분 숫자 */ int b = 2; /* 2번 쓰레드 구분 숫자 */ /* 'do_loop()를 실행시킬 새 쓰레드 만들기 */ thr_id = pthread_create(&p_thread, NULL, do_loop, (void*)&a); /* main()함수에서도 'do_loop()' 실행시키기 */ do_loop((void*)&b); /* NOT REACHED */ return 0; } 

위 프로그램에서 몇 가지를 살펴보겠습니다.

  1. 메인 프로그램 자체도 쓰레드이기 때문에 do_loop()는 자신이 새로 실행시킨 쓰레드가 실행시킨 do_loop()와 병렬로 동작합니다.
  2. pthread_create()는 4개의 파라미터를 받습니다. 첫 번째는 쓰레드에 대한 정보를 제공하기 위해서 쓰입니다. 두 번째는 새 쓰레드에 속성을 주기 위해서 쓰이는데 우리는 NULL 포인터를 넘겨 줘서 기본값을 쓰게 했습니다. 세 번째 파라미터는 어떤 함수에서 쓰레드가 시작할 것인지를 알려주는 것이고 네 번째는 그 함수로 넘겨줄 아규먼트를 나타냅니다. 여기서 'void*'로 캐스팅 한 것은 이것이 비록 ANSI-C 문법에서는 불필요하지만 좀 더 명확하게 하기 위해서 쓰인 것입니다.
  3. 지연 루프는 병렬로 실행되는 쓰레드를 확실히 보여주기 위해서 쓰였습니다. CPU가 너무 빨라서 한 쓰레드가 모두 출력된 다음 다른 쓰레드의 출력이 나온다면 지연값을 증가시키기 바랍니다.
  4. pthread_exit()는 현재 쓰레드를 종료 시키고 자신이 갖고 있던 자신만의 쓰레드 리소스들을 놓아 줍니다. 쓰레드의 첫 함수 마지막에서 꼭 이 함수를 불러야 할 필요는 없습니다. 그 함수에서 리턴을 하게 되면 자동으로 종료가 됩니다. 쓰레드 중간에서 쓰레드를 종료하고 싶은 경우가 생길 때, 유용하게 쓰일 수 있습니다.

멀티 쓰레드 프로그램을 gcc로 컴파일 하려면 pthread 라이브러리 를 링크시켜줘야 합니다. 이미 여러분의 시스템에 이 라이브러리가 설치되어 있다고 가정하고 어떻게 컴파일 하는지를 보여 드리겠습니다.

gcc pthread_create.c -o pthread_create -lpthread

앞으로 나올 몇몇 프로그램들은 제대로 컴파일 하기 위해서 '-D_GNU_SOURCE' 를 줘서 컴파일 해야 할지도 모릅니다. 주의하세요.

이 프로그램의 소스 코드는 pthread_create.c를 보세요.


뮤텍스로 쓰레드 동기화하기

여러개의 쓰레드를 동시에 돌릴 때 발생하는 기본적인 문제점 중의 하나는 같은 메모리 영역을 쓰기 때문에 "서로의 상태에 신경 쓰도록" 하는 것입니다. 그래서 여기서는 두 개의 쓰레드가 동일한 데이타 구조에 접근할 때 생기는 문제점을 살펴 보도록 하겠습니다.

예를 들어서, 두 쓰레드가 두 변수를 업데이트 하려고 하는 상황을 생각해 봅시다. 한 쓰레드는 두 변수를 0으로 세트하려고 하고, 다른 쓰레드는 두 변수를 1로 세트 하려고 합니다. 만약에 두 쓰레드가 동시에 이 일을 하려고 한다면 한 변수는 1로, 다른 한 변수는 0으로 세트된 상황이 생길 수도 있습니다. 이런 일이 생기는 이유는 첫번째 쓰레드가 첫번째 변수를 0으로 만들고 나서 바로 컨택스트 스위치(context switching-이제 이게 뭔지 아시죠?)가 일어나고, 두번째 쓰레드가 두 변수를 1로 세트를 한 다음 다시 첫번째 쓰레드가 동작을 하면 두 번째 변수만을 0으로 만들기 때문에 결과적으로 첫 번째 변수는 1로, 두 번째 변수는 0으로 됩니다.


뮤텍스(mutex)가 뭐죠?

이 문제를 해결하기 위해서 pthread 라이브러리가 제공하는 기본 메카니즘을 뮤텍스라 부릅니다. 뮤텍스는 다음 세가지를 보장해주는 잠금 장치입니다. (역주: mutex - MUTual EXclusion - 상호 배타성)

  1. 원자성(Atomicity) - 뮤텍스를 걸었을 경우 다른 쓰레드가 동시에 뮤텍스가 걸린 영역으로 들어오지 못하게 보장해주는 원자적 동작입니다.
  2. 유일성(Singularity) - 한 쓰레드가 뮤텍스를 걸었을 경우 자신이 풀기 전에는 다른 쓰레드가 다시 뮤텍스를 걸지 못하게 해 줍니다.
  3. Non-Busy Wait - A라는 쓰레드가 이미 뮤텍스가 걸린 B 쓰레드를 걸려고 한다면 A 쓰레드는 B 쓰레드가 뮤텍스를 풀 때까지 서스펜드(suspend)됩니다 (CPU 리소스를 전혀 사용하지 않습니다). B가 뮤텍스를 풀면 A는 깨어나고 자신이 뮤텍스를 걸고 실행을 계속해 나갑니다.

이 세가지에서 볼 수 있듯이 어떻게 뮤텍스가 변수(혹은 코드의 임계 부분)에 대해서 배타적 접근을 확실하게 해 주는지 알 수 있습니다. 앞에서 설명했던 두 변수를 업데이트 해주는 가상 코드를 살펴 보죠. 다음은 첫 번째 쓰레드입니다.

'X1' 뮤텍스를 잠근다. 첫번째 변수를 '0'으로 세팅. 두번째 변수를 '0'으로 세팅. 'X1' 뮤텍스를 푼다. 



두번째 쓰레드는 이렇게 되겠죠.

'X1' 뮤텍스를 잠근다. 첫번째 변수를 '1'로 세팅. 두번째 변수를 '1'로 세팅. 'X1' 뮤텍스를 푼다. 



두 쓰레드가 같은 뮤텍스를 쓰고 동시에 돌았다고 하면 두 변수 모두 '0'으로 세트되어 있던지 '1'로 세트되어 있을 겁니다. 프로그래머가 주의할 일이 조금 있습니다. 만약에 세번째 쓰레드가 코드의 다른 부분에서 'X1' 뮤텍스 없이 이 두 변수에 접근을 한다면 역시나 변수 내용이 뒤죽박죽 될 가능성이 있습니다. 따라서 이 변수에 접근하는 모든 코드들을 조그만 함수로 만들어 놓고 이 변수들에 접근 할 때는 이 함수만 쓰도록 해야 합니다.


뮤텍스 만들고 초기화하기

뮤텍스를 만들려면 먼저 pthread_mutex_t 형의 변수를 선언하고 초기화 해야 합니다. 가장 간단한 방법은 PTHREAD_MUTEX_INITIALIZER 상수를 할당하는 것입니다. 따라서 다음같은 코드를 쓰면 되겠습니다.

 pthread_mutex_t a_mutex = PTHREAD_MUTEX_INITIALIZER; 



주의 할 점이 하나 있는데, 이런 형태의 초기화는 '빠른 뮤텍스(fast mutex)'라는 뮤텍스를 만들어 줍니다. 무슨 뜻이냐면, 만약에 쓰레드가 뮤텍스를 잠근 뒤에 또, 그 뮤텍스를 잠그려고 하면, 그냥 멈춰버릴 것입니다. - 데드락(deadlock)이 걸린다는 뜻입니다.

'재귀적 뮤텍스(recursive mutex)'란 다른 형태도 있는데 한 번 잠근 뒤에도 몇 번이고 더 잠글 수 있게 해주는 뮤텍스입니다. 이 뮤텍스는 위에서 말한 데드락 상황이 안 걸리게 해 줍니다(하지만 이 뮤텍스를 풀려고 하는 다른 쓰레드는 멈출 것입니다). 걸었던 뮤텍스를 풀 때, 걸었던 만큼 풀지 않는 한 뮤텍스가 계속 걸려 있을 겁니다. 이 방법은 현대적인 문잠금 장치에서 문을 잠글 때는 시계 방향으로 두 번 돌리고, 풀 때는 반시계 방향으로 두 번 돌리는 것과 비슷합니다. 이런 뮤텍스를 만들려면 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP를 할당해 주면 됩니다.


뮤텍스 걸고 풀기(Locking And Unlocking A Mutex)

뮤텍스를 걸때는 pthread_mutex_lock() 함수를 씁니다. 뮤텍스를 걸려고 하는데, 이미 다른 쓰레드가 그 뮤텍스를 걸어놨다면 자신의 쓰레드를 멈추게 합니다. 이렇게 멈췄을 경우에는 뮤텍스를 걸었던 프로세스가 뮤텍스를 풀면 이 함수는 다시 뮤텍스를 걸고 리턴을 합니다. 미리 초기화 했다고 가정하고 어떻게 뮤텍스를 거는지 보여 드리죠.

 int rc = pthread_mutex_lock(&a_mutex); if (rc) { /* 에러 발생 */ perror("pthread_mutex_lock"); pthread_exit(NULL); } /* 뮤텍스가 걸렸습니다. 필요한 일을 하세요. */ . . 



 

쓰레드는 자신이 할 일(변수나 데이타 구조의 값을 바꾼다거나 파일을 처리하는등)을 하고 나면 다음처럼 pthread_mutex_unlock() 함수를 써서 뮤텍스를 풀어 줘야 합니다.

 rc = pthread_mutex_unlock(&a_mutex); if (rc) { perror("pthread_mutex_unlock"); pthread_exit(NULL); } 

뮤텍스 없애기(Destroying A Mutex)

뮤텍스로 할 일을 다 했다면 이젠 없앨 차례입니다. 할 일을 다 했다는 얘기는 어떤 쓰레드도 그 뮤텍스가 필요없어졌다는 뜻입니다. 만약에 한 쓰레드만 뮤텍스로 할 일을 끝마쳤다면 이 때는 없애면 안 됩니다. 다른 쓰레드가 그 뮤텍스를 쓸지도 모르기 때문입니다. 모든 쓰레드가 확실히 뮤텍스를 쓸 일이 없다면 마지막 쓰레드가 pthread_mutex_destroy() 함수로 그 뮤텍스를 없앨 수 있습니다.

 rc = pthread_mutex_destroy(&a_mutex); 



이 함수를 부르고 나면 a_mutex 변수는 다시 초기화 되지 않는 한 더 이상 뮤텍스로 쓰일 수가 없습니다. 따라서 만약에 한 쓰레드가 너무 일찍 뮤텍스를 없앴을 경우에, 다른 쓰레드에서 잠그거나 풀려고 한다면 잠그고 푸는 함수는 EINVAL 에러 코드를 만나게 됩니다.


뮤텍스 사용법 - 완전한 예제(Using A Mutex - A Complete Example)

뮤텍스의 탄생부터 죽음까지 모두 알아봤기 때문에 이제는 예제를 살펴보겠습니다. 이 예제는 영광스러운 "종업원상"을 타기 위해 다투는 두 종업원을 시뮬레이션합니다. 빠르게 시뮬레이션하기 위해서 3개의 쓰레드를 쓰겠습니다. 하나는 Danny를 "종업원상"에 올리고 두번째 쓰레드는 Moshe를 올립니다. 세번째 쓰레드는 "종업원상"의 내용이 일치하는 지를 보여줍니다(즉, 정확하게 한 종업원의 데이타가 들어있음).
두 개의 프로그램이 있는데 하나는 뮤텍스를 쓰는 것이고 다른 하나는 쓰지 않는 것입니다. 둘 다 해보고 차이점을 알아본 다음, 멀티 쓰레드 환경에서 뮤텍스가 꼭 필요한 이유를 마음으로 느껴 보세요.

이 프로그램들은 파일 형태로 제공됩니다. 뮤텍스를 쓰는 것은 employee-with-mutex.c이고, 뮤텍스를 안 쓰는 것은 employee-without-mutex.c입니다. 소스에 있는 주석을 잘 읽어서 어떻게 동작하는지에 대해서 더 잘 이해하시기 바랍니다.


굶어죽기와 데드락 상황(Starvation And Deadlock Situations)

다시 기억을 되살려 보죠. pthread_mutex_lock()는 이미 잠겨 있는 뮤텍스에 대해서는 알 수 없는 시간 동안 멈춰 있을 수 있습니다. 만약에 그 잠김이 영원하다면 우리의 불쌍한 쓰레드는 "굶어(starved)" 죽습니다. 리소스를 얻으려 하지만 영원히 얻지 못하게 되는 것입니다. 이런 굶어 죽기( starvation)가 발생하지 않도록 하는 것은 프로그래머에게 달려 있습니다. pthread 라이브러리는 어떤 도움도 줄 수가 없습니다.

그렇지만, pthread 라이브러리는 "데드락(deadlock)"은 해결 할 수도 있습니다. 데드락이란 모두 같은 상태인 몇몇 쓰레드가 다른 쓰레드가 갖고 있는 리소스를 기다리는 상황입니다.(A deadlock is a situation in which a set of threads are all waiting for resources taken by other threads, all in the same set.) 당연히 모든 쓰레드가 뮤텍스를 기다리면서 멈춰있다면 아무도 다시 돌 수는 없을 것입니다. pthread 라이브러리는 이런 상황을 추적하다가 마지막 쓰레드가 pthread_mutex_lock()를 부르면 실패를 리턴하면서 EDEADLK 에러를 발생시킵니다. 프로그래머는 이런 값을 확인해서 데드락을 피할 방법을 찾아야 합니다.


세련된 동기화 - 조건 변수(Refined Synchronization - Condition Variables)

지금까지 살펴본 뮤텍스는 리소스에 대한 배타적 접근이라는 간단한 동기화를 제공합니다만, 가끔은 진짜 동기화가 필요할 경우가 있습니다.

  • 서버에서, 한 쓰레드는 클라이언트의 요청을 읽어들이고 그 요청을 해석해서 여러 쓰레드에게 처리를 넘깁니다. 이 처리 쓰레드들은 처리할 데이타가 생길 경우에 그 사실을 알아야 할 필요가 있습니다. 그렇지 않다면 CPU 시간을 쓰지 않으면서 기다려야 합니다.
  • GUI(Graphical User Interface) 어플리케이션에서 한 쓰레드는 사용자 입력을 읽어 들이고 한 쓰레드는 그래픽 출력을 담당하며, 한 쓰레드는 서버에 요청을 보내고 그 응답을 처리합니다. 서버쪽을 담당하는 쓰레드는 서버에서 응답이 왔을 때 그래픽을 담당하는 쓰레드에게 알려줄 수가 있어야 합니다. 그래야 사용자에게 즉시 보여줄 수 있기 때문입니다. 사용자 입력 담당 쓰레드는 예를 들면 서버 담당 쓰레드가 아주 긴 동작중이더라도 사용자가 그것을 취소 시킬 수 있게 해주는 상황처럼 사용자의 요청에 항상 빠르게 응답해야 할 필요가 있습니다.

이 상황들은 모두, 쓰레드는 서로 어떤 사건에 대해서 상대방에게 통보할 수 있는 능력이 필요합니다. 이것이 바로 조건 변수가 탄생한 이유입니다.


조건 변수가 뭐죠?(What Is A Condition Variable?)

조건 변수는 어떤 일이 발생할 때까지 CPU 사이클을 낭비하지 않고 기다릴 수 있도록 해 주는 메카니즘입니다. 몇개의 쓰레드가 조건 변수를 기다리고 있고, 다른 쓰레드가 그 조건 변수에 대해서 시그널을 날려주면(사건을 통지) 기다리던 쓰레드중의 하나가 깨어나서 그 사건에 대해 반응을 하게 됩니다. 또한 그 조건 변수를 기다리고 있던 모든 쓰레드를 깨울 수 있게 브로드캐스트 할 수 있는 방법도 있습니다.

주의할 점은 조건 변수는 잠금을 지원하지 않는다는 것입니다. 따라서 조건 변수에 접근을 하려면 뮤텍스와 같이 사용을 해야 합니다.


조건 변수 만들고 초기화하기(Creating And Initializing A Condition Variable)

조건 변수를 만들려면 pthread_cond_t 형의 변수를 선언하고 알맞게 초기화 시켜줘야 합니다. 초기화는 간단하게 PTHREAD_COND_INITIALIZER 라는 매크로를 쓰던지, pthread_cond_init() 함수를 쓰면 됩니다. 매크로를 쓰는 예제를 살펴 보겠습니다.

pthread_cond_t got_request = PTHREAD_COND_INITIALIZER;

'got_request'라는 조건 변수를 선언하고 초기화 합니다.

주의사항: PTHREAD_COND_INITIALIZER는 실제로 구조체이기 때문에 조건 변수가 선언 될 때에만 쓰일 수 있습니다. 실행 시간에 초기화를 해야 한다면 pthread_cond_init()함수를 쓰기 바랍니다.


조건 변수 시그널 날리기(Signaling A Condition Variable)

조건 변수에 시그널을 날리는 방법은 두 가지가 있습니다. 하나는 pthread_cond_signal() 함수를 부르는 것이고(이 변수를 기다리고 있는 하나의 쓰레드만을 깨울 때), 또 하나는 pthread_cond_broadcast() 함수를 부르는 것입니다(이 변수를 기다리고 있는 모든 쓰레드를 깨울 때). 'got_request'가 적당히 초기화 됐다고 가정하고 예제를 살펴보도록 하죠.

int rc = pthread_cond_signal(&got_request);

혹은 브로드캐스트 함수를 써서,

int rc = pthread_cond_broadcast(&got_request);

두 함수 모두 성공했을 때는 'rc'를 0으로, 실패했을 때는 0이 아닌 값으로 세팅합니다. 실패 했을 경우에는 리턴값은 에러 이유를 나타냅니다(파라미터가 조건 변수가 아닐 때는 EINVAL를, 시스템 메모리가 부족할 때는 ENOMEM를 나타냅니다).

주의 사항: 시그널이 성공했다고 해서 어떤 쓰레드가 깨어났다는 뜻은 아닙니다. 그 조건 변수를 기다리던 쓰레드가 하나도 없었다면 아무일도 아닌 것이죠(즉, 시그널을 잃어버리는 것입니다).
그리고 시그널을 저장해놨다가 쓸 수도 없습니다. 만약에 시그널 함수가 리턴한 다음에 어떤 쓰레드가 그 조건 변수를 기다리기 시작한다면 그 쓰레드는 다른 시그널이 발생해야 깨어날 수 있습니다.


조건 변수 기다리기(Waiting On A Condition Variable)

어떤 쓰레드가 조건 변수에 시그널을 날리길 다른 쓰레드가 기다리려고 한다면 다음 두 함수 중에 한 함수를 쓰면 됩니다. pthread_cond_wait(), pthread_cond_timedwait(). 각 함수는 조건 변수와 뮤텍스(기다리기 전에 뮤텍스를 걸지도 모르기 때문에)를 넘겨 받아서 뮤텍스를 푼 다음에 조건 변수에 시그널이 들어올 때까지 잠들어 버립니다. 앞에서 살펴 봤던 pthread_cond_signal()에 의해서 시그널이 발생해, 깨어 나게 된다면 뮤텍스는 자동으로 다시 잠기고 리턴하게 됩니다.

두 함수가 다른 점은 pthread_cond_timedwait()에 기다릴 시간을 알려준다는 것인데 ETIMEDOUT의 에러값을 갖고 리턴을 해서 조건 변수가 시그널을 받은 것이 아니라 시간이 지나서 리턴했다는 것을 알려준다는 것입니다. pthread_cond_wait() 는 시그널을 받기 전에는 영원히 기다릴 것입니다.

두 함수를 어떻게 쓰는지 보여드리죠. 'got_request'는 적당한 조건 변수로 초기화 됐고 역시 'request_mutex'도 적당한 뮤텍스로 초기화 됐다고 가정합니다. 먼저 pthread_cond_wait() 함수를 봅시다.

 /* 뮤텍스를 먼저 걸고 */ int rc = pthread_mutex_lock(&a_mutex); if (rc) { /* 에러 났음 */ perror("pthread_mutex_lock"); pthread_exit(NULL); } /* 이제 뮤텍스가 걸렸고, 조건 변수를 기다린다. */ /* pthread_cond_wait이 실행되는 동안 뮤텍스는 풀립니다. */ rc = pthread_cond_wait(&got_request, &request_mutex); if (rc == 0) { /* 조건 변수가 시그널을 받아서 깨어났습니다. */ /* pthread_cond_wait()가 뮤텍스를 다시 걸어 줍니다. */ /* 할 일을 하세요... */ . } /* 끝으로 뮤텍스를 풀어 줍시다. */ pthread_mutex_unlock(&request_mutex); 



다음은 pthread_cond_timedwait() 함수를 쓰는 예제입니다.

 #include <sys/time.h> /* struct timeval 정의 */ #include <unistd.h> /* gettimeofday() 선언 */ struct timeval now; /* 기다리기 시작하는 시각 */ struct timespec timeout; /* 대기 함수에서 쓸 타임아웃값 */ int done; /* 다 기다렸나요? */ /* 뮤텍스를 먼저 걸고 */ int rc = pthread_mutex_lock(&a_mutex); if (rc) { /* 에러 났음 */ perror("pthread_mutex_lock"); pthread_exit(NULL); } /* 이제 뮤텍스가 걸렸음. */ /* 지금 시각을 얻는다. */ gettimeofday(&now); /* 타임아웃값을 세팅 */ timeout.tv_sec = now.tv_sec + 5 timeout.tv_nsec = now.tv_usec * 1000; /* timeval은 마이크로(micro)초를 씁니다. */ /* timespec은 나노(nano)초를 씁니다. */ /* 1 나노초 = 1000 마이크로초 */ /* 조건 변수를 기다림 */ /* 유닉스 시그널이 타임아웃 전에 대기 상태를 멈추게 할 수 있기 때문에 루프를 써서 피하겠습니다. */ done = 0; while (!done) { /* pthread_cond_timedwait()은 함수 시작부분에서 뮤텍스를 푼다는 것을 기억하세요. */ rc = pthread_cond_timedwait(&got_request, &request_mutex, &timeout); switch(rc) { case 0: /* 조건 변수가 시그널을 받아서 깨어 났음 */ /* pthread_cond_timedwait가 뮤텍스를 다시 걸어줍니다. */ /* 할 일을 하시고... */ . . done = 0; break; case ETIMEDOUT: /* 시간이 다 됐네요 */ done = 0; break; default: /* 에러가 났습니다.(즉, 유닉스 시그널을 받았습니다.) */ break; /* swithc문을 빠져나가지만 다시 while 루프를 돕니다. */ } } /* 자, 끝으로 뮤텍스를 풀어 줍시다. */ pthread_mutex_unlock(&request_mutex); 



보는바와 같이 타임아웃을 쓰는 버전이 더 복잡합니다. 따라서 필요할 때마다 코드를 만들지 말고 래퍼 함수등을 쓰는게 훨씬 좋을 것입니다.

주의사항: 두 개 이상의 쓰레드가 기다리고 있는 조건 변수가 시그널을 아주 많이 받는다고 할 때, 기다리던 쓰레드 중의 하나는 영원히 깨어 나지 못 할 수도 있습니다. 조건 변수가 시그널을 받았을 때 기다리던 쓰레드중 어떤 쓰레드가 깨어날 지에 대해서 알 수가 없기 때문입니다. 방금 깨어난 쓰레드가 대기 상태로 다시 들어가자마자 시그널이 다시 발생해 그 쓰레드가 다시 깨어나는 식의 동작이 계속 될 수 있기 때문입니다. 이럴 경우에 계속 깨어나지 못하는 쓰레드를 가르켜 "굶어죽었다(starvation)"라고 부릅니다. 이렇게 원치 않는 동작이 일어날 가능성이 있는 상황을 피하는 것은 전적으로 프로그래머의 책임입니다. 하지만 앞에서 봤던 서버 예제에서는 요청이 아주 늦게 들어오고, 서비스 응답을 처리할 쓰레드는 많을 것이기 때문에 아주 바람직한 상황입니다. 즉, 이 경우에는 요청이 발생하자마자 바로바로 처리될 것이기 때문입니다.

주의사항 2: 뮤텍스가 pthread_cond_broadcast로 브로드캐스트를 받았을 때, 그 뮤텍스를 기다리던 모든 쓰레드가 동시에 실행되는것은 아닙니다. 기다리던 각각은 자신의 대기 함수가 리턴하기 전에 뮤텍스를 다시 걸려고 시도를 하기 때문에 하나씩 실행이 됩니다. 즉, 뮤텍스를 걸고, 자기 할 일을 하고, 뮤텍스를 풀고하는 식으로 차례차례 실행이 됩니다.


조건 변수 없애기(Destroying A Condition Variable)

조건 변수를 다 썼다면 없애야겠죠. 이래야 조건 변수가 갖고 있던 시스템 리소스를 반환할테니까요. pthread_cond_destroy()로 이 일을 합니다. 제대로 동작하려면 이 조건 변수를 기다리는 쓰레드가 하나도 없어야 합니다. 사용법을 보여드릴텐데, 역시 'got_request'가 이미 조건 변수로 초기화 되어 있었다고 가정합니다.

 int rc = pthread_cond_destroy(&got_request); if (rc == EBUSY) { /* 이 조건 변수를 기다리는 쓰레드가 있군요. */ /* 잘 처리하세요... */ . . } 



어떤 쓰레드가 여전히 조건 변수를 기다리고 있다면, 상황에 따라 다르겠지만, 이 조건 변수의 사용에 어떤 허점이 있었을 수도 있고 적당한 쓰레드 종료 코드가 빠졌을 수도 있습니다. 최소한 디버깅 단계에서는 이 상황을 프로그래머에게 알려주는게 좋습니다. 아무 것도 아닐 수도 있고 아주 중대한 결함일 수도 있으니까요.


실제 상황에서의 조건 변수(A Real Condition For A Condition Variable)

조건 변수에 대해서 하나 짚고 가야겠습니다. 이것과 관련된 실제 조건에 대한 확인들이 없다면 조건 변수는 거의 쓸모가 없습니다. 확실히 하기 위해서 앞에서 소개했던 서버 예제를 잠깐 살펴보도록 하죠. 'got_request' 조건 변수가 처리할 새 요청이 들어왔을 때 시그널을 받는다고 가정하고 사용을 했습니다. 이들은 또한 어떤 요청 큐에 들어 있을 것입니다. 그 조건 변수가 시그널을 받았을 때, 기다리던 쓰레드가 있다면 그 쓰레드는 깨어나고 응답을 처리할 것이라는 것을 확신할 수 있습니다.

하지만, 새 요청이 들어온 순간에 모든 쓰레드가 바로 전 응답을 처리하느라 바쁘다면 어떻게 될까요? 이 순간에는 모든 쓰레드는 조건 변수를 기다리고 있지 않고 자기 일을 하고 있었기 때문에 그 조건 변수가 받은 시그널은 무시될 겁니다. 또한 각 쓰레드가 자기 일을 마치고 조건 변수를 기다리는 상태가 됐을 경우, 그 무시됐던 시그널이 다시 발생하지도 않습니다(또다른 새 요청이 없다고 가정하면). 따라서, 모든 쓰레드가 시그널을 기다리느라 멈춰있는 동안 최소한 한 개의 요청이 처리되지 못 하고 남아 있게 됩니다.

이 문제를 해결하기 위해서 요청이 미처리 된 갯수를 정수 변수에 갖고 있겠습니다. 그리고 각 쓰레드는 조건 변수를 기다리기 전에 그 값을 확인해서 그 값이 양수이면 (미처리 된 요청이 있다), 멈추지 않고 그 응답을 처리할 겁니다. 또한, 요청을 처리한 쓰레드는 이 변수를 하나씩 감소시켜야 하는데 이렇게 해야 숫자가 정확해 질것입니다.
이런 고려 사항들이 위에서 봤던 코드를 어떻게 바꾸는지 봅시다.


 /* 미처리된 요청, 0으로 초기화 */ int num_requests = 0; . . /* 먼저, 뮤텍스를 잠급시다. */ int rc = pthread_mutex_lock(&a_mutex); if (rc) { /* 에러 있음 */ perror("pthread_mutex_lock"); pthread_exit(NULL); } /* 이제 뮤텍스는 잠겼고, 조건 변수를 기다립니다. */ /* 처리할 요청이 없다면 */ rc = 0; if (num_requests == 0) rc = pthread_cond_wait(&got_request, &request_mutex); if (num_requests > 0 && rc == 0) { /* 미처리 요청이 있네용 */ /* 할 일을 합시다. */ . . /* 미처리 요청수를 하나 줄입니다. */ num_requests--; } } /* 마지막으로, 뮤텍스를 풀어줘야죠 */ pthread_mutex_unlock(&request_mutex); 


조건 변수 사용법 - 완전한 예제(Using A Condition Variable - A Complete Example)

조건 변수의 실질적인 사용법을 보여주기 위해서 앞에서 설명했던 서버를 시뮬레이션하는 프로그램을 소개하겠습니다. 한 쓰레드는 수신자로서, 클라이언트의 요청을 받아 들여서 링크드 리스트에 요청을 집어 넣습니다. 핸들러 쓰레드는 이 요청을 처리하게 됩니다. 간단하게 하기 위해서 수신자 쓰레드는 실제 클라이언트에서 요청을 받아들이지 않고 자신이 요청을 만들어 내게 할 것입니다.

소스는 thread-pool-server.c에서 볼 수 있습니다. 소스안에 아주 자세한 주석이 달려 있으니까 소스를 먼저 읽어 본 다음에 밑에 나오는 설명을 참고하세요.

  1. 'main' 함수는 먼저 핸들러 쓰레드를 만들고, 자신의 메인 루프를 통해 수신자 쓰레드의 역할을 짊어집니다.
  2. 한 개의 뮤텍스로, 조건 변수와 요청을 기다릴 링크드 리스트, 두 개를 보호하는데 씁니다. 이렇게 하면 전체 설계를 간단하게 할 수 있습니다. 연습문제 하나 내죠. 이 예제를 두 개의 뮤텍스를 쓰는 방식으로 바꿔보세요.
  3. 여기서 쓰이는 뮤텍스는 재귀적 뮤텍스"여야" 합니다. 왜 그런가는 소스 코드중, 'handle_requests_loop' 함수를 보세요. 보면, 먼저 뮤텍스를 걸고, 'get_request' 함수를 부르는데, 여기서도 뮤텍스를 또 거는군요. 만약에 재귀적 뮤텍스를 안 썼다면 이 'get_request' 함수에서 뮤텍스를 거는 순간 영원히 멈춰버릴 것입니다.
    'get_request' 함수에서 뮤텍스 거는 부분을 빼서 두 번 거는 문제를 풀 수 있지 않겠냐라고 할 지도 모르겠지만 이렇게 하면 결함이 있는 설계가 돼 버립니다. 아주 큰 프로그램에서 'get_request'를 다른 코드상에서 부를 수도 있기 때문입니다. 따라서 매번 쓸 때마다 뮤텍스가 적절하게 잠겼는지 확인할 필요가 있습니다.
  4. 일반적으로, 재귀적 뮤텍스를 쓸 때에는, 뮤텍스를 잠그고 푸는 것을 한 함수 안에서 하도록 해야 합니다. 안 그러면, 잠근 수만큼 풀기가 아주 어려워 지고 결국 데드락이 발생하게 될 겁니다.
  5. pthread_cond_wait() 함수가 내부적으로 뮤텍스를 풀었다 다시 거는게 처음에는 헷갈릴 수도 있습니다. 제일 좋은 방법은 코드상에 이런 동작에 대해 주석으로 달아서, 다른 사람이 쓸데없이 뮤텍스를 또 걸지 않게 해 줄 수 있습니다.

개인적인 쓰레드 데이타 - 쓰레드만의 데이타("Private" thread data - Thread-Specific Data)

보통의 쓰레드 하나짜리 프로그램에서 가끔 전역 변수를 써야 할 때가 있습니다. 맞습니다. 나이 드신 훌륭한 선생님께서는 전역 변수를 쓰는게 아주 나쁜 습관이라고 말씀하셨습니다. 하지만 가끔 이게 편할 때가 있습니다. 특히나 한 파일 안에서만 보이는 정적 변수라면 더욱 그렇죠.

멀티 쓰레드 프로그램에서도 이런 전역 변수를 써야 할 경우가 있습니다. 모든 쓰레드에서 접근 가능한 하나의 변수에 대해서는 약간의 오버헤드를 갖는 뮤텍스를 써서 보호해야 한다는 것에 주의하시기 바랍니다. 게다가, 특정한 쓰레드에서만 쓰일 "전역" 변수가 필요할 수도 있고, 똑같은 "전역" 변수이나 다른 쓰레드에서는 다른 값을 가져야 할 때도 있습니다. 예를 들어, 각 쓰레드에서 전역적으로 접근할 수 있는 하나의 연결 리스트(그러나 같지 않은)가 필요하다고 가정해 보죠. 더군다나, 모든 쓰레드가 실행할 코드는 동일해야 합니다. 이런 경우에, 리스트의 시작을 나타내는 전역 포인터는 각 쓰레드에서 서로 다른 위치를 가르키고 있어야 합니다.

이런 포인터를 가지려면 메모리상의 위치가 다른 동일한 전역 변수가 있어야 합니다. 이것이 바로 쓰레드만의 데이타(thread-specific data) 메카니즘이 필요한 이유입니다.


쓰레드만의 데이타 지원 개요(Overview Of Thread-Specific Data Support)

쓰레드만의 데이타(TSD) 메카니즘에서는 키와 값이라는 개념이 필요합니다. 각 키는 이름을 갖고 있고 어떤 메모리 영역을 가르킵니다. 두 개의 서로 다른 쓰레드에서 이름이 같은 키를 갖고 있다면 항상 서로 다른 메모리 위치를 나타냅니다. 이 키를 가지고 접근할 수 있는 메모리 블럭을 할당해 주는 라이브러리 함수들이 이것을 처리해 줍니다. 키를 만들어 주는 함수(전체 프로세스에서 한 키에 대해서 한 번만 실행), 메모리를 할당해 주는 함수(각 쓰레드에서 실행), 특정 쓰레드에서 이 메모리를 다시 반환해 주는 함수, 전체 프로세스에서 그 키를 없애주는 함수등이 있습니다. 또, 키가 가르키는 데이타에 접근하는 함수와 그 값을 세팅하거나 값을 알아내는 함수도 있습니다.


쓰레드만의 데이타 블럭 할당하기(Allocating Thread-Specific Data Block)

pthread_key_create() 함수는 새로운 키를 만들어 내려고 할 때 쓰입니다. 이 키는 전체 프로세스의 모든 쓰레드에서 유효합니다. 키가 생성 됐을 때, 기본으로 NULL을 가르키게 됩니다. 다음에 각 쓰레드들은 자신이 원하는 값으로 이 복사본을 변경하게 됩니다. 사용법을 보여드리죠.

 /* rc 는 pthread 함수의 리턴값을 저장하는데 쓰입니다. */ int rc; /* 키를 갖고 있을 변수 정의. */ pthread_key_t list_key; /* cleanup_list 는 데이타를 청소해 주는 함수입니다. */ /* 이것은 우리 프로그램에서 만들어 주는 것이지 TSD 자체의 것이 아닙니다. */ extern void* cleanup_list(void*); /* 삭제시 불릴 함수를 넘겨서 키를 만듭니다. */ rc = pthread_key_create(&list_key, cleanup_list); 



몇 가지 주의사항:

  1. pthread_key_create() 가 리턴한 후에는 'list_key' 변수는 새롭게 생성된 키를 가르키게 됩니다.
  2. pthread_key_create()의 두번째 인자로 넘겨진 함수 포인터는 쓰레드 종료시, pthread 라이브러리에 의해서 키 값의 포인터를 인자로 받아서 불리게 됩니다. 함수 포인터에 NULL 포인터를 넘길 수도 있는데 이렇게 하면 종료시 해당 키에 대해서는 아무 함수도 실행 되지 않습니다. 주의할 점은, 이 키가 한 쓰레드에서 한 번만 생성됐다고 하더라도, 각 쓰레드가 종료할 때마다 실행된다는 것입니다.
    만약에 키를 여러개 생성했다면 키 생성 순서와는 상관없이 해당 종료 함수가 실행 될 것입니다.
  3. pthread_key_create() 함수는 성공시 0을, 실패시 에러 코드를 리턴합니다.
  4. PTHREAD_KEYS_MAX 만큼의 키 값 제한이 있습니다. PTHREAD_KEYS_MAX 가 넘어가게 되면 pthread_key_create() 함수에서 EAGAIN 에러 값을 받게 될 것입니다.

쓰레드만의 데이타에 접근하기(Accessing Thread-Specific Data)

키를 생성한 다음에는 pthread 함수를 써서 접근할 수 있습니다: pthread_getspecific()pthread_setspecific(). 첫번째 함수는 주어진 키에 대해서 그 값을 알아내는 데 쓰이고, 두번째 함수는 주어진 키에 데이타를 세트하는데 쓰입니다. 키 값은 간단하게 void 포인터(void *)이기 때문에, 아무것이나 저장할 수 있습니다. 사용법을 살펴보도록 하죠. 'a_key'는 pthread_key_t 타입으로서, 이미 적당히 초기화된 키 변수라고 가정합니다.


 /* 이 변수는 pthread 함수의 리턴 코드값을 저장하는데 쓰입니다. */ int rc; /* 데이타를 저장할 변수를 정의합니다. 여기서는 integer 라고 하죠. */ int* p_num = (int*)malloc(sizeof(int)); if (!p_num) { fprintf(stderr, "malloc: out of memory\n"; exit(1); } /* 변수를 아무 값으로 초기화 합니다. */ (*p_num) = 4; /* 이제 이 값을 TSD 키에 저장합니다. */ /* 주의할 것은 'p_num' 을 저장하는게 아니라 */ /* p_num이 가르키는 값을 저장한다는 것입니다. */ rc = pthread_setspecific(a_key, (void*)p_num); . . /* 어쩌구 저쩌구... */ . . /* 'a_key' 키의 값을 얻어서 출력. */ { int* p_keyval = (int*)pthread_getspecific(a_key); if (p_keyval != NULL) { printf("value of 'a_key' is: %d\n", *p_keyval); } } 

한 쓰레드에서 키 값을 세트한 후, 다른 쓰레드에서 그 값을 읽어보시기 바랍니다. NULL 을 얻게 될텐데 이 키 값은 쓰레드마다 서로 다르기 때문에 그렇습니다.

pthread_getspecific() 이 NULL을 리턴하는 두 가지 경우를 알아보겠습니다:

  1. 주어진 키가 유효하지 않다(즉, 키가 생성이 안 됐다).
  2. 키 값이 NULL이다. 이는 초기화가 안 됐거나 그 전에 pthread_setspecific()에 의해서 강제로 NULL로 세트됐을 경우중 하나이다.

쓰레드만의 데이타 블럭을 지우기(Deleting Thread-Specific Data Block)

pthread_key_delete() 함수는 키를 지울 때 쓰입니다만, 함수 이름 때문에 헷갈리지 말아야 할 것이 하나 있습니다. 이 함수는 해당 키가 갖고 있는 메모리를 지우지도 않고, 키 생성시 등록된 청소 함수를 부르지도 않습니다. 그러므로, 실행중에 이 메모리를 프리시켜야 한다면 직접 해 줘야 합니다. 하지만 보통, 전역 변수(쓰레드만의 데이타 역시)를 사용한다는 것은 쓰레드가 종료할 때까지 프리시킬 필요가 없을 테고, 이럴 경우에는 쓰레드 라이브러리가 종료함수를 불러줄 것입니다.

이 함수 사용법은 간단합니다. list_key를 알맞게 생성된 키를 가르키는 pthread_key_t 변수라고 가정하면 이런식으로 쓰면 됩니다:

int rc = pthread_key_delete(key);

성공시에는 0을 리턴하고, 주어진 변수가 유효한 TSD 키를 가르키지 않을 경우에는 EINVAL을 리턴합니다.


완전한 예제(A Complete Example)

아직 없습니다. 생각할 시간이 좀 필요하네요. 죄송합니다. 지금 당장 제가 생각할 수 있는 것은 '전역 변수는 아주 나쁘다'라는 것입니다. 앞으로 좋은 예제를 찾아보도록 하겠습니다. 혹시 좋은 예제가 있다면 제게 알려주시기 바랍니다.


쓰레드 취소와 끝내기

쓰레드를 만들었으니 끝내는것도 생각해 볼까요? 몇 가지를 살펴보죠. 쓰레드를 깨끗하게 끝낼 수 있어야 하겠죠. 그리고 아주 고약한 방법인 시그널을 사용하는 프로세스 포크(fork)와는 달리 pthread 라이브러리는 좀 더 신중하게 디자인 돼서 쓰레드를 취소한다든지 끝난 다음의 청소 작업등에 대한 완전한 시스템을 제공합니다. 한 번 살펴보죠.


Canceling A Thread

쓰레드를 끝내려고 할 때는 pthread_cancel를 쓰면 됩니다. 이 함수는 쓰레드 ID를 파라미터로 받아 그 쓰레드 ID로 취소 요청을 보냅니다. 이 요청에 대해 그 쓰레드가 어떻게 할 지는 그 쓰레드의 상태에 달려 있습니다. 즉시 취소될 수도 있고, 취소 위치(뒤에서 설명합니다)에 다다랐을때 취소될 수도 있고, 아예 무시해 버릴 수도 있습니다. 어떻게 쓰레드의 상태를 설정하며 취소 요청에 대해 어떻게 동작하는지에 대한 설정등에 대해서는 뒤에서 살펴보도록 하죠. 일단은 취소 함수를 어떻게 쓰는지 보겠습니다. 'thr_id'는 돌고 있는 쓰레드의 pthread_id 를 갖고 있는 변수라고 합시다.

 pthread_cancel(thr_id); 



pthread_cancel()은 0을 리턴하기 때문에 성공여부를 알 수가 없습니다.


쓰레드 취소 상태 설정하기

쓰레드의 취소 상태는 여러가지 방법으로 바꿀 수 있습니다. 첫번째는 pthread_setcancelstate() 함수를 쓰는 것입니다. 이 함수는 취소 요청을 받아 들일 것인지 아닌지를 결정합니다. 두 개의 파라미터가 필요한데, 하나는 새로운 취소 상태가 설정되어 있어야 하고 하나는 이전 취소 상태가 담겨질 변수입니다. 어떻게 쓰는지 보세요.

 int old_cancel_state; pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancel_state); 



이 함수를 부른 쓰레드는 취소될 수가 없습니다. 취소될 수 있도록 하려면 다음처럼 하면 됩니다.

 int old_cancel_state; pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancel_state); 



두 번째 파라미터를 NULL로 넘겨주게 되면 예전 취소 상태에 대해서 알 수가 없습니다.

비슷하게 pthread_setcanceltype()이란 함수가 있는데 이 함수는 취소 요청에 대한 반응을 결정합니다. 이 때 이 쓰레드는 취소될 수 있다고 가정합니다. 가능한 반응으로는 취소 요청을 즉시(비동기적으로) 처리하는것과 취소 위치에 도착하기 전까지 취소를 미루는 것입니다. 다음은 비동기적 취소 방법 입니다.

 int old_cancel_type; pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_cancel_type); 



취소 위치까지 취소를 미루는 것은 다음처럼 하면 됩니다.

 int old_cancel_type; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_cancel_type); 



두 번째 파라미터를 NULL로 이 함수를 부르면 예전 취소 상태에 대해 알 수 없습니다.

"취소 상태랑 타입을 설정 안 하면 어떻게 되나요?"라고 묻는다면 pthread_create()가 자동으로 PTHREAD_CANCEL_ENABLE (취소 요청 처리)과 PTHREAD_CANCEL_DEFERRED(취소 미룸)를 설정해 준다라고 대답해 드리죠.


취소 위치

지금까지 살펴본 것처럼, 쓰레드는 취소 요청을 즉시 처리하지 않을 수 있습니다. 대신 취소 위치에 도착할 때까지 그 요청을 미룰 수가 있습니다. 그럼 도대체 취소 위치란게 뭘까요?

보통, 쓰레드 실행을 오랫동안 정지시키는 함수는 취소 위치가 될 수 있습니다. 실제로는 특정 구현에 따라 달라지지고 얼마나 POSIX 표준을 따르냐(어느 버전의 표준)에 따라 달라집니다만 다음 함수들은 취소 위치입니다.

  • pthread_join()
  • pthread_cond_wait()
  • pthread_cond_timedwait()
  • pthread_testcancel()
  • sem_wait()
  • sigwait()

무슨 소리냐면 쓰레드가 이 중 한 함수를 실행중일 때, 뒤로 미룰 취소 요청이 있는지 확인하고 취소 요청이 들어와 있으면 자기가 끝난 다음에 취소 작업을 실행한 뒤 종료하게 됩니다. 이런 함수들이 실행중이 아니라면 방법은 한 가지 밖에 없는데, pthread_testcancel()를 쓰는 것입니다. 이 함수는 현재 쓰레드에서 대기중인 취소 요청이 있는지 확인해서 있다면 취소 작업을 실행하고, 없다면 그냥 리턴합니다. 보통 취소 상태로 들어가지 않고 긴 작업을 수행하는 쓰레드에서 쓰일 수 있습니다.

주의사항: 실제 pthread 표준에 일치하는 구현에서는, 프로세스를 블럭시키는 read(), select(), wait()등등의 시스템 콜들도 역시 취소 위치가 됩니다. 또한, 이 시스템 콜을 쓰는 표준 C 라이브러리들도 역시 마찬가지입니다(예를 들면 다양한 버전의 printf 함수들).


쓰레드 청소 함수 세팅하기(Setting Thread Cleanup Functions)

pthead 라이브러리가 제공해주는 기능중에, 자신이 종료하기 전에 자기 자신이 쓰던 리소스를 깨끗히 정리해주는 것이 있습니다. 이는 pthread 라이브러리에 의해서 자동으로 관련 함수가 불리거나 필요해 의해 스스로 부를 수 있기 때문에 가능해 집니다(즉, 자신이 pthread_exit()를 부르거나, 다른 쓰레드에 의해 취소될 때).

이를 위해 두 개의 함수가 제공됩니다. 하나는 pthread_cleanup_push() 함수로서 현재 쓰레드용 청소 함수 집합에 새로운 청소 함수를 추가해 줍니다. pthread_cleanup_pop() 함수는 pthread_cleanup_push()에 의해 추가된 마지막 함수를 제거해 줍니다. 쓰레드가 종료될 때는, 해당 청소 함수들은 등록됐던 반대 순서롤 불리게 됩니다. 즉, 마지막에 등록된 청소 함수가 제일 처음 불리게 됩니다.

pthread_cleanup_push() 함수의 두 번째 파라미터로 넘긴 변수가 청소 함수의 파라미터로 넘겨져서 불리게 됩니다. 이것들이 어떻게 쓰이는지를 살펴보도록 하죠. 여기 예제에서는 쓰레드가 시작할 때 할당받았던 메모리를 반환하는데 이 함수들을 적용시켜 보겠습니다.


 /* 등록할 청소 함수 */ /* 할당된 메로리의 포인터를 받고 프리시켜 줌 */ void cleanup_after_malloc(void* allocated_memory) { if (allocated_memory) free(allocated_memory); } /* 쓰레드 함수 */ /* thread-pool 서버 예제에서 썼던 함수 그대로... */ void* handle_requests_loop(void* data) { . . /* 이 변수는 나중에 쓰일 겁니다. 그냥 읽어 나가세요.. */ int old_cancel_type; /* 지금 이 쓰레드의 시작 시각을 기억하기 위해서 약간의 메모리를 할당 받습니다. */ /* MAX_TIME_LEN 은 앞에서 미리 정의된 매크로라고 가정합니다. */ char* start_time = (char*)malloc(MAX_TIME_LEN); /* 청소 함수를 등록합니다. */ pthread_cleanup_push(cleanup_after_malloc, (void*)start_time); . . /* 쓰레드의 메인 루프입니다. 어떤 일들을 하겠죠... */ . . . /* 그리고 끝으로, 청소 핸들러를 제거할텐데 이 방법이 좀 이상해 보이겠지만 */ /* 밑의 주석을 잘 읽어 보세요. */ /* 현재 쓰레드를 취소 미룸 상태에 둡니다. */ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &old_cancel_type); /* '1'을 넘기면, 청소 핸들러 집합에서 지워버리기 전에 청소 핸들러를 실행 시킵니다. */ /* '0'을 넘기면, 청소 핸들러를 실행하지 않습니다. */ pthread_cleanup_pop(1); /* 쓰레드를 이전 취소 상태로 다시 되돌려 놓습니다. */ pthread_setcanceltype(old_cancel_type, NULL); } 

여기서 볼 수 있듯이, 메모리를 약간 할당한 뒤, 이 메모리를 쓰레드 종료 시점에서 프리 시키도록 청소 핸들러를 등록시킵니다. 메인 루프가 다 돌고 나면 청소 핸들러를 제거시키게 되는데, 이 때 등록한 함수, 같은 블럭에서 제거 시켜야 합니다. 왜냐하면, pthread_cleanup_push()pthread_cleanup_pop() (역주 : 원문은 pthread_cleanup_pop()과 pthread_cleanup_pop(), 오타임)이 실제로는 '{'와 '}'를 나타내는 매크로이기 때문입니다.

청소 함수를 제거할 때 이렇게 복잡한 코드를 쓰는 이유는 청소 함수 내에서 쓰레드가 취소되지 않게 하기 위해서입니다. 쓰레드가 비동기 취소 상태에 있을 수도 있기 때문에, 확실히 하기 위해서 취소 미룸 상태로 바꾼 다음에, 청소 함수를 제거하고, 마지막으로 이전 취소 상태로 되돌려 놓는 것입니다. 주의 할 점은, 쓰레드가 pthread_cleanup_pop() 자체내에서 취소 되지 않는다는 것인데, pthread_cleanup_pop()가 취소 위치가 아니기 때문입니다.


쓰레드 종료 동기화 하기(Synchronizing On Threads Exiting)

가끔은 다른 쓰레드가 끝나길 기다려야 하는 경우가 있습니다. pthread_join() 함수로 이 일을 할 수 있습니다. 이 함수는 두 개의 파라미터가 필요한데, 조인(join)될 쓰레드를 나타내는 pthread_t 타입의 변수와 해당 쓰레드의 종료 코드값이 담길(취소 됐다면 PTHREAD_CANCELED) void *의 주소를 나타내는 변수입니다. pthread_join() 함수는 이 함수를 부르는 쓰레드를, 조인될 쓰레드가 끝날 때까지 중지시킵니다.

예를 들어 앞에서 살펴봤던 thread-pool 서버 예제를 생각해 보죠. 코드의 끝 부분을 보면, sleep()을 불러서 프로세스가 끝나길 기다리고 있는 것을 볼 수 있습니다. 이렇게 한 이유는 메인 쓰레드가 다른 쓰레드의 지연된 처리가 끝났는지 어떤지를 알 수 있는 방법이 없기 때문입니다. 해결 방법은 이것이 비록 바쁜 루프가 되긴 하겠지만 지연된 요청이 없을 때까지 메인 쓰레드가 루프를 돌게 하는 것입니다.

지금까지 살펴 본 것들을 깔끔하게 구현하려면 다음 세가지 변경사항을 추가시키면 됩니다.

  1. 요청이 다 만들어지면, 플래그를 이용해서 핸들러 쓰레드에게 알려줍니다.
  2. 요청 큐가 비어 있을 때마다 더 이상 만들 요청이 있는지 없는지를 확인하게 합니다. 더 만들어질 요청이 없다면 쓰레드를 종료시킵니다.
  3. 메인 쓰레드는 자신이 만든 쓰레드들이 끝날 때까지 기다립니다.

앞에 두 가지 변경사항은 좀 쉽습니다. 'done_creating_requests'란 전역 변수를 하나 만들고 '0'으로 초기화를 시킨 다음, 각 쓰레드들은 조건 변수를 기다리기 전에 (즉, 요청 큐가 비어있을 때), 이 전역 변수 값을 확인합니다.
메인 쓰레드는 자신이 모든 요청을 다 만들어 낸 다음, 이 변수를 '1'로 세팅합니다. 그리고, 조건 변수에 브로드캐스트를 날려 혹시 조건 변수를 기다리고 있는 쓰레드가 확실히 'done_creating_requests' 플래그를 다시 확인 할 수 있게 해줍니다.

마지막 세번째 변경사항은 pthread_join() 루프로 처리할 수 있습니다. 각 핸들러 쓰레드마다 한 번씩 pthread_join()를 불러주면 됩니다. 이렇게 하면 모든 핸들러 쓰레드가 종료된 다음에 이 루프가 끝나게 됩니다. 따라서 전체 프로세스를 안전하게 종료할 수 있습니다. 만약에 이 루프를 쓰지 않는다면 핸들러 쓰레드가 요청을 처리하고 있는 중간에 전체 프로세스를 끝낼 가능성이 있습니다.

변경된 프로그램은 thread-pool-server-with-join.c 에서 볼 수 있습니다. 세 가지 변경 사항은 소스에서 'CHANGE'(대문자)란 곳을 찾아 보면 됩니다.


쓰레드 떼어내기(Detaching A Thread)

지금까지 pthread_join() 함수를 써서 쓰레드가 어떻게 조인 되는지를 살펴 봤습니다. 사실 조인가능한(join-able) 상태에 있는 쓰레드는 꼭 다른 쓰레드에 의해서 조인되어야 합니다. 그렇지 않다면 그 쓰레드가 갖고 있던 메모리 리소스가 완전하게 제거되지 않을 것입니다. 이는, 부모 프로세스가 자식 프로세스를 거둬들이지 않는 상황과 비슷합니다('고아'나 '좀비'프로세스라고 부르죠).

만약에 어떤 쓰레드가 다른 쓰레드에 조인이 필요없이 아무때나 종료하고 싶다면, 그 쓰레드는 떨어진(detached) 상태에 있어야 합니다. 이렇게 하려면, pthread_create()에 적당한 플래그를 줘서 쓰레드를 만들어 내던지, pthread_detach() 함수를 쓰면 됩니다. 여기서는 두 번째 방법을 살펴 보겠습니다.

pthread_detach() 함수는 파라미터가 한 개 필요합니다. 파라미터는 pthread_t 형으로서 떨어진(detached) 상태로 놓을 쓰레드를 나타냅니다. 예를 들면, 다음 코드처럼 쓰레드를 만들자마자 바로 떨어지게(detach) 할 수 있습니다.

 pthread_t a_thread; /* 쓰레드 구조체를 담을 변수 */ int rc; /* pthread 함수의 리턴값을 위한 변수 */ extern void* thread_loop(void*); /* 쓰레드의 메인 함수를 선언 */ /* 새 쓰레드를 만드는데... */ rc = pthread_create(&a_thread, NULL, thread_loop, NULL); /* 성공이라면 새 쓰레드를 떼어낸다. */ if (rc == 0) { rc = pthread_detach(a_thread); } 



물론, 떨어진(detached) 상태의 쓰레드를 즉시 갖고 싶다면 첫번째 방법 (pthread_create()를 부를 때, 떨어진(detached) 상태를 세트해서 부름)을 쓰는 것이 더 효과적입니다.


쓰레드 취소 - 완전한 예제(Threads Cancellation - A Complete Example)

다음 예제는 지금까지 예제들보다 훨씬 큰 예제입니다. 이 예제는 C에서, 약간은 깔끔한 멀티 쓰레드 프로그램을 어떻게 만드는가를 보여줍니다. 앞에서 썼던 thread-pool 서버 예제를 사용하겠습니다. 이 예제는 두 가지 면에서 업그레이드 될 텐데, 하나는 요청의 부하에 따라 핸들러 쓰레드의 숫자를 조절하는 기능입니다. 요청 큐가 커지면 새 쓰레드가 만들어지고, 큐가 다시 줄어들면 필요없는 쓰레드는 취소 될 것입니다.

두 번째는, 더 이상 처리할 요청이 없을 때 서버의 종료 방법을 고칠 것입니다. 깔끔하지 못한 sleep()을 쓰는 대신, 각 핸들러 쓰레드들이 자신의 마지막 요청을 처리하고 종료할 때까지, 메인 쓰레드가 pthread_join()을 써서 기다리도록 할 것입니다.

다음처럼 4개의 파일로 나눠서 구현됐습니다.

  1. requests_queue.c - 이 파일에는 요청 큐를 처리하는 함수들이 있습니다. add_request()get_request() 함수를 여기에 넣었는데, 앞에서 전역 변수로 정의됐던 큐 헤드용 포인터와, 요청 카운터, 큐용 뮤텍스, 조건 변수를 하나의 구조체로 묶어서 같이 넣었습니다. 이렇게 해서, 데이타에 대한 모든 조작이 한 파일 안에서 일어나게 되고, 이 파일안에 있는 모든 함수는 'requests_queue'라는 구조체에 대한 포인터를 받습니다.
  2. handler_thread.c - 이 파일은 각 핸들러 쓰레드가 실행 시킬 메인 루프를 돌리는 함수들이 있습니다. (이 버전에서의 'handle_requests_loop()' 함수와, 밑에서 설명할 몇 가지 지역 함수들). 각 쓰레드간에 주고 받을 데이타들을 위한 구조체를 정의하고, pthread_create()에 파라미터로 그 구조체의 포인터를 넘깁니다. 이렇게 해서 세련되지 못한 전역변수의 사용을 대신합니다. 이 구조체에는 쓰레드 ID, 요청 큐 구조체에 대한 포인터, 뮤텍스, 조건 변수가 들어 있습니다.
  3. handler_threads_pool.c - 여기서 쓰레드 풀(pool)의 추상화를 정의합니다. 여기에는 쓰레드를 만드는 함수, 취소시키는 함수, 프로그램 종료시 모든 활성화된 쓰레드를 없애는 함수들이 들어 있습니다. 요청큐에서처럼 구조체를 정의해서 쓰겠습니다. 이것들에 대해서는 메인 쓰레드 혼자만 접근하기 때문에 뮤텍스로 이것들을 막을 필요가 없습니다. 이렇게 하면 뮤텍스에 의한 약간의 오버헤드를 줄일 수 있는데, 비록 이런 오버헤드가 작을지라도, 아주 바쁜 서버에서는 큰 영향을 미치기 때문입니다.
  4. main.c - 그리고 마지막으로, 이 모든 것들을 묶고, 관리하는 메인 함수입니다. 이 함수는 요청큐와 쓰레드 풀(pool), 핸들러 쓰레드들을 만들고, 요청을 발생시킵니다. 그 요청이 큐로 들어간 다음에는 큐 크기와 현재 활성화된 쓰레드의 숫자를 확인해서 큐 크기에 맞게 쓰레드 수를 조절합니다. 수위(water-mark) 알고리즘을 사용하는데, 코드를 보면 알겠지만, 좀 더 세련되고 복잡한 알고리즘으로 쉽게 바꿀 수가 있습니다. 여기서 쓰인 수위(water-mark) 알고리즘은 간단합니다. 수위가 높아지면 큐를 빨리 비우기 위해서 쓰레드들을 새로 만들어 내고, 수위가 낮아지면 원래 핸들러 쓰레드를 제외한 나머지 쓰레드들은 취소 시킵니다.

원래 프로그램을 좀 더 다루기 쉽게 고친 다음에 우리가 새로 배운 pthread 함수들을 다음과 같이 적용시켰습니다.

  1. 각 핸들러 쓰레드는 취소 미룸 상태로 만들어 집니다. 이렇게 하면 이 쓰레드들이 취소가 됐을때, 현재 처리중인 요청을 다 처리한 다음에 종료할 수 있게 됩니다.
  2. 각 핸들러 쓰레드는 또한 청소 함수를 등록하는데, 각 쓰레드가 종료시 뮤텍스를 풀고 종료토록 하기 위해서입니다. 이는 아마 거의 대부분이 취소 상태인 pthread_cond_wait()에서 취소 명령을 받을 것이기 때문에 정확히 동작 할 것입니다. 만약에 뮤텍스를 건 다음에 최소되거나 종료되면 다른 모든 쓰레드가 그 뮤텍스에 의해 '멈춰버릴' 것입니다. 따라서 청소 핸들러( pthread_cleanup_push() 함수로 등록함)에 뮤텍스를 풀어 주는 함수를 등록하는 것은 아주 확실한 해결책이 될 것입니다.
  3. 끝으로, 메인 쓰레드는 대충 종료하지 않고 아주 정확하게 종료되도록 세트됩니다. 종료할 시점이 되면, 'delete_handler_threads_pool()' 함수를 불러서 남아 있는 핸들러 쓰레드들을 기다리도록 pthread_join을 부릅니다. 이렇게 함으로써, 모든 핸들러 쓰레드가 자신의 마지막 요청을 다 처리하고 난 다음에 이 'delete_handler_threads_pool()' 함수가 리턴하게 됩니다.

자, 이제 소스 코드를 통해 모든 것을 살펴보시기 바랍니다. 헤더 파일을 먼저 읽으면 전체 디자인을 이해하기 쉽습니다. 컴파일하려면, thread-pool-server-changes 디렉토리로 들어가 'gmake'라고 치면 됩니다.

연습문제 1 : 마지막 예제 프로그램에는 종료 시점에 약간의 경쟁 상태(race condition)가 존재합니다. 이 경쟁이 뭐에 대한 건지 알 수 있겠습니까? 이 문제에 대해서 완전한 해결책을 제시할 수 있습니까?(힌트 - 'delete_handler_thread()'함수를 써서 쓰레드를 없애려고 할 때 무슨 일이 생기는지 생각해 보세요)

연습문제 2 : 우리가 사용한 수위(water-mark) 알고리즘은 새 쓰레드를 만들어 낼 때, 너무 느리게 동작하는 것 같습니다. 요청들이 처리 되기전에 큐에서 기다리는 평균 시간을 줄일 수 있는 다른 알고리즘을 생각해 보세요. 그리고 이 시간을 잴 수 있는 코드를 넣어 보세요. 여러분의 "최적화된 풀(pool) 알고리즘"을 찾을 때까지 계속 실험을 해 보세요. 주의 사항 - 시간을 재는 것은 getrusage, 시스템 콜로 할 수 있습니다. 정확한 측정값을 위해 각 알고리즘을 여러번 실행 시켜보시기 바랍니다.


쓰레드를 이용한 사용자 인터페이스 프로그래밍(Using Threads For Responsive User Interface Programming)

쓰레드가 아주 유용하게 쓰일 수 있는 분야로 유저 인터페이스(user interface)용 프로그램이 있습니다. 이런 프로그램들은 보통 한 곳에서 루프를 돌면서 사용자 입력을 읽고, 처리한 다음, 결과를 보여주는 식으로 되어 있습니다. 만약에 처리 부분이 시간을 아주 오래 잡아 먹고 있다면 사용자는 이 동작이 끝날 때까지 계속 기다려야 합니다. 이런 긴 처리 부분을 독립된 쓰레드로 돌리고, 다른 쓰레드에서는 사용자 입력을 받게 한다면, 그 프로그램은 좀 더 사용자의 반응에 민감하게 될 것입니다. 사용자는 그 긴 동작 중간에 취소를 시킬 수 있게 됩니다.

그래피컬한 프로그램에서는 이 문제가 더욱 심각해 집니다. 왜냐하면, 이런 프로그램은 자신의 윈도우를 다시 그리도록 윈도우 시스템에서 오는 메세지를 항상 기다리고 있어야 하기 때문입니다. 만약에 다른 일을 하느라고 너무 바쁘다면 자신의 윈도우는 텅 비어 있을 것입니다. 아주 안 좋아 보이죠. 이런 경우에, 한 쓰레드가 윈도우 시스템의 메세지를 처리하는 루프를 돌리면서, 다시 그리라는 요청에 항상 응답 할 수 있게 하는 것은 아주 좋은 방법입니다( 사용자 입력에 대해서도 마찬가지겠죠). 이렇게 오래 걸릴법한 동작이 필요하다 싶으면(최악의 경우에 0.2초 이상이라고 합시다), 독립된 쓰레드로 돌게 하십시요.

세번째 쓰레드를 쓰는 좀 더 좋은 방법이 있습니다. 이 세번째 쓰레드가 사용자 입력 쓰레드와 작업 수행 쓰레드의 제어와 동기화를 맏게 하는 것입니다. 사용자 입력 쓰레드가 사용자 입력을 받으면 제어 쓰레드에게 이 일을 처리하도록 요청하고, 작업 수행 쓰레드가 자신의 일 처리를 끝내면 결과를 사용자에게 보여주도록 제어 쓰레드에게 요청하게 하는 것입니다.


사용자 인터페이스 - 완전한 예제(User Interaction - A Complete Example)

사용자가 중간에 취소 시킬 수 있는, 파일에서 줄 수를 읽어들이는 간단한 문자 모드 프로그램을 작성해 보겠습니다.

메인 쓰레드는 줄 수를 세도록 쓰레드 하나를 만듭니다. 다음으로 사용자 입력을 확인하도록 두번째 쓰레드를 만듭니다. 그리고나서, 메인 쓰레드는 조건 변수를 기다립니다. 아무 쓰레드나 자신의 일을 마치면, 이 조건 변수에 시그널을 날려서 메인 쓰레드가 알게 합니다. 사용자의 취소 요청이 일어났는지 아닌지를 확인하기 위해서 전역 변수를 씁니다. '0'으로 초기화를 시키는데 만약에 사용자 쓰레드가 취소 요청을 받는다면(사용자가 'e'를 누른다면), 이 전역 변수를 '1'로 세팅하고 조건 변수에 시그널을 날리고 종료합니다. 줄 수를 세는 쓰레드는 자신의 계산이 다 끝났을 경우에만 조건 변수에 시그널을 날릴 것입니다.

프로그램을 읽기 전에 system() 함수의 사용법과 'stty' 유닉스 명령어에 대해서 설명드리겠습니다. system() 함수는 파라미터로 받아 들인 유닉스 명령어를 실행시킬 쉘을 하나 생성합니다. stty 유닉스 명령어는 터미널 모드 세팅을 바꾸는데 쓰입니다. 우리는 터미널을 라인 버퍼 모드에서 캐릭터 모드(raw 모드라고도 하죠)로 바꾸는데 썼습니다. 이렇게 하면, 사용자 입력 쓰레드에서 getchar() 를 부를 때, 사용자가 키를 누르자마자 즉시 리턴하도록 해줍니다. 만약에 이렇게 하지 않는다면, 사용자가 엔터(ENTER) 키를 누를 때까지 사용자의 입력을 버퍼에 저장해 놓을 것입니다. 끝으로, 이 캐릭터 모드는 별로 쓸모가 없기 때문에 프로그램이 종료하고 쉘 프롬프트를 다시 받으면, 사용자 입력 쓰레드는 원래의 터미널 모드(라인 버퍼 모드)로 돌아가도록 청소 함수를 등록시킵니다. 더 자세한 내용은 stty 매뉴얼을 참고하세요.

프로그램 소스는 line-count.c에서 받을 수 있습니다. 이 프로그램이 읽을 파일 이름은 'very_large_data_file'이라고 하드 코드 되어 있습니다. 이 이름의 파일을 하나 만드셔도 되고(충분한 시간동안 동작이 이뤄지도록 크게 만드세요), 저희가 제공하는 'very_large_data_file.Z' 파일을 받으셔서 압축을 풀어 사용하셔도 됩니다. 압축 푸는 명령어는 다음처럼 하시면 됩니다.

uncompress very_large_data_file.Z

압축이 풀리면 5메가(!) 짜리 'very_large_data_file'이 생기니까, 압축을 풀기 전에 디스크 용량이 충분한지 확인하시기 바랍니다.


멀티 쓰레드 어플리케이션에서 비시스템 라이브러리 쓰기(Using 3rd-Party Libraries In A Multi-Threaded Application)

멀티 쓰레드를 프로그램에 적용하려는 프로그래머에게 아주 중요한 것 하나만 더 말씀드리겠습니다. 멀티 쓰레드 프로그램은 동시에 똑같은 함수를 실행시킬 수도 있기 때문에, 한 쓰레드이상에서 동시에 실행 될지도 모르는 함수는 꼭 MT-safe(Multi-Thread Safe:멀티 쓰레드에 안전)해야 합니다. MT-safe한 함수 내부의 구조체나 다른 공유 리소스에 대한 접근이 뮤텍스로 보호된다는 뜻입니다.

멀티 쓰레드 프로그램에서 MT-safe하지 않는 라이브러리를 쓸 수 있는 가능한 방법은 두 가지가 있습니다.

  1. 오직 한 쓰레드에서만 이 라이브러리를 쓰기. 이 방법은 이 라이브러리 함수가 서로 다른 쓰레드에서 동시에 실행되지 않게 해줍니다. 하지만 이 방법은 문제가 있는데, 전체 설계에 제한 사항을 줄 수도 있다는 것입니다. 또한, 다른 쓰레드가 이 라이브러리의 함수를 쓰려고 할 가능성이 있기 때문에 쓰레드간 통신에 좀 더 신경을 써야 할 지도 모릅니다.
  2. 그 라이브러리 함수를 부를 때는 뮤텍스를 써서 보호할 것. 어느 쓰레드에서건 이 라이브러리의 함수를 부를 때는 하나의 뮤텍스를 쓰라는 뜻입니다. 뮤텍스를 걸고, 함수를 부르고, 뮤텍스를 푸는 순서로 사용하면 되겠습니다. 여기서 생길 수 있는 문제는 잠금이 그렇게 깔끔하게 이루어지지 않는다는 것입니다. 같은 라이브러리의 서로 다른 두 개의 함수가 서로 간섭하지 않는 독립된 함수임에도, 서로 다른 쓰레드에서 동시에 불릴 수 없을 수 있습니다. 두 번째 쓰레드는 첫번째 쓰레드가 함수 실행을 끝낼 때까지 뮤텍스에 걸려 있을 것입니다. 관련 없는 함수들에 대해서 서로 다른 두 개의 뮤텍스로 처리할 수도 있겠지만, 그 라이브러리가 실제로 어떻게 동작하는지 알 방법이 없기 때문에 어떤 함수끼리를 묶어야 하는지 알수가 없습니다. 거기다가, 혹시 안다고 할 지라도 새 버전의 라이브러리가 나왔을 때, 예전 버전과 다르게 동작할수도 있기 때문에 잠금 시스템 전체를 고쳐야 할 지도 모릅니다.

보시다시피, MT-safe하지 않은 라이브러리는 특별한 주의가 필요하기 때문에, 가능하면 비슷한 기능을 가진 MT-safe한 라이브러리를 찾아 쓰는 것이 제일 좋습니다.


쓰레드를 지원하는 디버거 쓰기(Using A Threads-Aware Debugger)

마지막 주의 사항입니다. 멀티 쓰레드 어플리케이션을 디버깅 할 때, 쓰레드를 "인식"하는 디버거가 필요합니다. 상용 개발 환경의 거의 대부분의 최신 디버거들은 모두 쓰레드를 처리할 수 있습니다. 리눅스에서, 거의 대부분의 배포판에 들어 있는 gdb는 쓰레드를 인식하지 못합니다. 'SmargGDB'라는 프로젝트가 있는데, gdb에 쓰레드 지원과, 그래픽 사용자 인터페이스(멀티 쓰레드 어플리케이션을 디버깅 할 때만 가능한)를 추가하는 프로젝트입니다. 어쨌든, 이것으로 다양한 사용자 레벨의 쓰레드 라이브러리를 쓰는 쓰레드 프로그램 에만 쓰이고, LinuxThreads를 디버깅하려면 커널 패치가 필요한데, 2.1.X 대 버전에서만 가능합니다. 더 자세히 알고 싶다면 http://hegel.ittc.ukans.edu/projects/smartgdb/를 찾아보시기 바랍니다. 또한 커널 2.0.32에 패치하는 법과 gdb 4.17을 쓰는 것에 대한 정보도 있는데, LinuxThreads homepage에서 찾아보시기 바랍니다.


Side-Notes

수위(water-mark) 알고리즘
버퍼나 큐를 처리할 때 주로 쓰이는 알고리즘입니다. 버퍼나 큐에 데이타를 채워넣다가, 크기가 상위 한계를 넘으면 큐에 넣는 것을 멈춥니다( 혹은 비우는 것을 좀 더 빠르게 합니다). 이 상태를 하위 한계 이하로 떨어질 때까지 계속 유지하다가 떨어지면, 큐에 채워넣기를 계속하게 됩니다( 혹은 비우는 속도를 원래 속도로 되돌려 놓습니다).



 

블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

Naming Rule for C/C++

IT/일반 2008. 2. 27. 12:29
1. 개요
개발과 관련된 code convention과 naming rule에 대해 기술한다.

2. 필요성
  2-1. 소프트웨어 생명(Life Time)의 80%는 유지 / 보수에 소요된다.
  2-2. 소프트웨어의 가독성을 향상시킨다.
  2-3. 본래의 개발자에 의해서 소프트웨어 개발 전체가 유지되는 소프트웨어는 거의 존재하지 않는다.

3. 함수
  3-1. 기본 규칙
     * MainCategory_SubCategory1SubCategory2VerbObject1Object2
     * 예)
              - Log_OpenLogSystem() : modules/Log의 로그시스템 생성 함수
              - DB_TrieDBNew() : modules/DB/TrieDB의 생성 함수
  3-2. Verb 동사가 나오기 전까지는 카테고리를 표현
  3-3. Verb 동사는 어떤 기능을 하는지 표현
  3-4. Object는 특정 목적 대상이 있거나, 특별하게 구별하고 싶을 때
  3-5. 동사든 명사든 첫 글자는 대문자로 표기
  3-6. MainCategory가 첫 자가 대문자인 경우는 라이브러리(modules 디렉토리)에서, 소문자인 경우는 App 자체 (apps 디렉토리) 모듈
     * 예)
            - Pthread_Create() : 라이브러리(modules/Pthread)에 소속된 모듈로써, thread를 생성하는 함수
            - ppr_Create() : 라이브러리에 소속되지 않은 모듈로써 ppr 자료구조를 새롭게 생성하는 함수
            - static void _CheckLogFileSize() : _(언더바)로 시작하는 함수는 선언한 파일 안에서만 통용되는 함수로써 그 의미를 명확하게 하기 위해 static으로 선언.
  3-7. 다소 길어도 명확하게 - 의미 전달이 명확하게
  3-8. 생성자의 경우엔 "_Create", 소멸자인 경우엔 "_Destroy", 초기화의 경우엔 "_Init" 사용
      * 예) ppr_Create();, ppr_Destroy();, ppr_Init();
  3-9. File 쓰기엔 "_Write", 읽기엔 "_Read" 사용
  3-10. File read 시 첫번재 item 읽을때 "_ReadFirst", 그 다음 item 읽을때 "_ReadNext" 사용
      * 예) ppr_Write();, ppr_Read();, ppr_ReadFirst();, ppr_ReadNext();
  3-11. 구조체 object에 값 setting 할때 "_Put", 값 불러올때 "_Get" 사용
      * 예) Parameters_PutItem();, Parameters_GetItem();
  3-12. 통상적으로 사용하는 연산이 있는 경우는 그대로 사용한다.
      * 예) Queue_enQueue();, Queue_deQueue();, Stack_Push();, Stack_Pop();

4. 변수
  4-1. 헝가리안 표기법 사용
     *  헝가리안 표기법에 대한 분분한 의견이 있지만, C/C++에서 이만큼 의미를 명확히 표현해주는 표기법이 없다.
     * Format
       * x_xXxxxxxx
       * 0123456789
       * 0 : 변수의 위치를 지정한다. g(전역변수), m(멤버변수), 없음(지역변수)
       * 1 : 0 위치에 g 나 m을 지정한 경우 "_"를 기술
       * 2 : 자료형의 종류
       * 3 ~ : 변수의 의미있는 이름을 기술. 3 위치는 대문자를 사용, 변수 이름이 너무 긴 경우 자음만을 기술.
  4-2. Table



prefix type description example
b bool any boolean type bool bTrue;
c char character type char cLetter;
i int integer for index int iCars
n int number, quantity int nNum;
l long long type long lDistance;
u unsigned unsigned type(4byte) unsigned uPercent;
w WORD unsigned word(2byte) WORD wCnt;
dw DWORD unsigned double word(4byte) DWORD dwLength;
d double double floating point double dPercent;
f float floating point float fPercent;
s static a static variable static short ssChoice;
rg array stands for range float rgfTemp[16];
p * any pointer int iAddr;
sz * null terminated string of characters char szText[16];
pfn * function pointer int (*pifnFunc1)(int x, int y);
t struct a user defined type  
e enum variable which takes enumerated values  
E struct a user defined type  
g_ Global Global Variable String *g_psBuffer;
m_ Member class private member variable int m_iMember;
k constant formal parameter   void vFunc(const long klGalaxies)
r reference formal parameter   void vFunc(long &rlGalaxies)
str String string class(C++) String strName;
prg   dynamically allocated array char *prgGrades;
h handle handle to something hMenu
x/y   used as size int xWitdth, yHeight;

  4-3. Example
      * int g_nCnt;  : 정수형 글로벌 카운터
      * unsigned char ucByte;  : 한 바이트 데이터
      * char cChar;  : 한 문자
      * unsigned char rgucByte[10];  : 바이트 데이터 10개
      * char rgcChar[10];  : 문자 데이터 10개
      * char szChar[16 + 1]; : 문자 16개를 저장할 수 있는 문자열 공간
      * Etc
         * typedef로 재정의된 자료형일 경우 t_를 prefix로 사용
            * 예) unsigned char t_UC
      * Attention
         * 포인터(*) 및 참조(&)는 변수의 앞에 붙여서 선언
            * int * piAddr; -> int *piAddr;
      * Exception
         * 일반적으로 사용하는 변수 i, j, argc, argv는 그대로 사용한다.
         * C 라이브러리의 종속적인 변수 type일 경우, 일반적으로 사용하는 변수명을 사용한다.
           * 예)
              * phtread_t *tid (pthread_t가 unsigned int임에도 불구하구...)
              * phtread_attr_t *attr;

5.  구조체
  5-1. 기본 규칙
     * Maincategory_Name
  5-2. 첫 자만 대문자 (매크로와의 구분을 위해...)
  5-3. 예) Socket_Context (Socket의 Context에 사용되는 구조체)

6.  매크로
  6-1. 기본 규칙
     * MAINCATEGORY_SUBCATEGORY_NAME
  6-2. 대문자만을 사용
  6-3. 예) TKIR_INDEX_METHOD_BLOCK

7. 파일 생성규칙
  7-1. 하나의 디렉토리에 디렉토리를 대표하는 인터페이스용 헤더파일이 한 개 존재해야 함.
  7-2. 각각의 모듈 내부에서만 사용하는 함수를 인터페이스에 노출시킬 필요 없음.
  7-3. 헤더파일
      *. 중복 참조 방지용 매크로를 정의한다.

#ifndef __PTHREAD_H__
#define __PTHREAD_H__

#endif

  7-4. C++을 위하여 다음을 선언한다.
#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

8. 기타
  8-1. 리턴 값
     * 일반적으로
       * 0 : 성공
       * 음수 : 실패
       * 양수 : 오류는 아니지만, 기타 정보를 돌려줄 때
  8-2. 들여쓰기 (개인적인 취향에 따르도록)
     * 4칸이나 8칸. 경험상 4칸이 적절하나 개인적인 취향이니 적절히
  8-3. 칼럼 (개인적인 취향에 따르도록)
     * 될 수 있으면 80 칼럼을 지키는 것이 가독성 향상

9.  문서화
  9-1. 위키 (문서 작성)
     * 최소한 관련 모듈에 대해선 위키에 기본 내용이라도 작성한다.
  9-2. doxygen (documentation)
     * doxygen을 이용하여 문서화를 기본으로 한다.
     * 기본적인 format인

/**
@brief 함수 관련 설명
@param pArg1 첫 번째 함수 인자에 대한 설명
@param pArg2 두 번째 함수 인자에 대한 설명
@return
0 : success
-1 : fail
*/

이 형태를 유지하고, 나머지 format은 권고 사항이다.

     * 참조 URL : http://wiki.kldp.org/wiki.php/Doxygen

'IT > 일반' 카테고리의 다른 글

fwrite 함수로 작성했으나, linux와 windows에서 사이즈가 ...  (2) 2009.07.27
struct의 size 값은??  (0) 2008.02.22
블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

검색 대상이 되는 데이터들의 증가 속도는 그야 말로 기하급수적으로 늘어나고 있는 추세이며
데이터의 수는 감히 상상조차 하기 힘든 수치이다.

이에 사용자들의 눈높이는 날이 갈수록 높아지고 있으며
검색엔진은 이 눈높이를 맞춰 나가기가 벅찰 정도가 되어 있다.

그 중 하나인 검색 결과 속도 측면에서만 본다면 수백억건에서 사용자가 원하는 정보를
어떻게 빨리 보여줄 수 있을까??
과연 하드웨어가 뒷받침해 줄 수 있을까??

이런 고민들로 인해 압축 기법도 검색엔진에 도입이 되었고
압축 효율도 좋으면서 decoding도 빠른 압축 기법을 선호하게 된다.

속도랑 압축이 무슨 관계일까?

압축하지 않는 데이터를 disk에 그대로 저장을 한다면
disk i/o에서의 엄청난 bottle neck이 발생할 것이다. 물론 대용량일 경우에 한해서이다.
이 disk i/o를 최소화하여 bottle neck의 요소를 제거하자는 의도에서 압축 기법이 등장하게 된다.

검색엔진에서 사용할 만한 압축 기법을 정리해 보았다.

- byte-aligned compression
- variable byte code compression
- gamma code compression
- word-aligned compression
  * simple-9
  * relative-10
  * carryover-12
    * slide

블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

struct의 size 값은??

IT/일반 2008. 2. 22. 17:40

예전에 선배가 이런 류의 문제를 낸 적이 있다.
난 당당히 21Byte입니다. 라고 했었는데... 쩝...

아래 구조체를 sizeof 하면 얼마가 나올까?

 struct stStructSize {
      int            index;
      double         nFirstData;
      double         nLastData;
      unsigned char  chType;
  };

간단히 생각하면 21 byte가 나온다. ( 4 + 8 + 8 + 1 = 21 )
그렇지만 실제 sizeof(stStructSize)를 해보면 엉뚱하게도 32 byte 라는 결과를 출력한다.
왜 그런것일까? (참고로 int는 4byte, double은 8byte, unsigned char은 1byte)

이유은.. 컴파일러가 똑똑해서 그렇다.
사실 다소 메모리의 낭비가 있지만 원시코드로 변환했을때 효율적으로 퍼포먼스를 좋게하며 메모리를 관리(&접근) 하기 위함이다.
조금 더 얘기하자면 컴파일러의 내부 파싱이 dword aligned 을 기준으로 처리한 결과이다.
그래서 위의 경우에는 모든 자료형을 8byte로 계산한 것이다.

그렇지만 우리는 구조체의 접근방법, 데이터의 처리등 byte의 절대값으로 처리해야할 경우가 있다.
그럴때는 아래와 같이 전처리기를 선언해 주면 실제 원하는 크기인 21byte를 얻을 수 있다.

  #pragma pack(1)

그럼 아래처럼 확인해 보면 정확히 byte size를 얻을 수 있는것을 알 수 있다.

  int nLength = sizeof(stStructSize);
fprintf(stdout, "Struct Size = %d\n", nLength);

#pragma pack(1) 의 의미는 byte aligned 이다.
참고로 2는 word aligned 이며 4는 dword aligned 이다. classsizeof도 위와 마찬가지 결과이며 1을 생략한 #pragma pack() 으로 선언할 경우 기본적으로 dword aligned 처리된다.

위와 같은 문제아닌 문제가 있으므로 byte type 이나 word type을 이용할때는 주의가 필요하다.


한가지 더 퀴즈를 내자면,
int 형의 size를 알기 위해선 sizeof() 를 사용한다.
그러나, 이 sizeof()를 사용할 수 없을 경우 int 형의 size는 어떻게 알 수 있을까???







int  cTest = -1;
로 선언한 후 bit 연산을 하면 알 수 있다.

'IT > 일반' 카테고리의 다른 글

fwrite 함수로 작성했으나, linux와 windows에서 사이즈가 ...  (2) 2009.07.27
Naming Rule for C/C++  (2) 2008.02.27
블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

Vitual Box

IT/리눅스 2008. 2. 20. 11:46

예전에 선배가 VMware를 사용하는 것을 보고 굉장한 놀라움을 금치 못하였다.
윈도우 상에서 Linux가 돌아가고 있는 것이었다.
혹은 반대의 경우도 있었다.
모 이런게 다 있나 했어, 설치해서 사용해 보니 개발자인 나에겐 정말 새로움이었구, 즐거움이었다.
그러나, 이런 즐거움도 노트북으로 전용 PC를 바꾸니 사용하지 못하다가
최근에 회사를 옮기면서 Virtual Box를 접하게 되었다.

젠장...
환경이 점점 좋아지고 있는 상황에 나는 왜 이렇게 꿈뜬 것인지..
Virtual Box도 VMware와 같이 가상 머신 프로그램이며,
오픈소스이구, 상대적으로 많은 OS를 지원한다.

정말 대단하다...
하나의 컴퓨터에 가상 PC를 설치하여 여러개의 운영체제를 돌릴 수 있다니...와우~~!!

관심 있는 분들은 다운받자!!  --> 다운받기(http://www.virtualbox.org/wiki/Downloads)

참고로, virtualbox에서는 local에 있는 비디오 카드를 기본적으로 인식하지 못하기 때문에
virtual graphic 카드로 인식을 하기 때문에 Ubuntu7.10 에 있는 화려한 UI는 보기 힘들다.
다행인 것은 원하는 해상도는 획득할 수 있다.

virtualbox를 끈 후 윈도우  명령 프롬프트에서
>cd C:\Program Files\innotek VirtualBox
>VBoxSVC.exe /ReRegServer
>regsvr32.exe VBoxC.dll
와 같은 명령으로 해당 카드를 등록을 하면 된다.

그런 후 virtualbox를 구동 후 리눅스에서 /etc/X11/xorg.conf를 수정한다.
Section "Screen"
    Identifier "Screen0"
    Device "ATI Graphics Adapter"
    Monitor "Samsung SyncMaster 950p(T)"
    DefaultDepth 24
    SubSection "Display"
        Depth 24
        Modes "1600x1200"
    EndSubsection
EndSection

알아서 사용하고 싶은 해상도를 선택한다.

블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,

백세주와 소주를 합친 술을 우리는 흔히 "오십세주"라고 한다.
우리가 백세주와 소주의 특성을 알아 별도로 오십세주를 만드는 것이 아니라,
주어진 백세주와 소주를 적당한 병에 일정한 비율을 섞어 만들면 바로 오십세주가 되는 것이다.
Mash-up 이란 바로 이런 것이다.
즉, 주어지는 어플리케이션이나 웹 서비스를 적절하게 혼합하여 새로운 자원으로 만드는 것을
Mash-up 이라고 한다.

Mash-up 이라는 단어는 원래 음악계에서 사용하던 말이다.
가수나 DJ가 2가지 곡을 조합하여 하나의 곡을 만들어 내는 것을 의미하며
이 의미가 웹으로 진화를 하면서 Web 2.0이라는 패러다임과 잘 어울려지면서
각광을 받고 있는 새로운 서비스가 되고 있다.

그 일례로, 하우징맵스(www.housingmaps.com)라는 회사를 꼽을 수 있겠다.
구글이 지도 서비스에 대한 오픈 API를 공개하기 전에 Paul Rademacher라는 사람이
해킹하여 구글 지도 서비스와 해당 지역의 부동산 매물을 보여주는 서비스를 하였다.
이 사실에 구글은 고소는 커녕 2달 뒤 오픈 API를 공개하고
Paul Rademacher라는 사람을 채용했다.
(해킹으로  성공한 녀석들은 전부 이름 있는 회사에 팔려갔다. ㅠ.ㅠ)

그러나, Mash-up의 단점 또한 있다.
제공되어지는 서비스를 이용하여 새로운 서비스를 만듦에 있어 추가 비용은 거의 없는 것에 반해
1차 서비스를 통해 Mash-up 서비스를 하게 될 때, 1차 서비스가 서비스를 중단하거나
제공하지 않게 된다면 당연히 Mash-up 서비스도 중단할 수 밖에 없는 종속적이 관계가 된다.
또한, 1차 서비스의 제공 형태가 달라지면 Mash-up 서비스가 달라질 수 밖에 없다.
메타검색을 제공하는 서비스 업체와 별반 차이 없어 보이긴 한다.

그래서, 많은 Mash-up 서비스를 준비하는 업체 혹은 개인은
신뢰성이 있는 1차 서비스를 찾아야 하고, 이를 통해 Mash-up 서비스를 활성화가 된다면
1차 서비스는 당연히 더 많은 traffic를 야기시키고 활성화 될 것으로 보인다.
Mash-up 서비스는 1차 서비스에 종속적이긴 하나,
Mash-up 서비스의 역할에 따라 1차 서비스도 성공적인 서비스로 가는 초석을 다지는 길일 것이다.

생각해 볼 수 있는 서비스는
1. 지도 서비스 + 뉴스 서비스
   1.1 뉴스가 발생한 지역에 특정 기호로 표시하여 어느 지역에서 사건이 발생되었는지 한 눈에 파악
   1.2 해당 지역을 선택하면 해당 지역 뉴스를 보여주는 편리함.
2. 지도 서비스 + 엔터테인먼트
   2.1 맛집, 멋집, 관광지, 부동산

굉장히 재미있는 서비스들이 많이 나올 것으로 보인다.

** http://cafe.daum.net/BestTanslation/241n/8 에서 일부 발췌 **


*

'IT > 개념' 카테고리의 다른 글

About ZooKeeper  (0) 2017.04.19
[웹 2.0] 소개  (0) 2008.05.22
블로그 이미지

쩐의시대

나답게 살아가고 나답게 살아가자

,