출처 : 

http://www.terms.co.kr/reentrant.htm


재진입성은 메모리 내의 동일한 사본이 다중 사용자들에 의해 공유될 수 있도록 작성된 컴퓨터 프로그램이나 루틴을 설명하기 위한 형용사이다. 재진입 코드는 통상 다중사용자 시스템 내에서 공유될 목적으로 작성된 운영체계와 응용프로그램에서 필요하다. 프로그래머는 어떠한 명령어도 프로그램 내의 다른 명령어들을 위한 변수 값을 수정하지 않는다는 확신을 가지고 재진입 프로그램을 작성한다. 프로그램이 한 사용자를 위해 진입할 때마다, 그 사용자를 위한 모든 변수 값들을 유지하기 위해 데이터 공간이 확보된다. 프로그램 그 자체의 데이터 공간은 메모리의 또다른 부분에 들어 있다. 다른 사용자에게 순서를 넘기기 위해 그 프로그램이 중단될 때, 그 사용자와 관련된 데이터 공간에 관한 정보는 저장된다. 중단된 사용자의 프로그램이 다시 재개될 때, 데이터 공간에 저장된 정보가 복원되며, 그 프로그램은 프로그램 내의 다른 명령어가 변수값을 변경했을지도 모른다는 우려 없이 재진입할 수 있다.



https://ko.wikipedia.org/wiki/%EC%9E%AC%EC%A7%84%EC%9E%85%EC%84%B1


컴퓨터 프로그램 또는 서브루틴에 재진입성이 있으면, 이 서브루틴은 동시에(병렬) 안전하게 실행 가능하다. 즉 재진입이 가능한 루틴은 동시에 접근해도 언제나 같은 실행 결과를 보장한다. 재진입이 가능하려면 함수는 다음 조건을 만족하여야 한다.


정적 (전역) 변수를 사용하면 안 된다.

정적 (전역) 변수의 주소를 반환하면 안 된다.

호출자가 호출 시 제공한 매개변수만으로 동작해야 한다.

싱글턴 객체의 잠금에 의존하면 안 된다.

다른 비-재진입 함수를 호출하면 안 된다.


여러 '사용자/객체/프로세스'와 멀티프로세싱이 대개 재진입 코드의 제어를 복잡하게 만든다. 또한 입출력 코드는 디스크나 터미널과 같은 공유 자원에 의존하고 있기 때문에 보통은 재진입성이 없다.


재진입성은 함수형 프로그래밍의 핵심 개념이다.



다음 C 코드에서 f g 함수는 재진입이 불가능하다.

int g_var = 1;

int f()
{
  g_var = g_var + 2;
  return g_var;
}

int g()
{
  return f() + 2;
}

위 코드에서 f 함수는 전역 변수 g_var에 의존하고 있다. 만약 두 개의 스레드가 이 함수를 실행하여 g_var에 동시에 접근할 경우, 결과는 실행되는 시점에 따라서 바뀌게 된다. 그러므로 f는 재진입성을 가지지 않는다. g 함수는 비-재진입 함수 f를 호출하기 때문에 역시 재진입이 불가능하다.

이 함수를 다음과 같이 고치면 재진입이 가능하다.

int f(int i)
{
  int priv = i;
  priv = priv + 2;
  return priv;
}

int g(int i)
{
  int priv = i;
  return f(priv) + 2;
}


http://www.jiniya.net/wp/archives/1197/trackback


재진입 함수의 의미
by 신영진(YoungJin Shin), codewiz at gmail.com, @codemaruhttp://www.jiniya.net

‘윈도우 메시지 프로시저는 재진입 가능한 함수다.’

그렇게도 많은 책에서 위와 같은 말들을 하고 있다. 하지만 딱 한 줄뿐 저 말이 무엇을 의미하는지를 자세히 설명하고 있는 책은 드물다. 그래서 그런지 저 말을 쓰면서도 저 말이 무슨 의미인지 모르는 경우도 많고, 자신의 의미대로 해석해버리는 경우도 있다. 그렇게 많이들 오해하는 이 재진입 함수의 의미에 대해 한번 제대로 이해해 보자.

재진입이라는 말은 말 그대로 함수 내부로 다시 진입하는 것을 의미한다. ‘다시’라는 말이 의미하듯이 함수 코드가 처리되는 와중에도 다시 함수 내부로 진입됨을 의미한다. 여기까지 설명하면 대부분의 윈도우 개발자는 다음과 같이 잘못된 생각을 가진다. <리스트 1>과 같은 간단한 메시지 처리 루틴에서 WM_PAINT 메시지가 처리되는 동안 Sleep 상태일 때에도 키보드가 눌리면 다시 MyWindowProcedure가 실행되어 WM_KEYDOWN 이벤트가 처리된다고 생각하는 것이다.

<리스트 1> 간단한 메시지 처리 루틴

  1. LRESULT CALLBACK MyWindowProcedure(HWND Window, UINT MsgId, WPARAM W, LPARAM L)  
  2. {  
  3.     switch(MsgId)  
  4.     {  
  5.     case WM_PAINT: Sleep(1000); break;  
  6.     case WM_KEYDOWN: OutputDebugStringA("WM_KEYDOWN"); break;  
  7.     }  
  8.   
  9.     return DefWindowProc(Window, MsgId, W, L);  
  10. }  

뭔가 이벤트 드리븐 개념에도 부합하는 게 그럴듯해 보인다. 하지만 이는 완전 엉터리 설명이다. 실제로 테스트해 보면 알겠지만 Sleep 상태인 동안에 키를 아무리 눌러본들 디버그 메시지는 출력되지 않는다. 이유는 무엇일까? 답은 메시지를 처리하는 방법에 있다. 많은 윈도우 프로그래밍 책들이 마치 메시지 프로시저를 운영체제가 알아서 호출해 주는 것처럼 설명하지만 실제로 운영체제는 메시지 프로시저를 호출하는 일 따위는 하지 않는다. 해당 일은 <리스트 2>에 나타난 것과 같은 메시지 처리 루틴에 의해 이뤄진다. DispatchMessage 함수 내부에서 해당 윈도우에 맞는 메시지 프로시저를 찾아서 호출되는 일이 진행된다. 따라서 해당 메시지 처리가 완료되어서 다음 GetMessage가 호출되기 전까지 메시지 프로시저는 새로운 것을 처리하고 싶어도 할 수 없는 상태인 것이다.

<리스트 2> 메시지 처리 루틴

  1. wehile(GetMessage(&Msg, NULL, 0, 0))  
  2. {  
  3.     TranslateMessage(&Msg);  
  4.     DispatchMessage(&Msg);  
  5. }  

그렇다면 다시 본론으로 돌아가서 재진입이란 말이 내포하고 있는 실제 의미에 대해 살펴보자. 이 말이 실제로 하고자 했던 이야기는 윈도우 프로시저는 스레드에 안전하며 병렬적으로 호출이 가능하도록 작성되어야 한다는 것을 나타낸다. 다시 설명한 내용도 무슨 말인지 선뜻 이해하기가 쉽지 않다. <리스트 3>과 <리스트 4>를 보면 무슨 의미인지가 분명해진다. <리스트 3>의 코드는 한 스레드에서 윈도우가 실행될 때와 두 스레드에 윈도우가 실행될 때 동작이 달라진다. 윈도우 의존적으로 처리되어야 하는 데이터가 정적 변수로 되어 있어서 다른 윈도우의 동작에까지 영향을 미치기 때문이다. 이를 제대로 처리하기 위해서는 윈도우 의존적인 변수는 윈도우 프로퍼티로 설정해서 참조하도록 변경해야 한다. <리스트 4>의 코드는 윈도우 프로시저가 글로벌 크리티컬 섹션에 묶여 있어서 메시지가 처리되는 과정이 서로 다른 스레드에 있는 윈도우의 동작에까지 영향을 미치게 된다. 다시 말하면 서로 다른 스레드의 윈도우는 상호 동작에 간섭을 받지 않아야 함에도 이 메시지 프로시저는 한 스레드의 메시지 프로시저가 완료되기 전까지는 다른 스레드의 메시지 프로시저가 처리될 수 없게 됨으로써 서로 원활하게 동시 실행이 처리되지 못하는 문제가 있다는 것이다. 이는 윈도우 운영체제가 의도하는 바가 아니기 때문에 이러한 식의 메시지 프로시저를 작성하는 것은 옳지 않다.

<리스트 3> 스레드에 안전하지 않은 메시지 프로시저

  1. LRESULT CALLBACK MyWindowProcedure(HWND Window, UINT MsgId, WPARAM W, LPARAM L)  
  2. {  
  3.     static int Count = 0;  
  4.     switch(MsgId)  
  5.     {  
  6.   case WM_KEYDOWN:  
  7.     ++Count;  
  8.     if(Count % 2)  
  9.       OutputDebugStringA(“WM_KEYDOWN”);  
  10.       break;  
  11.     }  
  12.   
  13.     return DefWindowProc(Window, MsgId, W, L);  
  14. }  

<리스트 4> 병렬적으로 호출이 불가능한 메시지 프로시저

  1. LRESULT CALLBACK MyWindowProcedure(HWND Window, UINT MsgId, WPARAM W, LPARAM L)  
  2. {  
  3.     EnterCriticalSection(&GlobalCs);  
  4.     switch(MsgId)  
  5.     {  
  6.   case WM_KEYDOWN: OutputDebugStringA(“WM_KEYDOWN”); break;  
  7.     }  
  8.     LeaveSection(&GlobalCs);  
  9.   
  10.     return DefWindowProc(Window, MsgId, W, L);  
  11. }  

아직도 무슨 상황에 문제가 생긴다는 건지 잘 이해가 되지 않을 수도 있다. 그렇다면 위와 같은 메시지 핸들러를 가지고 다음과 같은 세 가지 경우를 모두 테스트해 보도록 하자.

1. 한 스레드에서 해당 메시지 핸들러를 사용하는 윈도우를 하나만 생성한 경우
2. 한 스레드에서 해당 메시지 핸들러를 사용하는 윈도우를 여러 개 생성한 경우
3. 여러 스레드에서 해당 메시지 핸들러를 사용하는 윈도우를 생성한 경우

올바로 디자인된, 다시 말하면 재진입 가능하도록 만들어진 메시지 프로시저라면 1, 2, 3의 경우에 모두 동일한 동작을 보장해야 한다. 만약 세 가지 중에 한 가지 경우라도 동작이 다르다면 해당 메시지 핸들러는 잘못 작성된 것으로 범용적으로 사용할 수 없음을 의미한다.

 0  0 



Read more: http://www.jiniya.net/wp/archives/1197#ixzz40KVbEKvT







Posted by GENESIS8

댓글을 달아 주세요