posted by 방랑군 2012. 1. 19. 22:05

파일 디스크립터의 변화를 확인하는 함수
기본적으로 blocking 함수(확인할 파일 디스크립터에 변화가 생길 때까지 무한 대기)

멀티플렉싱 서버를 구현하기 위한 방법으로 select 함수가 가장 많이 사용되는 방법이고 윈도우즈 시스템에서도 동일한 이름으로 동일한 기능을 하는 함수를 제공하고 있으니 이식성에서도 높은 점수를 줄 수 있다

select 함수를 사용하게 되면, 한 곳에 모아놓은 여러 개의 파일 디스크립터를 동시에 관찰할 수 있다.
수신할 데이터를 지니고 있는 파일 디스크립터가 어떤 것들인지, 데이터를 전송할 경우 블로킹되지 않고 바로 전달 가능한 파일디스크립터는 어떤 것들인지, 그리고 예외가 발생한 파일 디스크립터는 어떤 것들인지 정도가 관찰 내용이 된다.

select 함수의 기능과 호출 순서

-- select 함수 사용 순서 --
1. 디스크립터 설정
2. 검사 범위 설정
3. 타임 아웃 설정
4. select 함수 호출
5. 결과 확인
-------------------------



===== 디스크립터 설정 =====

1) 파일 디스크립터 설정
  - 변화를 확인할 파일 디스크립터들을 한 묶음으로 모아둔다.
  - 파일 디스크립터를 모아두는 비트단위 자료형 fd_set 이용
  - 3가지 변화(수신 데이터 존재유무, 데이터 송신 가능여부, 소켓의 예외상황 발생여부)별로 파일 디스크립터들을 구분지어 모아둠



fd_set 자료형 관련 함수
 FD_ZERO(fd_set * fdset); //fd_set 초기화 함수
 FD_SET(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 1로 셋
 FD_CLR(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 0으로 셋
 FD_ISSET(int fd, fd_set * fdset); //해당 파일디스크립터 fd가 1인지 확인


===== 검사범위 설정 =====

2) 검사할 파일 디스크립터의 범위 지정
  - 검사해야할 파일 디스크립터의 개수를 전달.
  - 가장 큰 파일 디스크립터 값에 1을 더함(파일 디스크립터 값이 0부터 시작하므로)


===== 타임아웃 설정 =====

3) 타임 아웃 설정
  - select 함수가 blocking되는 것을 피하기 위해 타임 아웃을 설정함. 

===== select 함수의 원형 ===== 

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int n, fd_set * readfds, fd_set * writefds, fd_set * exceptfds, struct timeval * timeout);

   리 턴 값    의    미
   -1    오류 발생
    0    타임 아웃
    0보다 큰 수    변화발생 파일 디스크립터 수

- n : 검새 대상이 되는 파일 디스크립터의 수

- readfds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 입력이 가능한지 확인된다. 즉, 입력 가능으로 인해 반환된 식별자에 대해 recv()는 블로킹되지 않는다.

- writefds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 출력이 가능한지 확인된다, 즉 출력 가능으로 인해 반환된 식별자에 대해 send()는 블로킹되지 않는다.

- excepfds : 이 리스트에 있는 식별자들은 시스템에 의해 예상되는 예외 사항이나 에러가 발생했는지 확인된다. TCP소켓에서 발생할 수 있는 이러한 예상되는 예외 사항의 한 예로 상대방이 데이터 전송 중에 TCP연결을 끊었을 때가 있다. 이런 경우, 다음에 이어지는 읽기 또는 쓰기는 실패하고 ECONNRESET에러를 나타낸다.

- timeout : 함수 호출 후, 무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을 설정하기 위해
 인자를 전달한다.

※ select 함수 호출시 전달되는 파일 디스크립터의 정보를 소켓뿐 아니라 파일을 나타내는 경우에도
   전달 가능한다.


===== 결과 확인 =====


fd_set자료형에 파일디스크립터 0(stdin), 3의 변화를 확인하기 위해 설정




Select() 호출하면 변화가 생긴 파일디스크립터는 1로 세팅됨(그림에서 파일디스크립터 3이 변화가 발생한 것을 확인할 수 있음)

- 변화가 확인된 해당 파일디스크립터와 데이터 통신을 진행하면 된다.


[ 파일 디스크립터 범위 설정하기 ]

select 함수는 여러 파일 디스크립터를 검사하고, 그 결과를 전달해 준다. 
select 함수는 여러 개의 파일 디스크립터를 확인해야 하는데, 이왕이면 확인해야 하는 파일 디스크립터의 범위를 제한해 주면, 보다 효율적으로 수행할 수 있다.
그래서 select 함수의 첫 번째 인자로 검사해야 하는 총 디스크립터의 개수를 넘겨주게 된다.
그러나 일반적으로 디스크립터는 생성될 때마다 값이 1씩 증가하기 때문에 가장 큰 파일 디스크립터 값에 1을 더해서 인자로 전달하면 된다.
1을 더하는 이유는 디스크립터 값이 0부터 시작하기 때문이다. 따라서 인자로 n이라는 값을 넘겨주게 되면 select 함수는 검사하게 되는 파일 디스크립터의 범위를 부터 n-1로 설정된다.
따라서 반드시 1을 더해줘야 한다.


[ 타임아웃 (time out) 설정하기 ]

타임아웃을 설정하기 위한 timeval 구조체

struct timeval
{
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
}
예를 들어서 tv_sec가 3이고 tv_usec가 500000 이면 타임아웃은 3.5초로 설정된다. 
이렇게 설정한 timeval 구조체 변수의 포인터를 select 함수의 마지막 인자(timeout 인자)로 넘겨주게 되면 파일 디스크립터에 아무런 변화가 없더라도 3.5초가 지나면 무좋건 리턴하게 된다. 만약에 타임 아웃을 설정해 주지 않을 경우, NULL 포인터를 인자로 전달하면 된다.


[ select 함수 호출 이후 결과 확인 ]
select 함수가 리턴되고 나서 무엇보다도 중요한 것은 결과를 얻는 것이다. 일단 함수 호출이 정상적으로 리턴했다는 것은 파일 디스크립터에 변화가 있엇거나, 아니면 타임아웃이 발생했거나 둘 중에 하나이다. 

리턴 값이 -1인 경우는 오류발생을 의미한다. 또한 0이 리턴 된 경우에는 타임아웃에 의해 리턴되었음을 의미한다. 즉 0이 리턴된 경우 파일 디스크립터에 아무런 변화도 발생하지 않았다는 의미가 된다. 
그러나 리턴된 값이 0보다 큰 경우에는, 변화가 발생한 파일 디스크립터의 수를 의미하게 된다.
예를 들어 수신할 데이터가 존재하는 파일 디스크립터가 두 개 발생했다면, 2가 리턴될 것이다.

-------- select 호출 전 / 후 --------

==select 호출 전 ==
fd0  fd1  fd2  fd3  fd4
| 1 | 0 | 0 | 1 | 0 | 0 |...........................

==select 호출 후==
fd0  fd1  fd2  fd3  fd4
| 0 | 0 | 0 | 1 | 0 | 0 |...........................

select 함수의 예)

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  fd_set reads, temps;
  int result;

  char message[BUFSIZE];
  int str_len;
  struct timeval timeout;

  FD_ZERO(&reads);  //0으로 초기화
  FD_SET(0&reads);  //파일디스크립터 0(stdin) 설정

  
/*timeout.tv_sec = 5;
   * timeout.tv_usec = 100000;*/

          //잘못된 timeout 설정
          
  while(1)
  {
    temps = reads;  //원본 보존 위해 복사본 temp를 이용

    timeout.tv_sec = 5;  //timeout 설정
    timeout.tv_usec = 0;

    result = select(1&temps, 00&timeout);
    if(result == -1)
    {
      puts("select(): 오류 발생");
      exit(1);
    }

    else if(result == 0)  //timeout에 의한 return
    {
      puts("select(): 시간이 초과되었습니다.");
    }

    else
    {
      if(FD_ISSET(0&temps));
      {
          str_len = read(0, message, BUFSIZE);
          message[str_len] = 0;
          fputs(message, stdout);
      }
    }
  }
  
  return 0;
}



[결과]



이미 주비해둔 fd_set 변수를 임시 변수에 복사해 두고 있다. 여기에는 그럴만한 이유가 있다. select 함수 호출이 끝나면 변화가 생긴 파일디스크립터 위치를 제외한 나머지 위치의 비트들이 0으로 초기화된다. 따라서 원본 변수를 직접 select 함수의 인자로 전달해 버리면 또 다시 변수를 설정하는 과정을 거쳐야 한다.
이를 막기 위해서 원본은 보존 하고 임시 변수에 원본을 복사해서 이것을 가지고 select 함수를 호출한다. 일반적인 select 함수 사용이니 반드시 기억해야한다.
그리고 timeout 변수가 반복문 내부에 존재하게 되는데... select 함수를 호출하기 전에 매번 타임 아웃을 재 설정 하게 되어 있는 것이다. 다시 비효율적인 방법으로 생각할수도있으나 select 함수 호출이 끝나게 되면 타임아웃이 발생하기 까지 남아 있던 시간이 timeval 구조체 변수에 저장된다.
따라서 timeval 구조체 변수를 한번만 설정하고 계속해서 select 함수를 호출한다면 나중에는 0이 된다.
select 함수를 호출하고 만약 콘솔로부터 입력된 데이터가 있다면 0보다 큰 수가 리턴될 것이며, 입력이 없어서 타임아웃이 발생하는 경우에는 0이 리턴될것이다.

'강좌 > C#' 카테고리의 다른 글

.Net single-instance application without activation  (0) 2012.01.21
C# 사운드 재생  (1) 2012.01.21
멀티플렉싱(multiplexing)과 select()함수  (0) 2012.01.19
C# 멀티코어 프로그래밍  (0) 2012.01.19
[C#] Template Method 패턴  (0) 2012.01.19
posted by 방랑군 2012. 1. 19. 21:51

멀티플렉싱(multiplexing)>>> 통신분야에서는 (다중화기)

지금까지 작성했던 프로그램들은 모두 하나의 단일 채널에서 일어는 입/출력만을 다루었습니다 즉 이전의 모든 버전의 에코 서버는 한번에 하나의 클라이언트 연결만을 처리 하였습니다 하지만 응용프로그램은 여러 채널의 입/출력을 동시에 처리하는 능력을 요구할 때가 자주 있습니다예를 들어 동시에 여러 포트를 열어서 에코서버를 할때 서버가 각 소켓을 생성하고 이를 각 포트에 바인딩 한 후 무슨 일이 일어날까를 생각해 봅시다기존방식에는 문제점이 발견되는데 서버는 연결을 accept할 준비가 되어있습니다 하지만 어떤 소켓을 선택해야 할지 선택을 하지 못합니다아무 서버나 연결하게 된다면 기존의 대기하고 있는 소켓역시 대기가 되어버리는 불편한 상황이 발생합니다물론 이러한 문제는 non-bloaking소켓을 이용하여 해결이 가능하지만 그것보다는 특정 소켓의 입/출력이 준비가 될때까지 서버를 bloacking을 하는 것이 좋습니다

 

하지만 단점도 있습니다 정리하자면

프로세스 생성에 많은 양의 연산메모리 공간 요구. IPC (inner process communcation) 방법도 복잡합니다

 


이런한 일련의 과정들은 가능하게 하는 것이
 linux상에서 다시 말하면 unix상에서 제공해주는 select를 사용하면 됩니다 select는 입/출력이 예상되는 소켓의 식별자를 리스트로 명시하고 리스트의 식별자중 준비되어진 식별자를 준비가 되었는지를 반환하여 알리고 blocking이 되지 않을 것이라는 것을 확신하고 진행하게 됩니다

 

 

전반적인 순서는 아래와 같습니다

하나의 서버에 여러개의 클라이언트 제어
서비스 품질은 멀티프로세스보다 더 떨어지는것 같아보입니다
실질적으로 서버와 클라이언트의 통신에서 데이타 송수신은 매우 작다 
그렇기 때문에 많은 클라이언트에게 서비스 할 수 있습니다.

하나의 프로세스가 해당 클라이언트n개 즉...파일 디스크립터를 묶어서
관리를 합니다
   
※ 즉 fd_set으로 묶어서 파일 디스크립터정보를 담습니다

 





select()함수>>>
select 
함수를 사용하기 위해서 간단한 순서를 확인해보자

참고>>그림


----- select 함수 사용 순서 -------

1. 디스크립터 설정

2. 검사 범위 설정

3. 타임 아웃 설정

4. select 함수 호출

5. 결과 확인

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

 

 
 

1. 디스크립터 설정


1)
파일 디스크립트 설정

 

fd_set 수형;

0

1

0

1

.........

fd0  fd1  fd2  fd3

 

fd0 : stdin    fd1 : stdout    fd2 : stderr    fd3 : socket

 

변화를 확인할 파일 디스크립터들을 한 묶음으로 모아둔다

 

fd_set 자료형 관련함수

  FD_ZERO(fd_set * fdset);        //fd_set 초기화 함수

  FD_SET(int fd, fd_set * fdset);   //해당 파일디스크립터  fd  1로 셋

  FD_CLR(int fd, fd_set * fdset);   //해당 파일디스크립터  fd  0으로 셋

  FD_ISSET(int fd, fd_set * fdset);  //해당 파일디스크립터  fd  1인지 확인

 

사용방법 예제


2)
검사할 파일 디스크립터의 범위 지정

 검사해야할 파일 디스크립터의 개수를 전달

 가장 큰 파일 디스크립터 값에 1을 더함(파일 디스크립터 값이 0부터 시작하므로)

 

3)타임 아웃 설정

 - select함수가 blocking 되는 것을 피하기 위해 타임 아웃을 설정함

 

2. select함수 호출

 
헤더파일

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>


원형

int select(int n fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

                                                                                                  리턴값 : 성공시 0  이상... 오류 발생시 -1 리턴
                                                                                 0을 리턴하는 경우에는 타임아웃에 의해 리턴되었음을 의미
                                                                                 보다 큰경우는 변경된 파일 디스크립터의 수를 의미한다. 

인자 값 분석>>


n : 
검색 대상이 되는 파일 디스크립터의 수

 

readfds : "입력스트림에 변화가 발생했는지확인하고자 하는 소켓들의 정보를 전달합니다여기서 입력 스트림에 변화가 발생했다는 것은 수신할 데이터가 있다는 뜻

 

writefds : "데이터 전송 시블로킹되지 않고 바로 전송이 가능한지확인하고자 하는 소켓들의 정보를 전달
 

excepfds : "예외가 발생했는지확인하고자 하는 소켓들의 정보를 전달
 

timeout : 함수 호출 후무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을 설정하기 위해 인자를 전달

 

예제>>

select(1,&reads,0,0,5)

<감지>5초동안 감지 수신에 변화가 없는지 감지하고 나와라 리턴값 = 1-

검사 디스크립트 수는 1


위 예제의 select같이 동작하는 프로그램을 작성합니다
 

select.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  fd_set reads, temps;
  int result;

  char message[BUFSIZE];
  int str_len;
  struct timeval timeout;

  FD_ZERO(&reads); //0
으로 초기화
  FD_SET(0,&reads); //
파일 디스크립터 0(stdin) 설정

  /*
  timeout.tv_sec 
= 5;
  timeout.tv_usec 
= 100000;
  */
  //
잘못된 timeout 설정(구조체 내에서만 업데이트된다)
  
  while(1)
  {
    temps = reads;
    
    timeout.tv_sec = 5//
실행후 다시 재설정해야한다
    timeout.tv_usec = 0;
    
    result = select(1&temps, 00&timeout);
    if(result == -1)
    {
      puts("select(): 
오류발생");
      exit(1);
    }
    else if(result == 0)
    {
      puts("select():
시간이 초과 되었습니다. ");
    }
    else
    {
      if(FD_ISSET(0,&temps))
      {
        str_len = read(0, message, BUFSIZE);
        message[str_len] = 0;
        fputs(message, stdout);
      }
    }
  }
  return 0;
}

 

결과>> 5초간 입력이 없을 경우 시간이 초과되었다는 경고 메시지가 출력됩니다

 

분석해보기>>
 
1)FD_ZERO(&reads); //0으로 초기화
    FD_SET(0,&reads); //
파일 디스크립터 0(stdin) 설정

1

0

0

0

.........

 

마지막 파일 디스크립터에 +1되게 되어있다?

 

1

0

0

1

.........

 

result = select(1&temps, 00&timeout);

수신데이터가 있는지 확인합니다 <5초설정>

확인할 파일디스크립트는 1개입니다

입력이 되었다면 result = 1



2)else
 if(result == 0) 5
초간 아무 변화가 없을때

 

0

0

0

0

.........


3)temps 
= reads; 
원본값으로 초기화 시켜주질 않을 경우

 while문을 한바퀴 돌고나면

1

0

0

0

.........

에서

0

0

0

0

.........

으로 변화되고 이후로는 계속 0인 상태가 됩니다

 

select사용시에는 원본을 저장 할 수 있는 변수를 써야 합니다


3. 결과 확인


참고>>
select()함수의 인자를 보면 이 중 n은 검사가 필요없는 가장 작은 식별자값으로써 최대 식별자  값보다 1이 작다 위에서 보면 소켓셋에 담을 수 있는 소켓 디스크립터의 최대갯수는 시스템 정의 상수인 FD_SETSIZE로 정의 되어 있지만 그 수가 상당히 크므로 매번 그 크기만큼 검사하면 비효율적이므로 이를 효율적으로 검사하기 위해서 정수 n을 전달하여 그 크기+1까지만 검사한다

 그 다음에는 각 소켓셋(읽기셋, 쓰기셋, 예외셋)이 파라미터로 들어가며 만약 NULL이 들어가면 그 소켓셋은 대상 리스트에 대한 입/출력 감시를 하지 않는다
 마지막 파라미터인 timeout은 NULL로 설정하면 읽기셋, 쓰기셋, 예외셋에 삽입한 소켓 중에 변화가 생길 때까지 대기하고 있다가 변화가 발생한 소켓의 수를 리턴하게 된다. 

timeout을 양수로 설정한 경우에는 변화가 발생한 소켓이 생길 때까지 대기하고 있다가 설정한 시간이 되면 변화가 발생한 소켓이 없더라도 대기상태를 해제하게 된다.


 이때 변화가 발생한 소켓이 없다면 0을 리턴하게 된다. 그리고 timeout 값이 0으로 설정되면 대기시간 없이 바로 리턴하게 된다

'강좌 > C#' 카테고리의 다른 글

C# 사운드 재생  (1) 2012.01.21
SELECT 함수  (0) 2012.01.19
C# 멀티코어 프로그래밍  (0) 2012.01.19
[C#] Template Method 패턴  (0) 2012.01.19
[C#] Singleton 패턴이 적용된 File 로거  (0) 2012.01.19
posted by 방랑군 2012. 1. 19. 02:30

요즘 대부분의 컴퓨터가 멀티코어다 보니 가끔 노는 코어들을 보게 된다. 그래서 궁금증에 멀티코어 프로그래밍 기법이 있어 퍼와봤다.

일단 기본적인 코드는 데브피아에서 가져왔는데 실제 어떻게 돌아가는지 내부 설명이 없어서 좀 조사해 봤다.

일단 코드...


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

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;


namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{

const int NumOfThread = 5;

Thread[] threads = new Thread[NumOfThread];

for (int i = 0; i < NumOfThread; i++)
{
threads[i] = new Thread(ThreadProc);
threads[i].Start(i);
}


Process currentProcess = Process.GetCurrentProcess();

foreach (ProcessThread processThread in currentProcess.Threads)
{
processThread.ProcessorAffinity = currentProcess.ProcessorAffinity;
}


for (int i = 0; i < NumOfThread; i++)
{
threads[i].Join();
}

}


public static void ThreadProc(object threadId)
{
//스레드 작업

}

}

}

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


코드 자체는 굉장히 심플하다. 실제 프로그램 만들때도 이렇게 심플하다면 좋겠지만 무언가 다른 문제점이 발생할지 모르니 왜 저 코드가 멀티코어 프로그래밍이 되는것인지 스스로 어느정도 파악해 보았다. 간단히 말하자면


Process currentProcess = Process.GetCurrentProcess();

currentProcess는 현재 프로세서의 정보를 가져오게 되고


foreach (ProcessThread processThread in currentProcess.Threads)
{
processThread.ProcessorAffinity = currentProcess.ProcessorAffinity;
}


현재 프로세서의 스레드를 하나씩 가져와서 ProcessorAffinity 속성을 현재 프로세서의 속성으로 바꿔 주는 코드 이다.

여기까지는 다른데 다 나와있는 설명이자 코드 설명이고 간단하게 내가 파악해본 내부 구조는 일반적으로 쿼드코어 PC의 환경이라고 한다면 일반적으로 제작한 프로그램은 아래와 같은 구조를 가지게 된다.




해당 프로세서는 쿼드쿼어중 하나의 코어에서 실행되게된다. 이 속성이 바로 ProcessorAffinity 속성으로 해당 코어중 하나가 처리할수 있도록 되어 있다. 프로세서에서 생성한 스레드는 해당 프로세서에서 실행이 되기 때문에 다수의 코어가 작동을 하지 않는다.

그래서 하위의 스레드의 속성을 상위 프로세서와 동일하게 바꿈으로써 실제 코어가 스레드의 작업을 처리할 수 있도록 설정하는 것이다.

구지 그림으로 표현한다면....




이런식으로 설정을 변경하는 것이다. 내 개인적인 조사기때문에 실제 다를수 있다;;; 위코드야 워낙 심플하니 문제가 없겠지만 실제 복잡한멀티코어 프로그래밍에서 중요한것은 문법이 아닌 시스템 전체의 구조와 각부분간의 영향력이 아닌가 싶다. 만약 오류사항이나 수정사항이 있으면 알려주세요~

'강좌 > C#' 카테고리의 다른 글

SELECT 함수  (0) 2012.01.19
멀티플렉싱(multiplexing)과 select()함수  (0) 2012.01.19
[C#] Template Method 패턴  (0) 2012.01.19
[C#] Singleton 패턴이 적용된 File 로거  (0) 2012.01.19
[C#] 리플렉션 Reflection  (1) 2012.01.19
posted by 방랑군 2012. 1. 19. 01:15

예제를 보면 이해가 빠릅니다.

Template Method 패턴은 상위 클래스에서 특정 동작의 Template만 정의해두고,

구체적인 단계의 동작 방법은 하위 클래스에서 재정의하는 패턴입니다.

 

동일한 동작의 흐름을 갖는 클래스 타입이 여럿 존재할 시 Template Method 패턴을 적용하면

전체 구성이 깔끔해지고 코드가 간결해지는 장점이 있습니다.

 

가상함수를 protected로 선언한 것과 상속받은 클래스에서 Machine 클래스를 어떻게 이용되고 있는지만

눈여겨 보시면 쉽게 이해가 가능합니다.

 

public class Machine
{
    
public void Execute()
    {
        Start();
        Produce();
        Stop();
    }

 

    protected virtual void Start()
    {
        
Trace.WriteLine("Machine.Starting..");
    }

 

    protected virtual void Produce()
    {
        
Trace.WriteLine("Machine.Producing..");
    }

 

    protected virtual void Stop()
    {
        
Trace.WriteLine("Machine.Stopping..");
    }
}


 

    public class PizzaMachine : Machine
    {
        
protected override void Produce()
        {
            
Trace.WriteLine("PizzaMachine.Producing..");
        }
    }


 

다음과 같이 호출하였을 때,


new PizzaMachine().Execute();

 

실제 호출 흐름은 아래와 같이 이루어집니다.

 

1. Machine.Execute() is invoked

2. Machine.Start() is invoked

3. PizzaMachine.Produce() is invoked

4. Machine.Stop() is invoked

 

도식화 하여보면 아래와 같습니다.

PizzaMachine 클래스의 역할이 가벼워진 것을 볼 수 있습니다.

동일한 흐름을 갖는 다양한 Machine들이 추가될 때 따르게 되는 이점은 결코 작지 않습니다.

 



posted by 방랑군 2012. 1. 19. 01:14

Singleton 패턴은 굳이 용어를 들먹이지 않아도 자연스레 사용되는 패턴 중 하나입니다.

하나의 클래스에 대하여 하나의 인스턴스만 생성되게끔 만들어주는 것인데요.

 

단순히 로그를 남기는 File 로거를 구현해보고자 합니다.

클래스 다이어그램을 보시면 이해가 빠릅니다.

 



클래스 내부에 static 인스턴스를 반환하는 메서드를 포함하고, 생성자를 private로 제한함으로써

해당 클래스의 인스턴스를 생성하는 방법을 제한할 수 있습니다.

즉, 해당 클래스의 인스턴스를 획득할 수 있는 방법은 static 인스턴스를 반환하는 메서드를 호출하는 방법 뿐이겠지요.

 

public class LogManager
{
    
private static LogManager _instance;

 

    public static LogManager Instance
    {
        
get 
        {
            
if (_instance == null)
                _instance = 
new LogManager();

            return _instance; 
        }
    }

 

    private LogManager()  // Constructor as Private
    {
        _fileStream = 
File.OpenWrite(GetExecutionFolder() + "\\Application.log");
        _streamWriter = 
new StreamWriter(_fileStream);
    }

 

    private FileStream _fileStream;
    
private StreamWriter _streamWriter;

 

    public void WriteLog(string message)
    {
        
StringBuilder formattedMessage = new StringBuilder();
        formattedMessage.AppendLine(
"Date: " + DateTime.Now.ToString());
        formattedMessage.AppendLine(
"Message: " + message);

        _streamWriter.WriteLine(formattedMessage.ToString());
        _streamWriter.Flush();
    }

 

    public string GetExecutionFolder()
    {
        
return Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    }
}

로거를 Singleton 형태로 구현하였을 때 얻는 이점은 쉽게 떠올릴 수 있습니다.

1. 하나의 인스턴스를 돌려가며 사용하기 때문에 메모리가 절약됩니다.

2. File 접근에 대한 Locking과 그에 따르는 오버헤드를 고려하지 않아도 됩니다.

 

단점도 존재합니다.

1. 멀티-스레딩 환경에서는 약간의 오버헤드를 감수하여야 합니다. (.Net에서는 Thread Safe를 보장합니다 :D)

 

'강좌 > C#' 카테고리의 다른 글

C# 멀티코어 프로그래밍  (0) 2012.01.19
[C#] Template Method 패턴  (0) 2012.01.19
[C#] 리플렉션 Reflection  (1) 2012.01.19
[C#] 소멸자 (de-constructor, finalizer)  (0) 2012.01.18
Thread vs ThreadPool  (0) 2012.01.18
posted by 방랑군 2012. 1. 19. 00:27

실행 중에 클래스나 객체의 타입 정보를 조사하는 기능이다.


타입 정보는 보통 컴파일 중에만 사용되며 컴파일러에 의해 기계어로 바뀌고 나면 사라지는 것이 일반적이나 C#은 컴파일된 결과 코드뿐만 아니라 타입에 대한 메타 데이터를 실행 파일에 같이 기록해 놓기 때문에 실행중에도 정보를 조사할 수 있다.


이 기능을 사용하면 실행 중에 다른 모듈에 선언된 인스턴스를 생성할 수 있고 메서드를 호출할 수도 있다.

Type 클래스에서 제공되는 정보로부터 모든 멤버의 정보를 알아낼 수 있기 때문에 실행 중에 클래스 선언문을 통해 복원할 수도 있을 것이다.

 

특정 클래스나 객체의 타입 정보를 조사하고 싶을 때에는 Type 객체를 얻어야 하는데 다음의 세가지 방법으로 얻는다.
------------------------------------------------

// 런타임 시 지정된 이름으로 접근하는 방법

1) Type p = Type.GetType("Top");

 

// 클래스 타입으로 접근하는 방법

2) Type p = tyoeof("Top");

 

// 객체로 접근하는 방법

3) Type p = Top.GetType()

 

Top t = (Top)Activator.CreateInstance(p);

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

 

// Top 형의 타입에 해당하는 클래스 정보를 Type 형의 객체로 얻어낸다.

// 이 객체 안에 Top 형에 관련된 모든 타입 정보가 들어있다고 보면 된다.

// CLR의 도움 없이 사용자가 직접 핸들하여 객체를 생성하는 것이 된다.

//

// 어셈블리 내에 들어있는 클래스의 타입 정보를 핸들하는 클래스를 Type 클래스라 할 수 있다.

 

  Type 클래스 : 클래스의 정보를 관리하는 클래스라 정의내릴 수 있다.

 

  다음의 예제 코드는, Type 클래스를 이용하여 특정 클래스의 정보를 출력한다.

//////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;

namespace ReflectionTest1
{
    public class ReflectionTest
    {
        public int temp = 1000;
        public void PrintShow()
        { }

        static void Main(string[] args)
        {
            try
            {
                Type t = Type.GetType("ReflectionTest1.ReflectionTest");

                Console.WriteLine("기본 클래스 타입");
                Console.WriteLine(t.BaseType);

 

                Console.WriteLine("\n생성자 목록");
                ConstructorInfo[] cList = t.GetConstructors();
                foreach (ConstructorInfo c in cList)
                    Console.WriteLine(c);

 

                Console.WriteLine("\n함수 목록");
                MethodInfo[] mList = t.GetMethods();
                foreach (MethodInfo m in mList)
                    Console.WriteLine(m);

 

                Console.WriteLine("\n변수 목록");
                FieldInfo[] fList = t.GetFields();
                foreach (FieldInfo f in fList)
                    Console.WriteLine(f);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

//////////////////////////////////////////////

 

리플렉션을 사용하지 않은 보통의 경우 우리가 작성한 프로그램은 CLR 내에서 실행되는 프로그램이다.

 

리플렉션을 사용하면 우리는 실행되는 프로그램이 아닌 실행시켜 주는 프로그램까지 만들어낼 수 있다.

 

즉, 미들웨어급 프로그램을 만들어낼 수 있다.

 

출처 : 소설같은 c#

 

## 책에는 리플렉션을 활용한 리플렉션 프로그래밍 기법이 소개되어 있습니다.

'강좌 > C#' 카테고리의 다른 글

[C#] Template Method 패턴  (0) 2012.01.19
[C#] Singleton 패턴이 적용된 File 로거  (0) 2012.01.19
[C#] 소멸자 (de-constructor, finalizer)  (0) 2012.01.18
Thread vs ThreadPool  (0) 2012.01.18
마샬링 (Marshaling)  (0) 2012.01.18