C# 쓰레드 이야기: 7



Similar documents
Microsoft PowerPoint - chap01-C언어개요.pptx

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

제11장 프로세스와 쓰레드

SIGIL 완벽입문

쉽게 풀어쓴 C 프로그래밍

Microsoft PowerPoint - CSharp-10-예외처리

152*220

C++ Programming

Cluster management software

슬라이드 1

Microsoft PowerPoint - Java7.pptx

슬라이드 1

JAVA PROGRAMMING 실습 09. 예외처리

PowerPoint Presentation

PowerPoint Presentation

<342EBAAFBCF620B9D720B9D9C0CEB5F92E687770>

Microsoft Word - windows server 2003 수동설치_non pro support_.doc

750 1,500 35

Windows 8에서 BioStar 1 설치하기

PowerPoint Presentation

(Microsoft PowerPoint - java1-lecture11.ppt [\310\243\310\257 \270\360\265\345])

041~084 ¹®È�Çö»óÀбâ



기본소득문답2

아이콘의 정의 본 사용자 설명서에서는 다음 아이콘을 사용합니다. 참고 참고는 발생할 수 있는 상황에 대처하는 방법을 알려 주거나 다른 기능과 함께 작동하는 방법에 대한 요령을 제공합니다. 상표 Brother 로고는 Brother Industries, Ltd.의 등록 상

q 이장에서다룰내용 1 객체지향프로그래밍의이해 2 객체지향언어 : 자바 2

1. 자바프로그램기초 및개발환경 2 장 & 3 장. 자바개발도구 충남대학교 컴퓨터공학과

<B3EDB9AEC0DBBCBAB9FD2E687770>

JVM 메모리구조

연구노트

Chapter ...

Microsoft PowerPoint - chap04-연산자.pptx

1

JAVA 프로그래밍실습 실습 1) 실습목표 - 메소드개념이해하기 - 매개변수이해하기 - 새메소드만들기 - Math 클래스의기존메소드이용하기 ( ) 문제 - 직사각형모양의땅이있다. 이땅의둘레, 면적과대각

PowerPoint Template

!

Microsoft PowerPoint - 04-UDP Programming.ppt

Microsoft PowerPoint 자바-기본문법(Ch2).pptx

예제 2) Test.java class A intvar= 10; void method() class B extends A intvar= 20; 1"); void method() 2"); void method1() public class Test 3"); args) A

PowerPoint Presentation

17장 클래스와 메소드

Microsoft PowerPoint - chap05-제어문.pptx

PowerPoint Presentation

PowerPoint Presentation

RHEV 2.2 인증서 만료 확인 및 갱신

Web Scraper in 30 Minutes 강철

PathEye 공식 블로그 다운로드 받으세요!! 지속적으로 업그래이드 됩니다. 여러분의 의견을 주시면 개발에 반영하겠 습니다.

[Brochure] KOR_TunA

PowerPoint Presentation

02 C h a p t e r Java

쓰리 핸드(삼침) 요일 및 2405 요일 시간, 및 요일 설정 1. 용두를 2의 위치로 당기고 반시계방향으로 돌려 전날로 를 설정합니다. 2. 용두를 시계방향으로 돌려 전날로 요일을 설정합니다. 3. 용두를 3의 위치로 당기고 오늘 와 요일이 표시될 때까지 시계방향으로

JAVA PROGRAMMING 실습 08.다형성

금오공대 컴퓨터공학전공 강의자료

다른 JSP 페이지호출 forward() 메서드 - 하나의 JSP 페이지실행이끝나고다른 JSP 페이지를호출할때사용한다. 예 ) <% RequestDispatcher dispatcher = request.getrequestdispatcher(" 실행할페이지.jsp");

초보자를 위한 C# 21일 완성

Microsoft PowerPoint - lec12 [호환 모드]

예외 예외정의예외발생예외처리예외전파 단정 단정의선언 단정조건검사옵션 2

wtu05_ÃÖÁ¾

C# Programming Guide - Types

커알못의 커널 탐방기 이 세상의 모든 커알못을 위해서

untitled

Microsoft 을 열면 깔끔한 사용자 중심의 메뉴 및 레이아웃이 제일 먼저 눈에 띕니다. 또한 은 스마트폰, 테블릿 및 클라우드는 물론 가 설치되어 있지 않은 PC 에서도 사용할 수 있습니다. 따라서 장소와 디바이스에 관계 없이 언제, 어디서나 문서를 확인하고 편집

Microsoft PowerPoint - chap03-변수와데이터형.pptx

Microsoft PowerPoint - chap06-2pointer.ppt

gnu-lee-oop-kor-lec06-3-chap7

<4D F736F F F696E74202D B3E22032C7D0B1E220C0A9B5B5BFECB0D4C0D3C7C1B7CEB1D7B7A1B9D620C1A638B0AD202D20C7C1B7B9C0D320BCD3B5B5C0C720C1B6C0FD>

유니티 변수-함수.key

비디오 / 그래픽 아답터 네트워크 만약에 ArcGolbe를 사용하는 경우, 추가적인 디스크 공간 필요. ArcGlobe는 캐시파일을 생성하여 사용 24 비트 그래픽 가속기 Oepn GL 2.0 이상을 지원하는 비디오카드 최소 64 MB 이고 256 MB 이상을 메모리

Microsoft PowerPoint - C++ 5 .pptx

PowerPoint 프레젠테이션

. 스레드 (Thread) 란? 스레드를설명하기전에이글에서언급되는용어들에대하여알아보도록하겠습니다. - 응용프로그램 ( Application ) 사용자에게특정서비스를제공할목적으로구현된응용프로그램을말합니다. - 컴포넌트 ( component ) 어플리케이션을구성하는기능별요

H3250_Wi-Fi_E.book

Microsoft Word - ntasFrameBuilderInstallGuide2.5.doc

ThisJava ..

PowerPoint 프레젠테이션

rmi_박준용_final.PDF

Design Issues

Network Programming

Microsoft PowerPoint - MonthlyInsighT-2018_9월%20v1[1]

PowerPoint Presentation

윤성우의 열혈 TCP/IP 소켓 프로그래밍

쉽게

쉽게 풀어쓴 C 프로그래밍

Microsoft PowerPoint - e pptx

9장.예외와 단정

경우 1) 80GB( 원본 ) => 2TB( 복사본 ), 원본 80GB 는 MBR 로디스크초기화하고 NTFS 로포맷한경우 복사본 HDD 도 MBR 로디스크초기화되고 80GB 만큼포맷되고나머지영역 (80GB~ 나머지부분 ) 은할당되지않음 으로나온다. A. Window P

ThinkVantage Fingerprint Software

Tcl의 문법

Microsoft Word - Crackme 15 from Simples 문제 풀이_by JohnGang.docx

새로운 지점에서 단이 시작하는 경우 기둥코로 시작하라고 표시합니다. 기둥코(standing stitch)로 시작하는 방법은 YouTube 에서 찾아볼 수 있습니다. 특수 용어 팝콘뜨기: 1 코에 한길긴뜨기 5 코, 바늘을 빼고 첫번째 한길긴뜨기코의 앞에서 바늘을 넣은

PowerPoint Presentation

슬라이드 1

< E20C6DFBFFEBEEE20C0DBBCBAC0BB20C0A7C7D12043BEF0BEEE20492E707074>

1. auto_ptr 다음프로그램의문제점은무엇인가? void func(void) int *p = new int; cout << " 양수입력 : "; cin >> *p; if (*p <= 0) cout << " 양수를입력해야합니다 " << endl; return; 동적할

04 Çмú_±â¼ú±â»ç

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

Microsoft PowerPoint 웹 연동 기술.pptx

View Licenses and Services (customer)

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

Transcription:

About C# Thread 스레드, 그리고 Java 와 C# - p2 C# 쓰레드 이야기: 1. 쓰레드는 무엇인가? - p19 C# 쓰레드 이야기: 2. 다중 쓰레드 p24 C# 쓰레드 이야기: 3. 쓰레드 제어 p30 C# 쓰레드 이야기: 4. 쓰레드 기본 개념 p38 C# 쓰레드 이야기: 5. NT vs UNIX p54 C# 쓰레드 이야기: 6. 쓰레드 예외 처리 p60 C# 쓰레드 이야기: 7. C#으로 만드는 WinTop p73 C# 쓰레드 이야기: 8. 동기화 p94 C# 쓰레드 이야기: 9. 임계 영역 p101 C# 쓰레드 이야기: 10. 뮤텍스(Mutex) p120 C# 쓰레드 이야기: 11. 이벤트(Event) p132 C# 쓰레드 이야기 - 12. 식사하는 철학자 p142 C# 쓰레드 이야기 - 13. Interlocked, Heap p170 C# 쓰레드 이야기 - 14. 마지막 이야기 p182 1

스레드, 그리고 Java 와 C# By 한동훈 이 논의는 스레드에 대한 이야기와 이 스레드를 지원하고 있는 Java와 C#언어에 대한 얘기들이다. 중간중간에 코드 조각들을 갖고 얘기를 하지만, 실행되는 완전한 코드는 아니며, 완전한 실행을 위해서는 약간의 코딩이 필요하다. Trax : 오늘 얘기하려고 하는 건 Java나 C#이 어떤 언어인지, 둘 중에 어떤 것이 더 좋다는 얘기를 하려는 것은 아닙니다. 스레드에 대해서 얘기하려고 합니다. C# 이전에 스레드를 언어에서 지원하는 것은 Java 뿐이었죠. 프로그래밍 언어는 크게 명령형 언어와 함수형 언어로 나눌 수 있습니다. C 언어와 같은 구조적 프로그래밍, 또는 절차적 프로그래밍을 지원하는 언어들은 함수형 언어입니다. APL과 같은 언어는 진정한 함수형 언어로 볼 수 있습니다. 예 그만큼 APL로 작성된 코드는 판독하기가 어렵습니다. 프로그래밍 언어에 있어서 유명한 저자인 코넬도 단 4 줄의 APL 코드를 분석하는 데 4 시간이 걸렸다고 털어놓았던 적이 있으니까요. 명령형 언어는 요즘에 많이 볼 수 있는 객체 지향 언어들을 말합니다. C++, Java, C#과 같은 언어가 명령형 언어이고, C++보다는 Java나 C#과 같은 언어가 명령형 언어에 더 가깝습니다. 제가 말하는 명령형 언어와 함수형 언어는 일반적인 프로그래밍 언어의 구분과는 조금 다릅니다. 그러니까 LISP 같은 언어는 논의에 두지 않고 있는 거죠. 어쨌든 논의에서 제외한 LISP과 같은 언어를 빼면 명령형 언어나 함수형 언어 모두 전통적인 폰 노이만 구조를 갖고 있습니다. 폰 노이만 구조라는 건 메모리에 데이터와 코드를 모두 저장하고, CPU에서 데이터와 코드를 조금씩 가져와서 차례대로 처리하는 것을 말합니다. 한 번에 조금만 가져올 수 있으니까 전체를 처리하려면 같은 일을 몇 번은 반복해야겠지요. 그래서 폰 노이만 구조라는 건 메모리에 데이터와 코드를 배정하고, CPU에서 연산을 처리하고, 이 같은 일을 반복하는 루프를 갖는 구조를 말합니다. 약간 사담이지만, 어떤 특정 언어의 습득에만 의미를 두는 것 보다는 폰 노이만 구조와 프로그래밍에 대해서 한 번은 공부해두면 조금은 시야가 넓어진다고 생각합니다. 다시 논의로 돌아와서 대부분의 프로그래밍 언어는 폰 노이만 구조를 갖고 있기 때문에 프로그래밍 방법론이 근본적으로 갖다고 할 수 있습니다. 배정(Allocation), 연산(operation), 반복(loop)이라는 구조를 갖기 때문에 대부분의 프로그래밍 언어는 비슷합니다. 조금 본질을 벗어나면 폰 노이만 구조외에 병렬 처리를 위한 적용(Adaptive) 언어도 있습니다. 그러면 왜 전통적인 폰 노이만 구조를 따르는 언어들 중에 Java와 C#과 같이 프로세스 제어, 즉 스레드를 지원하게 되었는지 궁금하지 않습니까? Java라는 언어가 나타나기 이전의 컴퓨팅 환경은 단일 태스크 작업 환경이었고, 그 이후의 시대에는 윈도우와 같은 멀티 태스크 작업 환경이 보편화된 것이 이유입니다. 사실대로 말하면 Java는 UI와 실제 처리를 위해서 스레드를 필요로 하기 때문이지만. 어쨌든 그 이후로는 응용 프로그램이 DOS와 같은 단일 태스크 환경에서 윈도우와 같은 멀티 태스크 환경으로 옮기는 것 뿐만 아니라 2

멀티 태스크 환경을 이용한 스레드 제어의 필요성이 생겼다는 거죠. 이 때문에 윈도우 개발자들 사이에서 스레드는 최고의 유행어가 되었던 때도 있었죠. 하지만 언어 자체에서 스레드를 지원하는 것이 아니기 때문에 OS에서 제공하는 기능을 빌려 쓰거나, 직접 스레드를 제어하는 비표준 API를 만들어 써야 했습니다. 만약에 다른 OS로 옮긴다면 스레드와 관련된 부분은 다시 작성될 필요가 있었던 거죠. 그래서 이런저런 이유로 새로운 언어는 프로세스 제어 기능이 언어 설계에서 요구되고, 새롭게 설계되는 언어들은 프로세스 지향적인 성격을 띄게 되었습니다. 이러한 프로세스 지향적인 언어로는 Java, C#, Ada95, Perl이 있습니다. 멀티 태스크 환경에서 사용자는 더 많은 것들을 하기를 원하지만, 그것이 어떻게 돌아가는지는 신경 쓰지 않죠. 반대로 개발자는 그것이 어떻게 돌아가는지 신경 써야 하구요. -_-. 이러한 배경으로 인해서 스레드 프로그래밍을 일반적으로 하게되고, OS에서 빌려 쓰거나, Java와 같은 언어를 사용해서 스레드를 구현합니다. 스레드를 사용하는 가장 대표적인 예가 워드 프로세서일 것입니다. 문자를 입력하는 동안 백그라운드 작업으로 출력을 할 수 있고, 문법 검사를 수행하고 파일을 저장할 수 있습니다. 새로운 브라우저 창을 만들어도 새로운 프로세스가 시작하는 것이 아니라 하나의 스레드만 추가되기 때문에 공통되는 자원을 공유할 수 있고, 적은 메모리로 여러 사이트를 돌아 다닐 수도 있는 거겠죠. 스레드를 이해하지 못하는 프로그래머의 코드는 지금과 같은 멀티 태스크 환경에서 스레드를 이용하는 코드보다 수행 성능이 떨어질 수 밖에 없고, 보다 많은 PC 자원을 독점합니다. 다시 말해, 스레드는 응용 프로그램이 보다 효율적으로 실행되고, 자원(특히, CPU 사이클)을 독점하지 않는 프로그램을 작성할 수 있도록 해주며, 보다 고급 기술을 익힘으로써 프로그래머 자신의 가치를 높일 수 있을 겁니다. 그리고 프로그래밍 언어 설계도 이제는 프로세스 지향이라는 새로운 패러다임을 향해 나아가고 있다는 것이죠. 4baf : 얘기는 잘 들었습니다. 예, 자바는 스레드를 언어 차원에서 지원하는 첫번째 언어죠. 그리고 C#도 마찬가지로 스레드를 언어 차원에서 지원합니다. 하지만 자바보다 C#의 스레드 지원이 더 풍부합니다. 파이썬도 스레드를 지원합니다. 프로세스 제어 가능 API라는게 무슨 말인지 잘 몰라서 자바, C#의 그것과 같은지 정확하게 말은 못하겠네요. 아무튼, 파이썬도 자바처럼 프로그래밍이 됩니다. class PyClass(Thread) : def init (self) : Thread. init (self) def run(self) : 작업 3

대강 이런 식이었던 것 같습니다. 동기화 부분은 공부를 안해서 모르겠네요. 사실 자료도 구할 수 없었습니다. 자바에서 스레드를 대부분의 프로그래머가 이용하지 않는다고 하셨는데, 아마 자바 스윙을 사용하는 사람들은 대부분 이용합니다. 스윙이 워낙 느립니다. 정말 상상하기도 싫을 정도로 그 덕에 별도의 스레드에서 일찌감치 GUI 객체들을 생성해 놓지 않으면 다운된건지 아닌지 알 수가 없습니다. JSP/서블릿이나 EJB 도, 기본적으로 모두 스레드로 돌아갑니다. 초보들은 JSP 가 스레드에 의해서 돌아간다는 것도 모르지만, 자동적으로 스레드에 의해서 실행 되죠. 초보자들은 그래서 자원에 대한 동기화를 안하고 결과적으로 진짜 CPU 두 개 달린 시스템으로 옮기면 완전히 죽어버리는 코드를 생산하게 됩니다. Trax : 제가 얘기하고자 하는 논지를 완전히 벗어나셨군요. -_-. 미들티어에서 COM/DCOM, MTS 와 통합된 COM+ 모두 스레드를 생각하지 않고는 얘기할 수 없습니다. 마찬가지로 Java 쪽의 미들티어 기술은 EJB 에서 스레드를 빼고 생각하는 것은 말도 안됩니다. 또한 모든 웹 어플리케이션 서버들은 모두 스레드 기반하에 돌아갑니다. 이것은 웹이 갖고 있는 특성에 기인합니다. (ColdFusion, JRun Server, ASP, JSP, PHP, 탱고) 제가 하고자 하는 말은 이러한 것을 얘기하는 것이 아니고 Java 와 C#을 비교하자는 것도 아닙니다. 언어의 계보를 따져보면 CPL -> BCPL -> B - > C -> C++ -> Java -> C# 이 될 겁니다. 또는 C++의 다른 갈래로 C#이 될 것입니다. 물론 제가 얘기하고 싶은 것은 이것도 아닙니다..!! -_-. 프로세스와 스레드에 대해 깊이 아는 것은(예... 알고 보면 별거 없지만) 프로그래머의 기본 소양으로 자리잡아 갈 것이라는 겁니다. 현대 프로그래밍에서 짚고 넘어갈 기본 테마는 스레드와 네트워크입니다. 네트워크 없이는 말도 할 수 없는 환경이니 네트워크는 기본인데... 왜 스레드인가? 라는 거겠죠. 이것은 멀티 태스크 OS 의 등장과 그에 따른 응용 프로그램의 필요성의 대두입니다. 그리고 지금까지 많은 개발자들은 OS 의 스레드 처리 기능을 가져와서 편하게 사용하기 위해 저마다 고유의 라이브러리를 만들어 왔습니다. 그리고 일부는 이것을 라이브러리로서 판매해왔습니다. (Sheridan 의 Active Thread 같은 라이브러리가 잘 알려져 있습니다.) 그러나 이것은 너무나 반복적인 작업이기 때문에 이제는 언어 설계 자체에 프로세스처리가 반영되고 있다는 얘기입니다. 물론, 가상 머신을 갖고 있으니 스레드 지원이 더 쉬운 점도 있겠지요. 앞으로 기업환경에서 스레드에 대한 요구는 점점 더 많아질 것이고, 배경지식이 무르익을 것입니다. 나중에는 프로그래머 지망생들은 당연히 스레드에 대해서 배우지 않을까요. 사실 Linux 나 Unix 에서 프로그래밍을 처음 공부한 사람들은 입문과정에서 프로세스와 스레드에 대해서 학습합니다. 이제 새롭게 프로그래머가 되는 사람들은 윈도우와 같은 멀티 태스크 OS 만을 사용한 세대들이죠. 그러므로 멀티 태스크와 스레드에 대한 이해가 자연스러운 것이 됩니다. 바꿔 말하면 저와 같은 도스 세대들은 화장실에서 똥 누면서 양치질 하면서 신문을 보는 엽기적인 행각을 벌이는 멀티 태스킹을 제대로 이해하지 못하고 상상도 하지 못합니다. 그리고 실제로 접하면서 이것이 도스와 다른 점을 깨닫고 멀티 태스킹에 대해 이해하려고 하지요. 즉, 4

멀티 태스크에 대해 장황한 말이 필요합니다. 하지만 처음부터 윈도우와 같은 멀티 태스크 OS 를 사용한 세대들에게는 당연한 것이죠. 마찬가지로 스레드와 같은 기술이 VC++에서도 있었지만, 사용의 어려움 때문에 널리 쓰이지 못했고, 꼭 필요할 때만 울며 겨자 먹기 식으로 쓰는 기술이다라는 인식이 더 강했습니다. 그리고 그 수준조차 초등학교 수준이랄까... 수박 겉핥기를 벗어나지 못합니다. 그러나 앞으로의 언어들은 프로세스 지향적인 부분을 갖게 되고, 프로세스와 스레드는 기본이 될 것이고, 개발자가 이러한 것들을 처리하기위해 비슷비슷한 것들을 반복해서 개발하는 일에서 탈피할 수 있다는 것입니다. 예... 이것은 전적으로 제 개인적인 생각입니다. VB.NET 도 프로세스와 스레드에 대한 처리를 지원합니다. (CLR 때문이라 생각하고 별 것 아니라고 치부할지 모르지만...) 앞으로의 모든 언어들은 프로세스 지향적인 성격을 갖게 됩니다. 분명 현재 베타버전, 즉 Perl 6 을 릴리스하기 위한 단계에 있는 Perl 조차 스레드를 지원하고 있습니다. 최근에 개발된 언어들은 Eiffel(1992), Java(1995), Ada95(1995), C# & VB.NET(2000)입니다. Eiffel 과 Ada95 는 제가 사용해본 적이 없는 언어입니다. 그러나 이 두 가지를 제외하고도 다른 언어들은 모두 프로세스 지향적인 성격을 갖고 있습니다. 이제 좀 감이 잡히는지요? 프로그래밍 언어 설계의 패러다임도 프로세스 지향으로 가고 있습니다. 제가 얘기하는 것은... 네트워크, 스레드를 빼고는 앞으로 좋은 프로그래머가 될 수 없다는 얘기이고, 먼 시일을 내다보면 이들 프로그래밍도 점점 보편화된다라는 것입니다. 동기화는 네트웍과 프로세스 모두에서 중요한 주제입니다. 특별히 동기화를 주제로 빼고 싶은 생각은 없습니다. 어떤 언어를 쓰거나 상관하지 않습니다. 네트웍이나 프로세스 둘 중에 하나라도 깊이 있게 원론적으로 알 정도로 파고들라는 얘기입니다. 왜 프로세스와 스레드라고 말하는가? 뭐... 스레드라면 단순하지만... C#을 보면 프로세스 자체를 제어할 수 있는 모니터, 뮤텍스를 클래스 차원에서 지원하고 있습니다(너무 단순하고 쉬워서 황당할 정도지요. 물론 기존 프로세스와 스레드 프로그래밍과 비교해서이지만). 즉, 스레드는 이제 기본입니다. 그 이상을 나아가서... 모니터, 모니커, 뮤텍스까지 자세히 알라는 얘기입니다. 지금까지도 프로그래밍 언어는 3~4 년에 하나씩 등장하고 있습니다. 예... 미안하게도 스크립트형 언어는 프로그래밍 언어라는 관점에서는 주목을 받지 않습니다. 왜냐면 그것은 로직을 편하게 하기 위한 개념이고, 주요 특징들은 컴파일러형 언어에서 모두 다 나타나기 때문입니다. 더 엄밀히 말하자면 C 언어에 대해서도 C 컴파일러는 C 언어 가상 컴퓨터라고 얘기합니다. 컴파일러는 소스코드를 기계어로 번역할 뿐입니다. 제가 얘기하는 것은 Java 와 C#이 주는 의미는 앞으로의 경향을 단적으로 보여주는 것이라는 것입니다. 언어 습득에 급급해서 나무만 보지 말고 언어 전체의 숲을 보라는 얘기입니다. 프로세스와 스레드를 더 깊이 들어가보면 하나의 부모 프로세스 밑에 자식 스레드들이 있는 경우에 부모 프로세스에서 자식 스레드들간의 정보 교환이나 관리하는 역할을 합니다. 예로, 어머니가 자신의 귀여운 자식들인 고추와 딸기가 티격태격 싸우면 뜯어 말리기도 하고, 딸기는 방 청소하고, 고추는 유리창 닦기를 시키라고 하는 것과 5

같습니다. 하지만 옆집에 있는 어머니와 직접 얘기하지 못하기 때문에 전화를 이용한다든가 하는 점에서는 프로세스간에 직접 통신을 하지 못하기 때문에 IPC(Inter-Protocol Change)와 같은 방법으로 간접적으로 통신하는 것과 비슷할 것입니다. 그러나 응용 프로그램의 범위를 벗어난 통신은 불가능합니다. -_-. C#은 AppDomain 을 통해서 응용 프로그램의 범위를 벗어난 통신이 가능합니다. 그러나 저 조차도 이 분야에 대해서는 깊이 있는 지식이 없어서 이것으로 무엇이 가능한가에 대해서는 상상조차 하지 못합니다. 예... 프로세스와 스레드의 전문가들은 벌써 생각하고 있는 것들이 있는 것 같은데 얘기를 안해주는군요. -_-. 언어 자체에서 응용 프로그램 도메인 범위까지 넘나들 수 있는 것은 C#밖에 없습니다. parting : Trax 님과 4baf 님의 얘기는 잘 들었습니다. 저는 멀티 스레딩에 대해서 얘기해 보죠. 멀티 스레딩이라는 것은 이런겁니다. 제가 자주 써먹는 예제지만.. 은행구좌의 금액을 읽어와서 입금한 금액을 더해서 다시 저장하는 과정이 하나의 쓰레드라고 보고 코드로 쓰면 이런식으로 될 겁니다. clientbalance = Balance.getBalance(); clientbalance += deposit; Balance.setBalance(clientBalance) 근데, 실제로 은행에서는 이 일들이 하나가 아니라 수십개 이상 독립적으로 구좌를 갱신할거라는 거죠. 그러니 일은 멀티스레딩으로 일어난다는 것. 스레드는 각각 독립적으로 작업을 수행해야 하고, 같은 객체를 공유할 수도 있다는 거죠. 같은 객체에 공유 접근하는게 유용하면서도, 사실 에러의 근본 원인이 된다는 겁니다. 즉 두 스레드가 거의 동시에 같은 데이터를 수정하다가 그 데이타 객체가 행여나 잘못되면 망한다는 거죠 -_-.;; 예를 들면, 하나의 구좌에 입금을 하고 있는 와중에 다른 사람이 다른 은행원에게 같은 구좌에 또 입금을 원한다고 하면 즉, 저번 작업을 하고 있는 도중에 다른 작업의 접근이 또 이뤄지면 헷갈릴 수가 있습니다. 그래서 나중 작업만 기록이 되어버리는 사태가 발생할 수 있습니다. 은행에서는 이럴 경우 "작업중이니 기다리쇼"라고 팻말을 붙여 놓겠죠...아마도.. 컴퓨터도 마찬가지로 객체를 잠궈버려서 접근을 막는 식으로 작동을 합니다. 멀티 스레딩은 하나의 어플리케이션의 수행간 즉, 같은 프로세스에서 동작을 더 세분화한다고 보면 됩니다. 하나의 스레드가 입력을 받는 동안 다른 스레드는 그전에 입력 받은 걸 계산해서 화면이 출력하는 루틴을 돌린다거나 이렇게 하면 된다는 거죠.. 자바에서 스레드는 표준 클래스로 정의되어 있답니다. 스레드 객체를 생성하면 되죠... ping, pong 예제가 유명할겁니다..(유명한가? -_-.;;) class PingPong extends Thread String word; 6

int delay; PingPong(String WhatToSay, int delaytime) word = WhatToSay; delay = delaytime; public void run() try for(;;) System.out.print(word+""); sleep(delay); catch(interruptedexception e) return; public static void main(stringp[] args) new PingPong("ping", 33).start(); new PingPong("Pong", 100).start(); PingPong 이라는 스레드형을 정의합니다. 루프를 돌리면서 delaytime 동안만 대기합니다. 출력을 하면 핑퐁핑퐁 그러는데 갈수록 퐁의 빈도가 낮아질겁니다. 딜레이 타임이 다르니까... 쓰레드 객체를 만든 다음에 start 하면 쓰레드 인스턴스를 만들고.. run 메쏘드를 호출해서 쓰레드가 작동한답니다.. 이외에도 동기화, 즉 synchronized method 를 호출하면 객체가 잠금 상태가 되서 서스펜드모드로 들어가는, 즉, 두개의 스레드가 상호배타적으로 수행되게 해주는 게 있습니다. 아까 은행원 얘기에서 잠깐 메모 붙여서 일을 기다려서 하게 하는 그걸 말하는 겁니다. 이를테면 이렇게 되겠죠.. 7

class Account private double balance; public Acccount(double initialdeposit) balance = inintialdeposit; public synchronized double getbalance() return balance; public synchronized void deposit(double amount) balance += amount; 이런 식으로 될 겁니다. 그 다음에는 스레드 스케줄링에 관한 것이 있을 것이고... 스레드마다 우선순위를 주고 수행시간을 할당해줄겁니다...^^;; 운영체제가 해주든지 자바 가상머신(VM)이 해주든지.. 누군가는 해줄겁니다. -_-.;;; 그 외에는 스레드 서스펜드(대기), 스레드 인터럽트가 있습니다. 또 다른 것으로는 Runnable Interface 를 참조해서 구현하는 것도 있습니다. 자바에서 제공하는 것은 스레드 보안과 디버깅 정도입니다. Trax : parting 님의 멀티 스레딩과 Java 에서의 처리에 대한 얘기였죠. 저는 하던 업무가 주문, 재고, 창고... 이 개념이기 때문에... 동시 다발적으로 일어나는 주문(Order)에 대해 재고를 가감하는 경우에 재고에 대한 락(Lock)이 필요합니다(Rock 이 아닙니다... 돌... 돌... 돌... 요즘 해코님이 골이 좀 비었다고 성급하게 돌을 채운다고 하지요. -_-.). 하지만 이 락은 간단히 쓰기는 편한데... 많아지면... 복잡하고 엄청난 노가다가 됩니다. 또한 어디서 락을 해야 하고 풀어야 하는지를 프로그래머가 결정해야합니다. 하지만 Java 나 C#이나 InterLocked 클래스에서 제공하는 것은 그저 숫자형 변수를 넘겨받아서 값을 증가시키거나 감소시키는 역할밖에는 하지 않습니다. 예... Java 는 아직 저수준에 머물러 있어서 보다 고급스러운 건 직접 개발해야 합니다. 반면에 C#은 코드 블록을 적절히 묶어서 처리할 수 있도록 해줍니다. 이러한 것을 위해서 모니터 클래스를 제공하지요... -_-. 즉, 정수값을 바꾸는 정도가 아니라 문자열을 갖고 있든지 뭐든지간에 해당 블록안의 값들의 변경에 대한 락을 알아서 지원하는 겁니다. 단지, Monitor.Enter()와 Monitor.Exit() 만으로 말이지요. -_-. (얼마나 황당하고 편합니까... 써드 파티에서는 몇 백만원씩 받으면서 이런걸 판매하고 있는데... Sheridan 의 ActiveThread 가 아주 유명하지요. -_-.) 스레드 프로그래밍 해야 하는데 VC++이나 C++처럼 8

OS 에서 빌려와서 할래?(하는 거에 비하면 프로그래머가 해야 할 게 너무 많음.) 아니면 Java 나 C#은 자체적으로 지원하니까 쉽거든 이거 할래? 라는 문제가 되겠죠. 언어를 선택할 수 있는 상황이라면 Java 나 C#을 선택할 겁니다. 4baf: 코드 블록을 묶는다는 말에 있어서 물어볼게 있어요. 자바도 메소드 단위로 synchronized 하는 것 말고 class ThreadDemo private aobject = new Object(); public void dojob() 이것저것하기... synchronized(aobject) 동기화 부분 하면 코드블록을 처리할 수 있잖아요. 이것과 다른 것을 의미하는 건가요? 그리고 다른 얘기 하나 더, EJB 를 쓰면 스레드 동기화까지 알아서 되는데, 어플리케이션 개발자 입장에서 그런 컨설턴트가 필요한가요? Trax : 스레드에서의 동기화 관점에서라면... -_-. C#과 Java 모두 Interlocked 클래스를 사용합니다. 하지만 이것은 하나의 변수 정도만 동기화를 유지하는데 사용할 수 있는 수단입니다. 문자열이나 객체를 담고 있는 것을 동기화 할 수는 없습니다. 또한 모든 것을 일일이 동기화하기 위해 같은 코드를 사용하면 코드가 상당히 지저분해 지겠지요. -_-. 그래서 이보다 한발 더 나아가서... Lock 객체를 통한 Lock 을 지원합니다. 즉, Interlocked 는 값을 하나 바꾸는 데는 유용하지만 그 이상에서는 유효하지 않습니다(코드만 지저분하게 만들뿐이지요). 그래서 C#에서는 lock 을 사용해서 다음처럼 코딩 할 수 있습니다. public void Increment() try while( counter < 1000) 9

lock(this) // 여기에 동기화할 코드를 위치 int temp = counter; temp++; Thread.sleep(1000); counter = temp; // end of lock // end of while // end of try catch ( ThreadInterruptedException) Console.WriteLine("Thread 0 interrupted!...", Thread.CurrentThread.Name); // end of catch 이 정도와 같을까요? 단순히 lock() 만으로 가능해집니다. Java 의 synchronized 와 비슷하지요. 하지만 보다 복잡한 프로그램에서 보다 정교하게 자원을 제어할 필요가 있다면 이것만으로 충분할까요? (저는 Java 가 스레드를 지원해도 이 부분에서는 기존의 언어들이 OS 에서 빌려오는 것 못지않게 복잡하고 어렵다고 생각하고 있습니다. -_-.) 예를 들어 보통의 스레드를 이용한 응용 프로그램들은 t1 스레드가 A 작업을 요청했는데 이미 a1, a2 와 같은 작업이 기다리고 있습니다. 그러면 보통의 경우에는 a1, a2, t1 의 순서로 스레드가 쌓입니다. 결국 t1 스레드는 여기서 병목을 일으키며 순서를 기다리게 됩니다. 그리고 다른 일을 할 수 있어도 하지 않게 됩니다. 만약 A 라는 작업이 처리시간이 긴 작업이라면 이 응용 프로그램은 여기서 모두 병목 현상을 일으키게 되고, 거의 모든 스레드는 여기서 작업을 기다리게 됩니다. 미들 티어에서 이렇게 된다면 어떻게 될까요? 그러면... 스레드가 어떤 작업을 요청했을 때 이미 사용중이라면 여기서 기다려야 할지, 다른 작업을 먼저 10

처리한 다음에 다시 돌아와서 작업을 처리할 것인지를 제어할 수 있어야합니다. 첫 번째 순차작업만을 하던 사람은 스레드를 접하면 작업 시간을 단축할 수 있다는 데에 매료되고 적용하게 됩니다. 그러나 시간이 지나면 이러한 문제점이 하나씩 나타나게 됩니다... 심지어는 평소에는 스레드간의 이동 작업과 같은 부하가 미미한 작업조차도 동시에 엄청나게 생성되는 스레드 숫자로 인해서 시스템이 반응조차 하지 않을 정도로 죽어버리고, 대부분의 CPU 사이클를 스레드의 작업 처리가 아니라 스레드의 변환작업에 소비하게 되는 현상을 겪게 됩니다. 모니터는 동기화에 있어서 동기화를 할 부분과 하지 않을 부분을 프로그래머가 결정할 수 있도록 해줍니다. 또한, 다른 코드 영역이 해제가 될 때까지 기다릴 수 있도록 해줍니다. 일종의 smart lock 의 개념이 모니터입니다. 모니터를 사용할 수 없으면, 모니터가 보호하고 있는 객체가 사용중이 됩니다. lock() 대신에 Monitor.Enter(this); 와 같이 쓸 수 있습니다. 그리고 제어문들을 통해서 Monitor.Pulse(), Monitor.Exit(), Monitor.Wait()등으로 세세한 제어를 할 수 있습니다. 특히, Monitor.Pulse(this); 와 같은 구문을 통해 스레드의 상태 변경을 알 수 있습니다. 즉, 현재 free 인지 waiting 인지를 알아낼 수 있고 적절한 작업을 지시할 수 있습니다. 이외에도 수작업으로 스레드 생성과 동기화를 할 수도 있습니다. -_-. 경쟁 조건이나 교착 상태 등을 제어할 수 있다는 거겠지요. -_-. 현재 Ultra Editor 등을 보면 파일이 외부에서 변경된 것을 알아냅니다. 심지어 삭제된 것도 알아내고 질문을 합니다. 이것이 의미하는 것은? 즉 스레드의 처리입니다. 파일의 처리자체가 멀티 태스크 OS 에서는 중요한 문제가 되는 것입니다. -_-. 옛날 버전의 Ultra Editor 가 이런 것을 알았을까요? 모릅니다. -_-. 어떤 스레드가 파일을 열려고 하고, 어떤 스레드는 파일을 쓰려고 한다. 그러면 이도 저도 못하고 교착상태에 빠지겠지요? 어쨌거나 이러한 것을 간단하게 할 수 있다는 것입니다. Java 에 비하면 더 세세하게 제어할 수 있다고 생각합니다. 물론, 모니터까지 사용하는 사람은 많지 않을 겁니다. 지금까지는 모니터와 같은 구현은 직접 하거나 서드 파티에서 라이브러리를 구입하거나 하는 둘 중 하나였고 직접 구현하는 것도 어려운 것은 마찬가지입니다. -_-. C#은 이걸 쉽게 만들었으니 더 많이 쓰지 않을까라는 생각입니다. 객체 지향 언어에서 자주 부딪히는 문제에 대해서 다음과 같은 디자인 패턴으로 스레드 동기화 문제를 다룰 것입니다. 상호 배제(mutual exclusion) 데이터나 코드와 같은 시스템 자원이 동시에 둘 이상의 스레드에 의해서 안전한 방법으로 접근될 수 없을 때 사용하는 방법입니다. 단일 허용 관문(single admission gate) 동시에 하나의 스레드만이 특정 코드 영역(specific code area)을 실행하도록 하지만 같은 코드를 수행하려는 다른 스레드를 막지 않습니다. 대신에 보호된 코드 영역에 들어가려고 하는 스레드는 바로 그 함수로부터 반환됩니다. 11

경계적 대기(alertable wait) 위에 제가 예로 든 것입니다. 일반적으로 스레드는 자원이 사용 가능할 때까지 기다려야 하지만, 경우에 따라서는 대기 상태(waiting)에서 자원이 사용 가능할 때 까지 기다리기 전에 리턴해야 할 필요가 있을 때 사용합니다. -_-. 제한된 자원에 대한 경쟁(race condition) 아마 가장 고전적인 문제인데 식사하는 철학자 문제로 잘 알려져 있습니다. 테이블에 5 개의 스파게티 접시와 5 개의 포크가 있습니다. 5 명의 철학자가 있고 철학자는 식사하는 것과 생각하는 것만 할 수 있으며, 스파게티를 먹기 위해선 두 개의 포크를 사용해야 합니다. 제한된 철학자만 식사를 하는 식으로 루프를 돌면 엄청나게 많은 불필요한 CPU 자원을 소비하게 될 것이고, 모든 철학자가 오른쪽 포크를 들면 왼쪽 포크를 들 수 있을 때까지 기다리게 되는 교착상태에 빠지게 될 겁니다. 예, 여기서 등장하는 개념이 뮤텍스입니다. 생산자와 소비자 문제(producer/consumer) 이것은 효율을 극대화하기 위해 다루는 문제입니다. 생산자는 최대한의 데이터를 생산하고, 소비자는 최대한 데이터를 소비해야 합니다. 그리고 이들 생산자와 소비자는 전혀 별개의 독립되어 있어야 합니다. 그렇지 않다면 생산자가 데이터를 생산하는 동안 소비자는 아무것도 못하고, 소비자가 데이터를 소비하는 동안 생산자는 아무것도 생산하지 못하게 될 것입니다. (반대로 생각하면 소비할게 없어서 소비자가 놀고, 생산할게 없어서 생산자가 논다고 생각해보시길...) 이러한 생산자와 소비자사이에 데이터를 주고 받는 메커니즘이 필요합니다. (얼마를 생산했다, 얼마를 소비했다.. 소비할 데이터가 얼마가 필요하다 등등.) 이것은 단일 생산자와 단일 소비자 문제와 다중 생산자와 다중 소비자 문제로 깊이 있게 나누어집니다.(물론, 단일 생산자와 다중 소비자, 다중 생산자와 단일 소비자로 있습니다. 총 4 가지의 조합 가능한 문제) 자자... 스레드에서 이 정도 디자인 패턴이 '기초'에 해당합니다. 더 깊이 들어가 볼까요? 객체와 스레드의 동기화의 문제가 생깁니다. 내부에서 동기화하고, 클라이언트(즉 객체를 이용하는 어플)에서는 동기화에 관여를 못하게 할 것인가(내부 동기화), 아니면 객체에서 동기화를 제공할 것인가(외부 동기화)의 문제가 있고 이것 역시 어떤 경우냐에 따라 다릅니다(답이 없음). 데이터 저장은 단일 스레드로 통일할 것인가, 다중 스레드로 분할하여 처리할 것인가라는 문제가 있고, 저장소는 정적 스레드 로컬 저장소를 사용할 것인가, 동적 스레드 로컬 저장소를 사용할 것인가...(즉, 정정 TLS 와 동적 TLS 문제로 나뉘게 됨) 그리고 이러한 것들을 뛰어넘어 스레드 풀을 구현하게 됩니다. 보통의 경우에 이러한 스레드 풀은 MTS 나 COM+에서 제공하게 됩니다. 그리고 이것은 CLR 에서도 마찬가지입니다. 스레드 풀의 구현이 필요 없습니다. 그러나 만약 스레드풀의 구현이 필요하다면? (온라인 게임 사이트나 모바일 관련 기기의 과금 시스템을 위한 Proxy 를 VC++ 이나 Solaris 에서 12

C++로 개발하게 되는 상황이라면? ) 멀티 스레드를 지원하는 DLL 의 작성과, 멀티 스레드 인터페이스까지 갈 수 있겠지요. (즉, 작업이 많아도 화면 반응이 좋고, 빨리 빨리 동작하는 것처럼 보이게 만드는 거겠지만) 멀티 스레드 GUI 역시 상당히 방대한 내용을 담고 있습니다.(말처럼 간단하지도 않고 생각처럼 간단하지도 않습니다.) 이제 개발이 종반에 이르면 어떤 문제가 생길까요? 멀티 스레드 응용 프로그램은 디버깅이 상상을 초월할 정도로 어렵습니다. PC 를 두 대 연결해서 Remote Debugging 을 해야 할 정도랍니다. -_-. 예외 처리도 어렵지요... 그에 비하면 C#은 상당히 쉬워서... 그 정도의 고민은 안해도 됩니다. ^^ 하지만 그래도 고민은 남는게... 어떤게 주 스레드인지 찾아내는 문제 그리고 지금 내가 어떤 스레드를 디버깅하고 있는지를 알아야 한다는 거지요. A 라는 작업을 동시에 처리하는 스레드가 10 개 있는데 그 중에 하나가 문제를 일으킨다면? 이러한 많은 문제들이 발생하게 되고, 프로세스와 스레드에 대해서 자세히 알지 않고 응용 프로그램에 적용하게 되면 위와 같은 문제들이 동시에 발생하기 때문에 스레드는 어려운 것이라는 인식이 있습니다. 4baf: Trax 님 말씀 잘 들었습니다. 말씀 중에서 나온 소비자/생산자 문제 등 4 가지인가 예를 드신 패턴들은 어떤 책을 통해서 전문적으로 공부 할 수 있나요. 소개 부탁드립니다. 또 한가지, C#에서 쓰레드 프로그래밍시 예외처리가 간단하다고 했는데 자바의 그것에 비해 저는 C#의 예외처리가 별로 안좋다고 생각하거든요. 자바는 RuntimeException, 혹은 그것의 derived exception 을 제외하고 모든 메소드는 그것이 스스로 catch 하지 않는 exception 에 대해서 throws 절을 추가해야 합니다. 하지만 C#은 그렇지 않죠 예를 들면 public void thismethodthrowsexception() thorws new SQLException thorw new SQLException(); 이런 식으로 구현해야만 해요. 그리고 이 메소드가 던지는 예외는 쭉 위로 올라가다가 어느 시점에서든 반드시 catch 가 되어야 합니다. 안그러면 컴파일타임 에러죠. 제가 C#은 catch()를 반드시 할 필요가 없던 것 같은데요. 그렇다면 어떤 예외를 던져지는지 명확한 검사가 이루어지지 않고 나중에 문제발생 소지도 커지는 것 같은데, 어떻게 생각하세요. Trax 님께서 말씀하신 그 예외처리의 강력함이 자바와 비교하여 어느 것이 더 낫다고 하신 것인지 잘 이해를 못하겠어요. 그리고 제가 약간의 태클을.. 위에 예로 드신 코드를 자바로 짜볼께요. public void Increment() try 13

while( counter < 1000) synchronized(this) // 여기에 동기화할 코드를 위치 int temp = counter; temp++; Thread.sleep(1000); counter = temp; // end of lock // end of while // end of try catch (InterruptedException ie) System.out.println("Interrupted at : " + new Date()); // end of catch '모니터'란 용어의 정확한 의미는 모르지만 자바역시 C# 과 거의 다르지 않게 구현할 수 있습니다. 위처럼요. this 인스턴스에 대한 모니터 객체를 획득하죠. 물론 wait() notify() notifyall() 과 같은 거도 가능하지요. 이 메소드들은 java.lang.object 에 정의되어 있으며 Object클래스는 C#의 object처럼 모든 클래스의 parent입니다. 모든 클래스가 이용 가능하죠. 다만 pulse()같은 메소드는 없습니다. C#이 다양한 쓰레드 지원 클래스가 있다는 것은 알고 있습니다. 자바에는 그게 부족하죠. 하지만 기본적인 모니터는 위처럼 지원한답니다. JAVAONE 같은 데 보면 외국 사람들이 많든 mutex클래스를 가지고 세미나도 하더군요. 썬측에 이런 클래스들을 지원해달라고 요청도 하구요. Trax: 4baf님이 물어보신 별도의 책은 없습니다. 디자인 패턴에 대한 것들은 parting님이 잘 아시기 parting님께 물어보도록 하죠. 참고가 될지는 모르겠지만 Win32 멀티스레드 프로그래밍 이 있고, 여기서 많은 기본 지식을 얻을 수 있을 것 같습니다. C#으로 된 책은 있을 리 없겠고, 14

디자인 패턴은 Java가 비교적 광범위하게 되어 있습니다. Java가 전문이시니 Java 관련서로 보는 게 좋을 것 같습니다. 쉽게 볼 수 있는 거로는 VIsual Basic Design Patterns 가 있습니다. 4baf님이 얘기하신 것처럼 C#은 throw를 반드시 명시할 필요는 없지만 C#도 throw를 지원합니다. Java의 경우도 C#과 비슷하지만 모든 예외는 Exception 클래스에서 파생된 것이고, 이것들은 모두 동일합니다. 즉, 다른 점은 없습니다. 메소드, 속성도 같고, 하는 동작도 같습니다. 다만 InvalidCastException과 같이 이름만 다를 뿐입니다. 즉, 이러한 예외 처리의 목적은 어떤 것에서 예외가 발생했는가를 알아내기 위한 것일 뿐입니다. 다시 말하면, 여기서 걸러지지 않으면 다시 상위로 올라간다는 것입니다. public void thismethodthrowsexception() thorws new SQLException thorw new SQLException(); > 즉, 위의 Java 코드와 같이 throws new 를 장황하게 써가면서 선언문을 길게 하지 않아도 됩니다. 중간에 얼마든지 원하는 예외를 잡기위해 catch 를 여러번 쓸 수 있습니다. try if () throw new exception ("망할~ 에러났잖아~!"); catch ( InvalidCastException e) catch ( Exception e) 15

이와 같이 예외를 던질 수도 있고, 다른 예외를 처리할 수도 있습니다. 이것은 C# 코드입니다. Exception 은 가장 상위입니다. 즉, 모든 예외는 Exception 에서 잡을 수 있지만, 그렇지 않고 InvalidCastException 에서만 잡고 싶다면 그렇게 할 수 있습니다. 어차피 나머지는 건너 뛰게 됩니다. public void Increment() try while( counter < 1000) synchronized(this) // 여기에 동기화할 코드를 위치 int temp = counter; temp++; Thread.sleep(1000); counter = temp; // end of lock // end of while // end of try catch (InterruptedException ie) System.out.println("Interrupted at : " + new Date()); // end of catch '모니터'란 용어의 정확한 의미는 모르지만 자바역시 C# 과 거의 다르지 않게 구현할 수 있습니다. 위처럼요... this 인스턴스에 대한 모니터 객체를 획득하죠. 물론 wait() notify() notifyall() 과 같은 거도 가능하져. 이 16

메소드들은 java.lang.object 에 정의되어 있으며 Object 클래스는 C#의 object 처럼 모든 클래스의 parent 입니다. 모든 클래스가 이용가능하죠. 위 코드는 제가 C#에서 작성한 lock()에 대해서 4baf 님이 Java 로 다시 작성하신 겁니다. C#에서 더 풍부하다고 얘기했던 부분은 synchronized(this) 이 부분에서 동기화가 들어가면 어디서 나올 수 있는가? 라는 것입니다. 단지 선언된 시점에서부터 모든 것이 동기화에 들어가는 것이지요 (제가 잘못 이해하고 있는 건지? -_-.) 하지만 C#에서는 언제 어디서나 들어갔다 나올 수 있습니다(Monitor.Enter, Monitor.Exit). 그리고 스레드가 대기 상태에서 자유가 되었는지, 자유 스레드가 대기 상태가 되었는지를 알아낼 수 있는 Pulse() 등을 쓸 수 있어서 스레드를 보다 손쉽게 정교하게 제어할 수 있습니다. -_-. Java 에서 mutex 클래스를 구현한 것도 있다고 하셨는데, 사실은 mutex 뿐만 아니라 스레드와 그에 따른 문제점들을 보다 효율적으로 처리하기 위한 디자인 패턴들이 있습니다. 4baf : 예, 말씀하신 것은 알겠습니다. Java 의 synchronized 가 C#의 lock 이라 생각하면 됩니다. Java 에는 모니터 클래스라는 게 따로 있지는 않습니다. 제가 말한 C#의 exception 단점은, 그 exception 을 반드시 catch 하지 않아도 된다는 것입니다. 자바는 반드시 메소드내에서 발생한 에러를 catch 하거나 혹은 throws 를 씀으로써 catch 하지 않겠다고 명시해야 합니다. public static void main() 메소드 내에서는 throws 를 명시할 수 없고, throws 로 선언된 메소드들을 호출한 경우 반드시 catch 해야 합니다. 즉 프로그램내에서 어디선가 exception 이 반드시 처리된다는 의미이고, C#은 그렇지 않습니다. 콘솔 어플리케이션이나 엔터프라이즈 솔루션을 C#으로 만든 경우 예외하나 catch 안했다가 시스템 exit()될겁니다. 자바의 쓰레드에 관한 제 두개의 코드중 첫번째 aobject 를 synchronized 시킨 경우는 aobject 에 대한 모니터를 획득했고, 두번째 코드는 this 인스턴스에 대한 모니터를 획득했습니다. 각각 aobject, this 에 대한 모니터를 획득하고자 하는 코드들은 해당 블럭 ( 로 시작하고 로 끝나는)을 실행중인 쓰레드가 있는 동안은 synchroznied 블럭 앞에서 대기하게 됩니다. 이런 면에서 C#의 lock 과 동일합니다. Nuthack : perl 의 멀티 쓰레드 예제 입니다..-_-.;; (Trax : 드디어 등장하셨군요.. ^^) use Thread; my $t1 = new Thread &start_sub; my $t2 = new Thread &start_sub; $t1->join; $t2->join; 17

while ( 1 ) print "I am the main threadn"; sleep 1; sub start_sub my $tid = Thread->self->tid; while ( 1 ) print "I am thread $tidn"; sleep 1; 4baf : 혹시 디자인패턴 자료 알고 계시면 좀 알려주세요. 제가 본 패턴책은 Design Patterns Java Companion 입니다. 이 책은 Java와 관련된 소스로 꽉 찬 책이었죠. parting : 4baf님이 보신 것 외에 주로 스몰토크 관련 사이트를 가면 많습니다. 스몰토크가 순수 객체 지향을 추구하다 보니 디자인 패턴 연구가 활발했었다는 메이저급 스몰토크 개발환경 개발사들에 가면 자료 꽤 있습니다. 제가 추천하는 자료는 www.object-arts.com(돌핀 스몰토크 만드는 회사)에 있는 튜토리얼하고, www.aw.com(애디슨 웨슬리입니다. 설마 모르는 사람이야 없겠지)에 가면 design pattern with smalltalk companion 이라는 책에 실린 디자인 패턴 예제들이 PDF 문서로 들어있더군요. 저는 디자인 패턴이 심각하게 필요한 사람이 아니라서 사실 별 관심은 없습니다. 스몰토크 공부하다가 우연히 봤는데 Trax님 말을 들어보니 '아하, 저게 그렇게 중요한 거였구나~'하는 -_-;; 저는 디자인 패턴을 그냥 빵굽는 틀로밖에 생각을 안하기 땜에.. -_-; 제 목표는 빵공장이 아니라 집에서 구워먹는 맛있는 빵인 관계로 ^^;; Nuthack : 상호 배제(mutual exclusion)라는 게 뮤텍스가 아닌가요? Trax : 예, 상호 배제(mutual exclusion)가 뮤텍스(mutex) 맞습니다. 이것도 디자인 패턴을 이용해서 개발자가 구현을 쉽게 할 수 있습니다(말은 쉽지요... -_-.). 그래서 실제로 직접 구현하는 프로그래머들도 많습니다. 일반적으로 OS가 제공하는 뮤텍스는 자신들의 API에 적합하게 구현한 것이지요. 18

이것은 경쟁 조건에 있는 자원들에 대해서 루프를 돌면서 순차적으로 처리하느라 CPU 자원을 소모하는 대신에 일정 개수의 토큰을 발급하는 것입니다.. 식사하는 철학자 문제도 역시 이러한 토큰의 발급... 즉, 토큰을 받으면 식사를 하고 토큰이 없으면 생각하도록 하는 것입니다. 정리 꽤 긴 시간동안의 토의였던 것 같습니다. 특별히 내릴만한 결론은 없었던 것 같지만, 스레드에 대한 얘기와 디자인 패턴, 그리고 Java 와 C#에서 구현하는 스레드에 대해서 좋은 얘기를 나눈 것 같습니다. 부디 이 토론이 저희 토론자 뿐만 아니라 다른 분들에게도 도움이 되고, 영감을 줄 수 있었으면 좋겠습니다. 주요 논의자 4baf - OCP, Java Programmer 이며, 현재는 C#으로 닷넷 환경에서 웹 서비스를 이용한 솔루션 개발 parting - TFT 팀 팀장이며, 주요 관심사는 스몰토크와 디자인 패턴이며 현재는 닷넷 웹 서비스 솔루션을 위한 설계를 하고 있음. Trax - Solaris System Administrator 로 일했으며, EIP 솔루션 개발을 했으며, 소일거리로 관심을 갖고 있는 분야는 C#이며 프로그래밍 이론 전반에 대해 관심을 갖고 있다. 현재는 무위도식중. Nuthack - 진짜 필명은 '골빈해커'이며, 열렬한 Perl 지지자로 이 세상 모든 것을 다시 Perl 로 만들고 싶다고 한다. 주로 Python, Ruby, Perl 과 같은 스크립트 언어를 다룬다. Dicajohn - 삼성 소프트웨어 멤버십 회원이며, Java 와 모바일 프로그래밍이 주요 관심사다. 이번 기사 '스레드, 그리고 Java 와 C#'는 앞으로 우리나라를 이끌어갈 차세대 프로그래머들이 스레드에 대해 수 시간에 걸쳐 메신저로 논의한 내용을 한동훈님께서 정리한 토론 내용입니다. 19

C# 쓰레드 이야기: 1. 쓰레드는 무엇인가? by 한동훈(traxacun@unitel.co.kr) 요구사항:.NET Framework SDK beta 2 우리가 흔히 사용하고 있는 OS는 '멀티 OS'라고 한다. 이것의 의미는 동시에 여러 가지 작업을 한다는 것을 뜻한다. MP3 를 들으며 워드를 작성하면서 인터넷 서핑을 할 수 있다. 이때 각각의 응용 프로그램은 하나의 프로세스를 갖는다. 그러니까 MP3 플레이어도 하나의 프로세스이고 워드 프로세서도 하나의 프로세스이고, 인터넷 브라우저도 하나의 프로세스라는 뜻이다. 반면에 쓰레드는 프로세스를 여러 개로 나눈 조각과 갖다고 설명할 수 있다. 워드 프로세서를 사용하는 경우를 예로 들자. 워드에서 글자를 입력하는 동안 파일을 디스크에 저장하고 있고, 내용을 프린터에 출력하고 있고, 입력하는 동안에 맞춤법 검사를 수행한다. 사용자의 입력을 받는 동안 행하는 이 모든 작업들은 각각의 쓰레드에 의해서 이루어진다. 글자를 입력 받는 쓰레드, 파일을 디스크에 저장하는 쓰레드, 출력할 내용을 프린터에 보내는 쓰레드, 입력하는 동안 맞춤법 검사를 수행하는 쓰레드 등이 있다. 즉, 워드 프로세서라는 큰 프로세스 하나에 여러 개의 쓰레드가 모여있는 것이다. 실제로 프로세스는 하나의 어드레스 공간을 갖고 있고, C# 에센스 모든 응용 프로그램은 메인 응응 프로그램을 위한 하나의 쓰레드를 갖는다. 그리고 여기에 다른 쓰레드들이 함께 수행될 수 있고, 각각의 쓰레드들은 자신을 관리하는 프로세스의 어드레스를 갖고 있다. 즉, 프로세스는 쓰레드에 대한 일종의 컨테이너역할을 한다. 쓰레드를 이용하면 얻을 수 있는 이점은 무엇인가? 사실 필자는 쓰레드를 그다지 이용하지 않기에 잘 모르지만, 다른 사람들의 얘기를 들어보면 잘 쓰면 명약이요, 잘못 쓰면 독약인 것이 쓰레드라고 하는 것 같다. 쓰레드를 이용하면 하나의 프로그램에서 한 번에 하나의 일을 처리하는 것이 아니라 동시에 많은 일을 처리할 수 있다는 장점이 있다. 뿐만 아니라 같은 일을 더 빠른 시간안에 처리할 수 있을 것이다. 처리 시간이 오래 걸리는 작업에 대해서 쓰레드에게 처리를 맡기고, 다른 일을 계속해서 처리할 수도 있는 것이다. 다음 그림을 보도록 하자. 20

그림에서처럼 하나의 프로세스에서 처리해야하는 세 가지의 작업 A, B, C 가 있고 각각의 처리시간이 위의 길이와 같다고 할 경우에 첫번째와 같이 순차적으로 처리하는 경우보다는 두 번째와 같이 쓰레드를 이용하여 동시에 처리하는 것이 처리시간이 더 짧다는 것을 알 수 있을 것이다. 이러한 쓰레드의 위력은 많은 동시 사용자를 처리하는 환경이나 한 번에 많은 작업을 처리하는 응용 프로그램에서 그 위력을 발휘할 것이다. 이제 간단한 쓰레드 프로그램의 예제를 살펴보도록 하자. SimpleThread.cs using System; using System.Threading; public class Tester public static void Main() Tester t = new Tester(); t.dotest(); public void DoTest() 21

Thread t1 = new Thread( new ThreadStart(WorkerThreadMethod) ); Console.WriteLine("쓰레드를 생성"); t1.start(); Console.WriteLine("쓰레드 시작을 요청 받았음"); public void WorkerThreadMethod() Console.WriteLine("WorkerThreadMethod 시작했음"); 각각의 코드를 살펴보도록 하자. using System; using System.Threading; System은 C#에서 응용 프로그램 개발에 있어서 필요하며 여기서는 Console.WriteLine을 사용하기 위해 선언했다. 중요한 것은 두 번째 문장으로 쓰레드 프로그래밍을 하기 위해서는 System.Threading 네임 스페이스를 선언하기만 하면 된다. (아 깜빡한 사실이 있는데, C#에서는 쓰레드를 지원한다. Java의 쓰레드와 비슷하다고 생각하면 된다. C#과 Java의 쓰레드 지원에 대한 논의는 쓰레드, 그리고 Java와 C#을 참고하기 바란다) public class Tester public static void Main() 22

Tester t = new Tester(); t.dotest(); Tester 라는 임시 클래스를 만들었고 Main() 함수를 선언했다. 여기서는 Main() 함수에 직접 함수를 두지 않고 DoTest()와 같은 별도의 함수를 만들어서 처리하고 있다. Main()은 응용 프로그램의 진입점(entry point)으로 사용하고, 실제 처리 코드는 별도의 함수로 만들어서 처리하는 것을 권한다. Thread t1 = new Thread( new ThreadStart(WorkerThreadMethod) ); 여기는 쓰레드를 생성하는 부분이다. t1 이라는 쓰레드를 생성하고, 쓰레드에서 호출할 함수는 ThreadStart(WorkerThreadMethod)와 같이 사용한다. WorkerThreadMethod()는 함수이므로 ThreadStart(WorkerThreadMethod())와 같이 사용해도 된다. 주의할 점은 ThreadStart 에 지정하는 함수는 반드시 void 함수이어야 한다는 것이다. t1.start(); 쓰레드의 시작을 요청한다. Start() 함수를 만나기 전까지 쓰레드는 시작되지 않는다는 것도 알아두자. public void WorkerThreadMethod() Console.WriteLine("WorkerThreadMethod 시작했음"); 쓰레드가 사용하는 함수를 정의한 부분이다. 위에서 알 수 있는 것처럼 쓰레드에 의해서 호출되는 메소드는 반드시 void 형으로 정의해야 한다. 다음에는 여러 개의 쓰레드를 동시에 처리하는 멀티쓰레드에 대해서 알아보도록 하자. 그때까지 심심하더라도 잘 지내기 바란다. 23

C# 쓰레드 이야기: 2. 다중 쓰레드 by 한동훈(traxacun@unitel.co.kr) 지난 시간(1. 쓰레드는 무엇인가?)에는 간단한 쓰레드를 생성하는 방법에 대해서 살펴보았다. 이번에는 동시에 여러 개의 쓰레드를 다루는 다중 쓰레드에 대해서 알아보자. 여러 개의 쓰레드를 이용하는 프로그램을 작성하는 것은 쉽다. 원하는 수 만큼 쓰레드를 생성하는 프로그램을 작성하기만 하면 된다. 여기서는 하나의 응용프로그램이 세 개의 쓰레드를 갖고 있고, 각각의 쓰레드는 인쇄하기, 저장하기, 철자 검사하기 작업을 시뮬레이션 한다고 가정한다. namespace csharp using System; using System.Threading; csharp 라는 네임 스페이스를 만든다. 기본 라이브러리인 System 네임 스페이스와 쓰레드를 사용하기 위해 System.Threading 네임 스페이스를 선언한다. class MultiThreadApp public static void Main() MultiThreadApp app = new MultiThreadApp(); app.dotest(); 클래스 명은 MultiThreadApp 로 하고 Main() 메소드에서 MultiThreadApp 클래스의 인스턴스를 하나 생성한다. 그리고 실제 코드는 DoTest() 함수에서 처리하기 때문에 Main()에는 간단한 코드만이 필요하다. private void DoTest() Thread t1 = new Thread( new ThreadStart(DoPrinting) ); 24

Thread t2 = new Thread( new ThreadStart(DoSpelling) ); Thread t3 = new Thread( new ThreadStart(DoSaving) ); t1.start(); t2.start(); t3.start(); 먼저 각각의 쓰레드 t1, t2, t3 를 만들고, 각 쓰레드에는 DoPrinting, DoSpelling, DoSaving 메소드를 위임한다. 쓰레드의 시작을 위해 각 쓰레드의 Start()를 호출한다. 이제 각각의 쓰레드를 시작하도록 했으므로 각각의 함수 DoPrinting DoSpelling, DoSaving 을 살펴보도록 하자. private void DoPrinting() Console.WriteLine("인쇄 시작"); for (int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(120); Console.Write("p "); Console.WriteLine("인쇄 완료"); 먼저 쓰레드를 이용하는 메소드는 void 타입이어야 한다고 설명했었다. 즉, 반환값을 갖지 않는다. 인쇄 시작을 출력하고 100 번의 루프를 돌도록 한다. Thread.Sleep(120)은 쓰레드에게 120ms 동안 기다리라는 것을 의미하고, 진행중임을 알기 위해 여기서는 루프를 돌 때마다 p 을 출력한다. 그리고 모든 작업이 끝나면 '인쇄 완료'를 출력한다. DoSpelling(), DoSaving()도 이와 같은 역할을 하는 코드이며, 쓰레드를 이용해서 동시에 처리하고 있음을 설명하기 위해 Thread.Sleep()의 시간을 각각 다르게 지정했다. 전체 소스는 다음과 같다. 25

<B<MULTITHREADAPP.CS< b> namespace csharp using System; using System.Threading; class MultiThreadApp public static void Main() MultiThreadApp app = new MultiThreadApp(); app.dotest(); private void DoTest() Thread t1 = new Thread( new ThreadStart(DoPrinting) ); Thread t2 = new Thread( new ThreadStart(DoSpelling) ); Thread t3 = new Thread( new ThreadStart(DoSaving) ); t1.start(); 26

t2.start(); t3.start(); private void DoPrinting() Console.WriteLine("인쇄 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(120); Console.Write("p "); Console.WriteLine("인쇄 완료"); private void DoSpelling() Console.WriteLine("철자 검사 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(100); Console.Write("c "); Console.WriteLine("철자 검사 완료"); 27

private void DoSaving() Console.WriteLine("저장 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(50); Console.Write("s "); Console.WriteLine("저장 완료"); 이 코드의 컴파일은 다음과 같이한다. csc /t:exe /out:multi.exe MultiThreadApp.cs 코드를 실행하면 결과는 다음과 같다. 결과에서 알 수 있는 것처럼 파일을 디스크에 저장하는 작업을 가장 먼저 시작하고(t1), 그 다음에 인쇄 작업(t2), 철자 검사(t3)를 시작한 것을 알 수 있다. 그러나 각각의 작업을 뜻하는 알파벳을 출력하도록 한 결과를 유심히 살펴보면 s c s p s c 와 같이 각각의 작업이 뒤죽박죽으로 섞여서 동시에 처리되고 있다는 28

것을 알 수 있을 것이다(Thread.Sleep() 함수에 각각 다른 지연 시간을 지정했기 때문에 이와 같은 결과를 나타낸다. 이 부분을 제거하면 각각의 작업은 같은 비율로 처리 시간을 갖게 될 것이다). 또한 가장 마지막에 시작했던 철자 검사 작업이 두 번째로 끝난다는 것을 알 수 있다. 쓰레드를 동시에 처리하는 것을 조금 더 탐구하고 싶다면 각 Do 함수의 루프 카운터와 Thread.Sleep()의 지연 시간을 바꿔가면서 테스트 해보기 바란다. 다음은 위 예제에서 각각의 쓰레드를 생성해서 처리하던 것을 쓰레드의 배열로 만들어서 보 다 간결하게 쓰레드를 처리하는 예제다. 위 코드에서 DoTest() 함수만 변경되었다. private void DoTest() Thread[] athread = new Thread( new ThreadStart(DoPrinting) ), new Thread( new ThreadStart(DoSpelling) ), new Thread( new ThreadStart(DoSaving) ) ; foreach( Thread t in athread) t.start(); 다음 단계 다음 시간에는 쓰레드를 종료하는 방법과 예외처리, 백그라운드 처리와 같은 방법에 대해서 알아볼 것이다. 쓰레드에 대한 기본 개념을 코드를 통해서 충분히 연습하고 익힌 다음에는 다중 쓰레드 응용 프로그램에서 발생할 수 있는 많은 문제들에 대해서 살펴볼 때 많은 도움이 될 것이다. 천리길도 한 걸음부터 우리가 아직 오를 계단은 많이 남아있다. 다음 계단을 오를 때 까지 코드를 변형해보면서 테스트하고, 보다 많은 것들을 살펴보고자 한다면 온라인 도움말을 찾아보기 바란다. 궁금한 사항이 있다면 게시판이나 전자우편으로 질문하기 바란다. 29

C# 쓰레드 이야기: 3. 쓰레드 제어 편집자주: 모든 코드는.NET Framework beta2(1.0.2419)와.net Framework RC(1.0.3328.4)에서 테스트되었다. 지난 시간(2. 다중 쓰레드)에는 여러개의 쓰레드를 생성하는 방법에 대해서 간단히 살펴보았다. 생각처럼 어렵지 않았을 것이다. 단순히 여러개의 쓰레드를 생성하고 각각의 함수를 위임하고 실행하면 그만이었을 것이다. 쓰레드 기다리기(Joining Thread) 일반적인 쓰레드 처리는 코드의 수행과 상관없이 계속해서 실행된다. 만약, 여러분이 쓰레드를 이용해서 긴 시간 동안 처리해야 하는 작업을 맡았고, 그 작업이 끝나면 결과를 받아서 화면에 출력을 하거나 계산을 하는 프로그램을 작성한다고 생각해보자. 이 경우에 쓰레드에서 처리하고 있는 작업이 끝나기도 전에 프로그램은 이미 출력이나 계산을 수행하는 코드를 실행하게 된다(아마도 에러를 만나게 될 것이다). 이러한 경우에 화면에 출력하거나 계산을 수행하기 전에 쓰레드가 끝나는 것을 기다려야 한다. 먼저, 쓰레드를 기다리지 않을 경우에 어떤 일이 일어나는지 알아보기 위해 지난 시간에 사용했던 코드를 이용해 보도록 하자. 지난 시간에 사용한 코드에서 다음부분을 찾아서 수정한다(빨간색으로 표시된 부분). private void DoTest() Thread[] athread = new Thread( new ThreadStart(DoPrinting) ), C# 에센스 new Thread( new ThreadStart(DoSpelling) ), new Thread( new ThreadStart(DoSaving) ) ; foreach( Thread t in athread) t.start(); 30

athread[1].interrupt(); Console.WriteLine("모든 쓰레드가 종료되었습니다"); 또한 모든 Do 함수에서 Thread.Sleep(50); 과 같이 모두 같은 시간동안 수행되도록 한다. 코드를 다시 컴파일하고 실행하면 다음과 같은 결과가 나타날 것이다. 코드가 쓰레드로 수행되고 있기 때문에 실제로 모든 쓰레드가 종료되지 않았는데 '모든 쓰레드가 종료되었습니다'라는 메시지가 중간에 나타나는 것을 알 수 있을 것이다. 모든 쓰레드가 끝난 다음에 '모든 쓰레드가 종료되었습니다'라고 나타나도록 하려면 다음과 같이 코드를 변경하면 된다. foreach( Thread t in athread) t.start(); athread[0].join(); // 인쇄 작업 athread[1].join(); // 철자 검사 athread[2].join(); // 저장 하기 31

Console.WriteLine("모든 쓰레드가 종료되었습니다"); foreach 루프문에서 Join()을 수행하지 않고, 바깥에서 쓰레드의 배열에서 직접 Join()을 수행한다는 것에 주의하기 바란다. 각각의 쓰레드는 인쇄 작업, 철자 검사, 저장 하기를 수행한다. 또한 모든 쓰레드가 종료될 때까지 Console.WriteLine()은 수행되지 않는다. 결과에서 알 수 있는 것처럼 모든 쓰레드가 끝난 다음에 Console.WriteLine()이 실행되는 것을 알 수 있다. 이와 같이 각각의 쓰레드가 독립적으로 처리되고, 모든 쓰레드가 종료된 다음에 특정 코드를 실행하기 위해서 위와 같이 Join()을 사용할 수 있다. 쓰레드 t1 과 t2 가 각각 작업을 수행하고 있을 때, t2.join(); 과 같은 코드를 넣으면, 쓰레드 t1 에서 수행중인 함수의 실행을 중지하고, 쓰레드 t2 가 종료될 때 까지 실행을 기다리게 된다. 위에서는 athread[0], athread[1], athread[2]에 대해서 각각 Join()을 호출하였다. 각 쓰레드 athread[0], athread[1], athread[2]는 응용 프로그램 쓰레드에 대해서 Join()을 수행한다. 따라서 응용 프로그램 쓰레드는 실행을 중지하고 각각의 athread[0], athread[1], athread[2]의 실행이 종료될 때까지 기다리게 된다. 다시 정리하면 다음과 같다. athread[0].join(); // 응용 프로그램 쓰레드 정지하고 인쇄 쓰레드 완료를 기다림 athread[1].join(); // 응용 프로그램 쓰레드 정지하고 철자 쓰레드 완료를 기다림 athread[2].join(); // 응용 프로그램 쓰레드 정지하고 저장 쓰레드 완료를 기다림 첫번째 시간에 응용 프로그램의 프로세스와 쓰레드에 대해서 이야기 한 것을 기억하는가? 기억하지 못하는 독자를 위해서 잠시 기억을 되살려 보도록 하자. 응용 프로그램은 하나의 프로세스를 가진다. 이 프로세스는 실제로 하나의 쓰레드이며, 프로세스는 어드레스 공간에 대한 단순한 주소라고 이야기 했었다. 또한 프로세스는 32

단순히 쓰레드에 대한 컨테이너 역할을 한다고 얘기했었다. 다시 말해 각각의 athread[].join()은 응용 프로그램 쓰레드 Main()에 대한 Join()이라는 것을 알 수 있을 것이다. 여기서는 모든 쓰레드가 응용 프로그램 쓰레드에 대해서 Join()을 했으므로 모든 쓰레드가 종료될 때까지 응용 프로그램 쓰레드 Main()을 실행을 중지하고 기다린다. 반대로 athread[0].join();을 지우고, DoPrinting() 함수에서 Thread.Sleep(100);으로 다시 컴파일하여 실행해보면 다음과 같은 결과를 볼 수 있을 것이다. 결과에서 알 수 있는 것처럼 철자 검사 athread[1]과 저장 athread[2]가 끝나고 "모든 쓰레드가 종료되었습니다"라는 메시지를 출력한 다음에도 계속해서 athread[0]은 실행되는 것을 알 수 있다. 이렇게 실행되는 이유는 athread[1]과 athread[2]가 Join()을 수행하여 각각의 쓰레드가 종료 될 때 까지 응용 프로그램 쓰레드를 중지시키고 대기 상태로 두는 반면에 인쇄 작업을 수행하는 athread[0]은 응용 프로그램 쓰레드에 관계없이 동시에 실행되기 때문이다. Join 에서 주의할 점 위에서 Join 이 어떻게 작동하는 가에 대해서 자세하게 설명했다(어쩌면 독자들의 머리를 아프게 했을지도 모르겠다). 여러분은 여러 개의 쓰레드를 foreach 를 사용하여 멋지게 처리하는 것을 보았는데, 왜 루프안에 Join()을 넣지 않았는지 의아해 했을지도 모르겠다. '왜 루프안에서 Join()을 수행하지 않고, 루프 밖에서 일일이 수행하는 거지?' 이제, 이것에 대해서 설명하고자 한다. 먼저 다음과 같이 코드를 변경해 보도록 하자. foreach( Thread t in athread) t.start(); t.join(); 33

Console.WriteLine("모든 쓰레드가 종료되었습니다"); 여러분이 생각하는 것처럼 foreach 루프안에서 일괄적으로 쓰레드를 시작하고 Join()을 수행하고 있다. 코드를 다시 컴파일하고 결과를 살펴보면 다음과 같은 결과가 나타날 것이다. 결과에서 알 수 있는 것처럼 쓰레드로 동시에 처리되는 것이 아니라 순차적으로 실행되는 것을 알 수 있다. 루프안에서 Start()에 의해서 쓰레드가 시작되고, Join()에 의해서 응용 프로그램 쓰레드에 대해서 Join()을 수행하게 된다. 다시 말해, 응용 프로그램은 t.join();까지 실행한 다음에 쓰레드 t 의 수행이 끝날 때까지 모든 작업을 중지하고 기다리게 된다. 마찬가지로 두 번째 루프를 처리할 때도 같은 방식으로 동작한다. 때문에 쓰레드 프로그래밍을 하는 경우에는 이와 같이 루프안에서 Join()을 사용해서는 안된다. 쓰레드에 대해서 Join()을 수행하고 싶다면 다음과 같은 방식으로 코딩하기 바란다. foreach( Thread t in athread) t.start(); foreach( Thread t in athread) t.join(); 34

쓰레드를 시작하는 부분과 쓰레드를 Join()하는 부분을 각각 두 개의 루프로 분리해서 처리하도록 한다. Join 대기하기 먼저 각각의 쓰레드 t1, t2, t3 가 있다고 하자. 다음과 같이 모두 Join()을 수행하고 있다고 가정하자. t1.join(); t2.join(); t3.join(); 이 세가지 쓰레드는 각각의 쓰레드가 종료되고 제어권을 다시 넘길 때까지 응용 프로그램 쓰레드를 중지시킨다. 만약에 이 쓰레드 중에 어떤 쓰레드가 종료되지 않는다면 응용 프로그램은 영원히 종료되지 않을 수도 있다. 이러한 경우에 일정 시간 동안만 응용 프로그램 쓰레드를 대기 시키도록 할 수 있다. 사용법은 다음과 같다. t1.join(100); // 100ms 동안만 응용 프로그램 쓰레드를 중지시킨다. t2.join(100); t3.join(100); 이제 앞에서 살펴본 예제에서 각각의 쓰레드를 다음과 같이 변경하도록 하자. foreach( Thread t in athread) t.start(); athread[0].join(100); athread[1].join(100); athread[2].join(100); 이와 같이 코드를 변경한 다음에 실행다면 다음과 같은 결과를 확인할 수 있을 것이다. 35

결과에서 알 수 있는 것처럼 각각의 쓰레드가 Join(100)에서 지정했으므로 300ms 초 이후에 "모든 쓰레드가 종료되었습니다"라는 메시지가 출력된 후에 계속해서 쓰레드가 수행되는 것을 알 수 있다. 각각의 쓰레드에 대해서 Join(1000)으로 변경한다면 결과는 다음과 같을 것이다. 각각의 쓰레드에 대해서 Join(1000)으로 변경했으므로 3000ms 이후에 응용 프로그램 쓰레드에 제어권이 넘어가게 되고, "모든 쓰레드가 종료되었습니다"라는 메시지가 출력되는 것을 알 수 있을 것이다. 앞의 예제에서 각각의 쓰레드에 대해서 Join(1000), Join(2000)과 같이 값을 바꿔보면서 여러가지로 테스트 해 보면 Join()에 대해서 더 잘 이해할 수 있을 것이다. 쓰레드 정리 지난 시간에 쓰레드를 종료하기 위해 Interrupt()를 사용했던 것을 기억하는지 모르겠다. 닷넷 환경은 CLR에 의해서 쓰레기 수집(Garbage Collection)을 하기 때문에 프로그래머가 쓰레드를 직접 정리하지 않아도 된다. 정리 마지막으로 쓰레드에 대해서 정리해 보도록 하자. 위 예제에서는 3 개의 쓰레드를 사용했다. 그렇다면 실제로 응용 프로그램은 몇 개의 쓰레드를 사용했는가? 답은 '4 개의 쓰레드를 사용했다'이다. 이 한 개는 응용 프로그램에 대한 쓰레드가 되며, 또한 하나의 프로세스를 사용한다. 이 프로세스는 4 개의 쓰레드에 대한 컨테이너가 된다. 응용 프로그램 쓰레드를 1 차 쓰레드라고 하며, 예제에서 생성한 각각의 쓰레드들은 모두 2 차 쓰레드라고 한다. 따라서 2 차 쓰레드에 대한 제어권은 1 차 36

쓰레드가 갖고 있다. 벌써 머리가 복잡해 지는가? 이제 잠시 쉬면서 다음을 위한 휴식을 하도록 하자. 다음 단계 지금까지 쓰레드에 대해 알아보면서 쓰레드의 처리를 제어할 수 있는 것들을 알아봤다. 첫번째는 Thread.Sleep()을 이용해서 쓰레드의 처리를 단순히 지연시키는 것에서부터, 쓰레드의 처리가 종료될 때까지 선행 쓰레드를 멈추게 하는 Join()에 대해서 알아봤으며, 쓰레드를 종료 시킬 수 있는 Interrupted()에 대해서 알아보았다. 다음에는 1 차 쓰레드와 2 차 쓰레드에 대해서, 그리고 System.Threading 네임 스페이스와 System.Threading.Thread 클래스에 대해서 자세히 살펴보는 시간을 갖도록 하자. 아무쪼록 다음 시간까지 즐거운 탐험의 시간이 되기를 바란다. 소스 다운로드 37

C# 쓰레드 이야기: 4. 쓰레드 기본 개념 By 한동훈 필자는 쓰레드에 대해서 글을 쓰지만, 전혀 쓰레드에 대한 전문가가 아니라는 사실을 독자들이 알았으면 싶다. 사람들은 종종 '쓰레드? 그건 왜 쓰는데? 백그라운드 프로세스로 여러 개를 동시에 처리하면 되잖아..' 라고 얘기하곤 한다. 그래서 이번에는 실제 코드와는 관계없이 쓰레드의 기본 개념에 대해서 설명하고자 한다. 물론 여기서 소개하는 기본 개념은 공통 언어 런타임(이하 CLR, Common Language Runtime), 윈도우, 유닉스와는 전혀 관계가 없다. 이들 운영체제에서 쓰레드를 관리하는 것에 대한 기본을 설명하고자 하는 것이다. 그러니 윈도우에서 이렇게 쓰레드를 처리한다라고 생각하지 말 것을 당부한다. 쓰레드 스케줄링 윈도우 2000 의 작업 관리자에서 현재 수행중인 프로세스와 쓰레드의 숫자를 볼 수 있다. 동시에 '저만큼이 과연 실행될 수 있을까? 지금 내 PC 에는 CPU 가 한 개 뿐인데?' 라는 의문을 가질 것이다. 그리고 가장 많은 설명을 들었던 것이 시분할(Time Sharing)에 의해서 가능하다고 한다. 참고: 정확히 말하면 NT 운영체제는 시분할이 아닌 타임 슬라이싱(Time Slicing)을 사용한다. 이에 대해서는 나중에 논의하겠다. 반면에 Solaris 와 같은 운영체제는 시분할을 사용한다. 시분할이라는 것은 CPU 의 실행시간을 100 이라 보았을 때 10 으로 10 개를 나누어서 10 개의 쓰레드를 차례대로 교환하면서 실행하는 것을 말한다. 현재 사용하는 대부분의 운영체제는 32bit 운영체제이고 따라서 4GB 의 어드레싱 영역을 얻을 수가 있다. 각각의 4G 영역을 2G 씩 나누어서 상위 2G 에는 시스템 코드, DLL, 프로세스 간 공유되는 데이터와 코드가 위치하고, 하위 2G 에는 프로그램을 실행하는 프로세스, 응용 프로그램의 바이너리 이미지(실행파일), 응용 프로그램 공유 라이브러리(DLL)의 코드와 데이터가 위치한다. A 프로세스를 잠시 중지하고 B 쓰레드를 실행하는 것과 같이 프로세스와 프로세스 사이에 변환하는 것은 프로세스 문맥 교환(Process Context Exchange)이라 한다. 마찬가지로 하나의 프로세스안에 있는 여러 개의 쓰레드가 서로 실행되는 것을 쓰레드 문맥 교환(Thread Context Switching)이라고 한다. 이와 같이 프로세스와 쓰레드간에 문맥 교환을 어떻게 처리하는 가를 담당하는 것이 멀티태스킹 운영체제의 스케줄러다. 스케줄러는 어떤 쓰레드를 다음에 동작시킬 것인지를 결정한 뒤에 선택한 쓰레드를 동작시켜서 동시에 여러 가지 일을 처리하도록 한다. 즉, 운영체제 스케줄러의 최대 목표는 CPU 를 최대한 활용하여 PC 의 성능을 최대한 활용하는 것이다. 우선 순위 우선 순위에는 두 가지가 있다. 하나는 프로세스간에 우선 순위를 결정하는 프로세스 우선 순위가 있고, 하나의 프로세스내에 있는 쓰레드간의 우선 순위를 결정하는 쓰레드 우선 순위가 있다. 대부분의 운영체제에서는 우선 순위를 결정하는데 라운드 로빈(round-robin) 스케줄링 알고리즘을 포함하고 있다. 쓰레드는 FIFO(First-In First-Out) 구조의 큐에 38

들어가며, 스케줄러는 가장 우선 순위가 높은 쓰레드를 차례대로 수행한다. 스케줄러는 이 큐의 쓰레드를 차례대로 수행하면서 점점 낮은 순위의 큐로 이동한다. 만약, 새로 생성된 쓰레드가 현재 스케줄러가 있는 큐보다 우선 순위가 높은 큐에 들어가게 되면, 스케줄러는 우선 순위가 높은 큐로 이동하여 쓰레드를 수행하게 된다. 쓰레드의 우선 순위는 숫자로 표시하며, 이 숫자값은 운영체제마다 다르다. 윈도우 2000 의 작업 관리자에서 해당 프로세스에서 마우스 오른쪽 버튼을 클릭하면 다음과 같이 우선 순위를 설정하는 것을 볼 수 있다. 그림에서 볼 수 있는 것처럼 윈도우 2000 서버는 실시간, 높음, 보통 초과, 보통, 보통 미만, 낮음과 같은 프로세스 우선 순위를 설정할 수 있다. 윈도우의 우선 순위는 32 를 기준으로 설정한다. 이 숫자값이 높으면 높을수록 우선 순위가 높다. 다음 표를 참고하자. 우선 순위 숫자값 플래그 C# 플래그 실시간(Time Critical) 32 THREAD_PRIORITY_TIME_CRITICAL 39

실시간(Real Time) 24 REALTIME_PRIORITY_CLASS 최상(Highest) THREAD_PRIORITY_HIGHEST ThreadPriority.Highest 보통 초과(AboveNormal) THREAD_PRIORITY_ABOVE_NORMAL ThreadPriority.AboveNormal 보통(Normal) 7-9 THREAD_PRIORITY_NORMAL ThreadPriority.Normal 보통 미만(BelowNormal) THREAD_PRIORITY_BELOW_NORMAL ThreadPriority.BelowNormal 낮음(Lowest) THREAD_PRIORITY_LOWEST ThreadPriority.Lowest 휴지(Idle) 1 THREAD_PRIORITY_IDLE [표 1] Win32 와 CLR 우선순위 표에는 Win32 와 CLR 의 우선 순위를 각각 정리해 두었다. 표에서 알 수 있는 것처럼 닷넷 환경에서는 다섯 가지의 우선 순위만을 지정할 수 있으며, 시스템의 수행에 결정적인 영향을 줄 수 있는 실시간 우선 순위 등은 지원하지 않는 다는 것을 알 수 있다. 참고로 윈도우 9x 계열의 운영체제는 보통 초과(AboveNormal), 보통 미만(BelowNormal) 우선 순위는 지원되지 않는다. 이러한 우선 순위의 종류나 각각의 숫자값은 운영체제마다 다르다. [표 1]의 Win32 플래그는 winbase.h 헤더파일에 정의되어 있다. 일반적으로 독자들이 쓰고 있는 NT 운영체제는 7 가지 쓰레드 우선 순위를 지원한다. 대부분의 멀티 쓰레드 프로그래밍을 하는 경우에는 프로세스의 우선 순위보다는 쓰레드의 우선 순위에 많은 신경을 쓰게 된다. 프로세스의 우선 순위는 운영 체제에서 알아서 하는 것이 대부분이지만, 응용 프로그램에서의 쓰레드 우선 순위는 프로그래머가 직접 제어한다. 예를 들어서, 시스템에 특정 파일을 검색하는 검색 프로그램을 작성한다고 하자. 이 경우에 검색을 수행하는 쓰레드와 사용자 인터페이스를 담당하는 쓰레드가 있어야 한다. 검색을 하는 도중에 사용자가 검색을 중지하고자 '중지'버튼을 클릭한다면 바로 반응을 보일 수 있어야 한다. 이 경우에는 언제든지 사용자에게 반응하도록 하기 위해 인터페이스 쓰레드의 우선 순위를 검색 쓰레드보다 상위에 두도록 한다. 닷넷에서의 쓰레드 우선 순위 닷넷에서 쓰레드 우선 순위를 변경하여 프로그램이 어떻게 동작하는지 알아보도록 하자. 먼저 지난 시간에 이용한 예제를 다시 한 번 살펴보도록 하자. 여기서는 Join()문을 없앴으며 각각의 Do 함수들도 Thread.Sleep(50)으로 같은 주기를 갖도록 하였다. 이름: MultiThread.cs namespace csharp 40

using System; using System.Threading; class MultiThreadApp public static void Main() MultiThreadApp app = new MultiThreadApp(); app.dotest(); // End of Main() private void DoTest() Thread[] athread = new Thread( new ThreadStart(DoPrinting) ), new Thread( new ThreadStart(DoSpelling) ), new Thread( new ThreadStart(DoSaving) ) ; foreach( Thread t in athread) t.start(); // End of DoTest() 41

private void DoPrinting() Console.WriteLine("인쇄 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(50); Console.Write("p "); Console.WriteLine("인쇄 완료"); // End of DoPrinting() private void DoSpelling() Console.WriteLine("철자 검사 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(50); Console.Write("c "); Console.WriteLine("철자 검사 완료"); // End of DoSpelling() private void DoSaving() 42

Console.WriteLine("저장 시작"); for ( int LoopCtr = 0; LoopCtr < 100; LoopCtr++) Thread.Sleep(50); Console.Write("s "); Console.WriteLine("저장 완료"); // End Of DoSaving() // End of class MultiThreadApp // end of namespace csharp 코드를 위와 같이 고쳤으면 다시 컴파일을 하고 실행해 보도록 하자. 실행결과는 다음과 같을 것이다. 결과에서 볼 수 있는 것처럼 저장, 인쇄, 철자 검사를 시작하고 차례대로 저장, 인쇄, 철자 검사 순으로 완료되는 것을 볼 수 있다. 이제 각각의 쓰레드의 우선 순위를 변경해서 철자 검사를 가장 먼저 끝내고, 인쇄를 가장 마지막에 끝나도록 해보자. 쓰레드 우선 순위는 다음과 같이 설정한다. somethread.priority = ThreadPriority.Highest; 또는 다음과 같이 사용할 수도 있다([표 1. Win32 와 CLR 우선순위] 참고). somethread.priority = ThreadPriority.Normal + 2; // Highest 사용 43

ThreadPriority 는 열거형(enum)이고, 사용할 수 있는 종류는 [표 1]에 있는 것과 같다. DoTest() 함수를 다음과 같이 바꿔보도록 하자. Thread[] athread = new Thread( new ThreadStart(DoPrinting) ), new Thread( new ThreadStart(DoSpelling) ), new Thread( new ThreadStart(DoSaving) ) ; // 인쇄 쓰레드의 우선 순위를 낮음으로 설정한다. athread[0].priority = ThreadPriority.Lowest; // 철자 검사 쓰레드의 우선 순위를 높음으로 설정한다. athread[1].priority = ThreadPriority.Highest; Console.WriteLine("인쇄 쓰레드 우선 순위 : " + athread[0].priority); Console.WriteLine("철자 쓰레드 우선 순위 : " + athread[1].priority); Console.WriteLine("저장 쓰레드 우선 순위 : " + athread[2].priority); 코드를 모두 변경했으면 다시 컴파일하고 실행해 보도록 하자. 결과는 다음과 같을 것이다. 44

위에서 알 수 있는 것처럼 각각의 쓰레드에 대해서 쓰레드 우선 순위를 지정할 수 있다. [표 1]에서 알 수 있는 것처럼 CLR 에서는 5 가지의 우선 순위를 지정할 수 있다. CLR 은 현재 MS 의 윈도우에서만 실행되지만 다른 운영체제에서도 실행될 수 있다. 다른 운영체제에서는 3 가지의 우선 순위를 가진다면 C#에서 지정한 우선 순위중에 몇 가지 같은 우선 순위로 지정할 것이다. 다행히도 NT 는 7 가지의 우선 순위를 가지지만, 윈도우 95/98 과 같은 운영체제는 AboveNormal 과 BelowNormal 우선 순위를 운영 체제의 다른 우선 순위로 지정할 것이다. 쓰레드 우선 순위와 스케줄러 멀티 쓰레드 응용 프로그램에서 쓰레드에 대해서 다른 우선 순위를 지정하여 실행 순서를 다르게 할 수 있다. 그러면 같은 우선 순위를 가진 쓰레드가 여러 개 있는 경우에는 어떤 쓰레드가 우선 순위를 가지게 될까? 이 경우에는 어떤 쓰레드가 우선 순위를 갖는 다고 보장할 수 없다. 이것은 전적으로 호스트되는 OS 의 쓰레드 스케줄러에 달려있다. CLR 은 같은 우선 순위를 갖는 쓰레드가 공평하게 실행된다고 보장하지 않는다. 같은 순위의 쓰레드를 차례대로 실행할 수도 있고, 무작위로 실행할 수도 있다. 심지어는 제어권을 양보한 쓰레드를 다시 실행할 수도 있다. OS 의 쓰레드 스케줄러가 동일 우선 순위를 갖는 쓰레드를 어떻게 스케줄링하든지 간에 한 번 실행된 쓰레드가 다시 실행되는 것을 방지하려면 쓰레드의 루프안에 Sleep()을 사용하도록 해야 한다. 탐욕 쓰레드(Selfish Thread) 지금까지 글을 읽은 독자중에 자바와 같은 언어에서 쓰레드 프로그래밍을 한 경험이 있다면 이상하다고 여기는 부분이 있을 것이다. 지금까지 필자가 만든 예제들은 모두 한 가지 작업을 할 때마다 제어권을 양보하고 있다. 마지막에 만든 코드의 실행결과를 살펴보아도 각각의 쓰레드 우선 순위가 Highest, Normal, Lowest 인데도 불구하고, 실행 순서와 종료 순서가 바뀐 것 이외에는, 'c', 'p', 's'가 사이 좋게 번갈아가며 찍히는 것을 보았을 것이다. 각각 다른 우선 순위를 갖는 쓰레드가 사이좋게 제어권을 양보하면서 차례대로 실행될 수 있는 것은 각각의 루프안에 있는 Sleep() 때문이다. Sleep() 함수가 없는 쓰레드의 실행시간이 길다면 홀로 스레드 실행시간을 독차지 하는 것을 볼 수 있다. 이와 같은 쓰레드를 탐욕 쓰레드라고 한다. 탐욕 쓰레드를 알아보기 위해 각각의 Do 함수의 Thread.Sleep()을 지우고, 루프의 횟수를 2000 회로 늘렸다(이와 같이 루프를 늘리는 것은 시스템에 따라 정상적으로 쓰레드 교환(Switching)이 일어나더라도 한 번에 200 270 회의 출력을 할 수 있어 교환이 일어나는 것을 관찰할 수 없기 때문이다). 다시 한 번 전체 소스를 살펴보도록 하자. 이름: Selfish.cs namespace csharp 45

using System; using System.Threading; class MultiThreadApp public static void Main() MultiThreadApp app = new MultiThreadApp(); app.dotest(); private void DoTest() Thread[] athread = new Thread( new ThreadStart(DoPrinting) ), new Thread( new ThreadStart(DoSpelling) ), new Thread( new ThreadStart(DoSaving) ) ; athread[1].priority = ThreadPriority.Normal + 2; foreach( Thread t in athread) t.start(); 46

foreach( Thread t in athread) t.join(); Console.WriteLine("모든 쓰레드가 종료되었습니다"); private void DoPrinting() Console.WriteLine("인쇄 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) Console.Write("p "); Console.WriteLine("인쇄 완료"); private void DoSpelling() Console.WriteLine("철자 검사 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) Console.Write("c "); 47

Console.WriteLine("철자 검사 완료"); private void DoSaving() Console.WriteLine("저장 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) Console.Write("s "); Console.WriteLine("저장 완료"); 실행 결과를 살펴보면 다른 쓰레드가 시작하기 전에 철자 검사 쓰레드가 시작하고 종료되는 것을 알 수 있다. Thread.Sleep()이 없기 때문에 그런 것이지 원래 쓰레드는 이렇게 실행되는 것이다. 실행시간이 너무 짧아서 제대로 확인할 수 없다고 의심할 수도 있을 것이다. -_-; 그러면 위 코드에서 다음 부분을 주석 처리하고 다시 컴파일하여 실행해 보기 바란다. // athread[1].priority = ThreadPriority.Normal + 2; 실행 결과는 각각의 쓰레드가 비슷하게 시작하고, 비슷하게 종료된다. 만약 파일을 검색하는 프로그램을 만든다고 가정하자. 이때 파일을 검색하는 쓰레드에 Sleep() 구문이 없다면 화면에 버튼을 클릭하려해도 반응을 보이지 않을 것이다. 특정 쓰레드가 자원을 독점하지 않도록 하려면 쓰레드 루프안에 Thread.Sleep()이 들어가야 한다. 이제 각각의 Do 함수를 다음과 같이 수정하고 다시 컴파일하고 실행해 보도록 하자. private void DoPrinting() 48

Console.WriteLine("인쇄 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) Console.Write("p "); Thread.Sleep(1); Console.WriteLine("인쇄 완료"); private void DoSpelling() Console.WriteLine("철자 검사 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) Console.Write("c "); Thread.Sleep(1); Console.WriteLine("철자 검사 완료"); private void DoSaving() Console.WriteLine("저장 시작"); for ( int LoopCtr = 0; LoopCtr < 1000; LoopCtr++) 49

Console.Write("s "); Thread.Sleep(1); Console.WriteLine("저장 완료"); 실행결과를 보면 'c', 'p', 's'가 사이 좋게 번갈아가며 찍히는 것을 알 수 있을 것이다. 위 예제에서 탐욕 쓰레드를 언급하면서 하나의 쓰레드에 대해서만 Highest 를 주었다. 만약 세 개의 쓰레드가 모두 Normal 인 경우에는 탐욕 쓰레드가 있을 수 없는 걸까? 확인해보기 위해서 Do 함수에서 Thread.Sleep(1)을 모두 지워버리자. 물론, 우선 순위를 설정하는 부분이 있다면 그 부분도 지우도록 한다. DoSpelling() 함수를 다음과 같이 수정한다. private void DoSpelling() Console.WriteLine("철자 검사 시작"); for ( int LoopCtr = 0; LoopCtr < 1000000; LoopCtr++) Console.Write("c "); Console.WriteLine("철자 검사 완료"); 실행 결과를 보면 불행히도 쓰레드의 자원 독점은 일어나지 않는다. 각각의 쓰레드가 여전히 번갈아가며 실행되는 것을 알 수 있다. 이것은 타임 슬라이싱 때문에 그렇다. 타임 슬라이싱(Time Slicing) CPU 가 하나인 시스템에서 쓰레드는 한 번에 하나의 코드 만을 실행할 수 있다고 말한 것을 기억하는가. 그렇다면 운영체제는 새로운 쓰레드를 스케줄링하기 위해 어떻게 쓰레드에서 CPU 의 제어권을 가져올 수 있는가? 운영체제도 결국 코드에 불과한데, 어떻게 해서 제어권을 가져올 수 있을까? 그 정답은 타임 슬라이싱에 있다. NT 와 같은 운영체제는 컴퓨터의 하드웨어에서 일정 시간 주기로 인터럽트를 발생시키도록 타이머를 지정한다. 스케줄러는 쓰레드에게 적절한 시간을 할당한다. 다음 인터럽트가 발생할 때까지 쓰레드는 CPU 를 독점적으로 사용하게 50

된다. 인터럽트가 발생하면 쓰레드는 스케줄러에게 제어권을 넘겨준다. 쓰레드가 제어권을 넘겨주면 스케줄러는 같은 우선 순위를 갖고 있는 다른 쓰레드에게 제어권을 넘겨준다. 만약 같은 우선 순위를 갖고 있는 다른 쓰레드가 없다면 같은 쓰레드가 다시 실행 시간을 할당 받고 CPU 를 독점적으로 사용하도록 한다. 따라서 위 예제에서 같은 우선 순위를 갖고 있는 경우에는 자원의 독점적인 사용이 일어나지 않는다. 마찬가지 이유로 하나의 쓰레드의 우선 순위가 높은 경우에는 하나의 쓰레드가 계속해서 실행된다. 그러나 CLR 이 NT 가 아닌 다른 운영체제에서 호스트된다면 이와 같은 동작은 보장할 수 없다. 마지막으로 Thread 클래스에서 관심을 가질만한 멤버들을 표에 정리해 두었다. 정적 멤버 설명 CurrentContext 현재 실행중인 쓰레드 컨텍스트를 가져온다. CurrentThread 현재 실행중인 쓰레드의 참조를 가져온다. GetData() SetData() 쓰레드의 현재 도메인에 대해서 실행중인 쓰레드의 특정 슬롯의 값을 설정하거나 가져온다. GetDomain() GetDomainID() 현재 쓰레드가 실행중인 도메인에 대한 참조를 가져온다. Sleep() 현재 실행중인 쓰레드를 일정 시간 동안 중지시킨다. [표 2] Thread 클래스의 정적 멤버 인스턴스 멤버 설명 IsAlive 쓰레드가 시작된 이후로 살아있는지를 알려준다. IsBackground 쓰레드가 백그라운드 쓰레드인지 아닌지를 설정하거나 알려준다(ThreadState.Background). Name 쓰레드의 이름을 설정하거나 가져올 수 있다. Priority ThreadPriority 열거형에 정의된 쓰레드 우선 순위를 정의하거나 알려준다. ThreadState ThreadState 열거형에 정의된 쓰레드의 상태를 알려주는 읽기 전용 프로퍼티 51

Abort() 쓰레드를 죽일 때 사용한다. 이 메소드를 사용하면 ThreadAbortException 예외가 발생한다(ThreadState.Aborted, ThreadState.AbortRequested). Interrupt() 현재 쓰레드를 중지시킨다. Join() 현재 쓰레드가 완료될 때까지 기다린다(ThreadState.WaitSleepJoin). Suspend() 쓰레드를 잠시 대기상태로 만든다(ThreadState.Suspended, ThreadState.SuspendRequested). Resume() 대기 상태로 만든 쓰레드를 다시 활성상태로 만든다. Start() ThreadStart 에 위임한 쓰레드 실행을 시작한다(ThreadState.Unstarted, ThreadState.Running). [표 3] Thread 클래스의 인스턴스 멤버 IsAlive 의 주의할 점은 이 쓰레드가 실행 가능한지, Sleep, Join 등에 의해 블록되어 있는지, 지금 실행되고 있는지를 알 수는 없다. 또한 쓰레드가 실행할 수 없는 상태와 종료된 상태를 구별하지는 못한다. 쓰레드의 상태를 알고자 하는 경우에는 ThreadState 를 사용하도록 한다. Abort()에 대하여 설명했으나 실제로 멀티 쓰레드 응용 프로그램에서 쓰레드를 중지시킬 때는 Abort()를 사용하지 않고 Interrupt()를 사용하도록 한다. Suspend()를 사용하는 것은 바람직하지 않으며 대신에 Sleep()을 사용하도록 한다. 만약 입력 상태에서 쓰레드가 대기 상태가 되었다면 쓰레드를 Resume() 할 수 없게 된다. 이와 같이 수행이 보류된 스레드는 별도의 작업(Resume() 사용과 같이)이 없는 경우에는 다시 수행되지 않는다. 이러한 쓰레드를 보류된 쓰레드라 한다. 보류된 쓰레드와 블로킹된 쓰레드(Blocked Thread)의 차이점은 다음과 같다. 보류된 쓰레드는 별도의 작업이 없으면 더 이상 실행되지 않으며, 블로킹된 쓰레드는 프로그래머가 자발적으로 Sleep(), Join()과 같은 함수를 호출하여 특정 기간 동안 쓰레드의 수행을 보류하는 것이다. 따라서 지정한 기간이 지나거나(Sleep 의 경우), 특정 쓰레드의 실행이 끝난 경우(Join 의 경우)에 쓰레드는 다시 실행되게 된다. 요약 쓰레드 스케줄링과 쓰레드 우선순위에 대하여 알아보았다. 반드시 기억해야 할 것은 다음과 같다. 쓰레드가 독점하지 않도록 하려면 쓰레드 루프에 Thread.Sleep() 등을 사용하여 적절히 제어권을 양보하도록 해야 한다. 멀티 쓰레드 응용 프로그램은 단순히 쓰레드 우선 순위 제어만으로는 쓰레드의 실행 순서를 제어할 수 없다. 52

코드를 실행하도록 하기 위해서 우선 순위를 제어하는 것은 잘못하고 있는 것이다. 따라서 멀티쓰레드를 제어하는 신뢰성있는 기법을 익히기 위해 이 강좌를 보거나 다른 참고문헌을 보는 것이 좋다. 다음 단계 다음에는 스케줄러, 멀티 프로세서 환경에서의 쓰레드와 쓰레드 친밀도(Thread Affinity)에 대해서 간단히 알아볼 것이다. 또한, 쓰레드의 예외 처리에 대해서 알아보고, 어떠한 문제점이 있는지 알아보도록 하자. 참고로 필자는 쓰레드에 대해서 모르기 때문에 글이 틀릴 수가 있다. 그러니 이상한 점이 있다면 주저말고 Email(traxacun@unitel.co.kr)을 보내주기 바란다. 53

C# 쓰레드 이야기: 5. NT vs UNIX 지난 시간에 스케줄러에 대해서 이야기했다. NT 를 사용하거나 UNIX 를 사용하거나 각각의 OS 에는 스케줄러가 있다. 그리고 둘 다 라운드 로빈 방식에 의해서 스케줄링을 한다는 것만 기억하자. NT 와 UNIX 의 차이는 NT 는 쓰레드로 스케줄링을 한다는 것이고 UNIX 는 프로세스로 스케줄링을 한다는 것이다. NT 는 하나의 프로세스에 여러 개의 쓰레드를 두어 처리하는 방법을 따르고 UNIX 는 fork()와 같은 함수에 의해서 부모 프로세스를 복제하여 자식 프로세스를 만드는 것과 같이 여러 개의 자식 프로세스를 두어 처리하는 방법을 따른다. 예를 들어, 동시에 200 개 이상의 작업을 처리한다고 할 때, NT 에서는 200 개의 쓰레드로 하지만 UNIX 에서는 200 개의 프로세스로 처리하기 때문에 NT 에서 비교적 더 잘 처리할 수 있다. 그러나 NT 에서도 동시에 많은 작업을 처리하는 데에는 쓰레드가 무겁기에 경량화(lightweight) 쓰레드를 소개했고, UNIX 에서도 마찬가지로 경량화 프로세스, 즉 쓰레드를 제공하고 있다(아마도 웹과 같은 환경이 가장 큰 요인이겠지만). NT 와 UNIX 모두 모든 것을 갖고 있는 종합 선물 세트 대신에 빠르게 처리할 수 있는 경량화된 쓰레드를 제공하고 있다. UNIX 의 경량화 프로세스, 즉 쓰레드를 이용하게 된 것은 최근의 일이다. 시스템에 있는 프로세스의 확인은 NT 는 시스템의 프로세스를 작업 관리자(Task Manager)를 통해서 할 수 있고, UNIX 는 ps 와 같은 명령으로 할 수 있다. GNU 유틸리티인 top 을 이용할 수도 있다. 참고: NT 4.0 에서 소개된 경량화 쓰레드인 fiber 는 다소 다르다. 이 fiber 는 프로그램내에서 수동으로 스케줄링된다. 이것은 Win16 에서 자체적으로 쓰레드를 스케줄링하도록 작성된 응용 프로그램을 NT 로 쉽게 포팅할 수 있도록 하기 위해 소개되었다. 물론 fiber 는 생성한 쓰레드내에 있으므로 이 쓰레드의 스케줄링을 따른다. 쓰레드 프로그래밍 이점 쓰레드 프로그래밍이 갖고 있는 이점은 무엇일까? 먼저 프로세스는 전역 데이터, 환경 데이터, 힙, 쓰레드 스택, 쓰레드 코드로 이루어진다. 그림을 그려보면 다음과 같을 것이다. 54

두 개의 쓰레드를 갖고 있다면 다음 그림과 같이 될 것이다. 그림에서 알 수 있는 것처럼 쓰레드는 프로세스 내에 있는 데이터를 공유할 수 있다. 만약 프로세스간에 데이터를 주고 받으려면 IPC(Inter Process Communication)와 같은 복잡한 환경을 이용해야 한다. 기본적으로 프로세스간에 데이터를 주고 받는 것은 어렵다. 반면에 쓰레드간에 데이터를 주고 받는 것은 간단하다. 프로세서 친밀도 시스템에 있는 모든 프로세스는 프로세서 친밀도를 갖고 있다. 프로세서 친밀도는 프로세스가 어떤 CPU 에서 실행될 것인지를 결정한다. 프로세서 친밀도는 NT 와 UNIX 프로세스 모두 갖고 있다. 참고로 윈도우 2000 부터는 하나의 프로세스에 있는 쓰레드에 대해서도 프로세서 친밀도를 지정할 수 있다. 또한 쓰레드 친밀도는 프로세스의 친밀도와 같은 값을 갖는다. 프로세서 친밀도는 멀티 프로세서를 갖고 있는 55

환경에서 시스템 부하가 많이 걸리는 작업을 특정 CPU 에 나누어서 부하를 줄이고, 처리 시간을 줄이는 데 유용하게 사용될 수 있지만 사용할만한 확실한 이유가 없는 한 사용하지 않는 것이 좋다. 즉, 꼭 필요한 경우에만 사용하도록 한다. NT 는 운영체제의 버전과 종류에 따라 사용할 수 있는 프로세서의 개수가 제한되어 있지만, 보통 8 개의 CPU 를 가진 환경에서 사용할 수 있는 값들은 다음과 같다. Bit mask Binary value Eligible processors 0x0001 00000000 00000001 1 0x0003 00000000 00000011 1, 2 0x0007 00000000 00000111 1, 2, 3 0x0009 00000000 00001001 1, 4 0x007F 00000000 01111111 1, 2, 3, 4, 5, 6, 7 자신의 시스템이 몇 개의 CPU 를 갖고 있는 가는 NT 에서는 성능 뷰어나 작업 관리자에서 볼 수 있고, UNIX 에서는 mpstat 나 dmesg 와 같은 명령으로 간단히 확인할 수 있다. C#에서 간단히 현재 실행중인 프로그램의 프로세서 친밀도를 알아내는 것은 다음과 같다. 대개의 경우 자신의 CPU 가 1 개일 것이므로 값은 1 이 될 것이다. 그리고 여러 개의 CPU 를 갖고 있다면 이 값을 변경하여 이 프로세스가 실행될 수 있는 CPU 를 제한할 수 있을 것이다. 56

이름: ProcessApp.cs using System; using System.Diagnostics; using System.ComponentModel; class ProcessApp public static void Main() Process proc = Process.GetCurrentProcess(); try //Process Name Console.WriteLine("Process Name: " + proc.processname); //Processor Affinity Console.WriteLine("Processor Affinity: " + proc.processoraffinity); catch(win32exception e) Console.WriteLine(e.ToString()); 현재 실행중인 프로그램의 프로세스를 가져오기 위해 Process.GetCurrentProcess()를 사용하고 이것을 사용하기 위해서는 System.Diagnostics 가 필요하다. 57

Process proc = Process.GetCurrentProcess(); NT 에서 몇몇 시스템 서비스의 경우에는 프로세서 친밀도 값을 가져올 수 없다. 이러한 경우에 Win32Exception 이 발생한다. 이 예외를 처리할 수 있도록 하기 위해 System.ComponentModel 네임 스페이스가 필요하다. 만약 현재 2 개의 CPU 를 갖고 있는 환경이라면 다음과 같이 프로세서 친밀도를 바꿀 수 있다. proc.processoraffinity = (IntPtr)3; 이 경우에는 두 개의 프로세서에서 모두 실행되도록 할 것이다. 단일 CPU 환경에서 이와 같이 한다면 에러를 만나게 될 것이다. IntPtr 데이터형은 시스템에 따른 고유한 정수 값 형식을 지정한다. Int 형을 2 바이트로 쓴다면 2 바이트를 사용하고, 4 바이트를 사용하는 환경에서는 4 바이트를 사용하도록 하는 정수형이다. 다음은 시스템에 있는 모든 프로세스를 출력하는 예제다. 이름: pinfo.cs using System; using System.Diagnostics; class pinfoapp public static void Main() Process[] procs = Process.GetProcesses(); foreach(process proc in procs) // Process Name Console.Write("Process Name: " + proc.processname); 58

// Process Id Console.WriteLine(", Process ID: " + proc.id.tostring()); 다음 단계 프로세서 친밀도에 대해서 알아보았다. 다음에는 쓰레드의 예외 처리에 대해서 알아보도록 하자. 59