다른 예를 들어보면 만약 .NET을 사용하여 서버를 제작할 때 하나의 클라이언트에 하나의 스레드를 할당하게 된다면, 1024명의 동접 사용자가 있을 경우 이미 스택 메모리를 위해서만 1GB 메모리가 사용된다는 것 입니다.
또, 쓰레드를 많이 생성시켜 병렬처리를 하는 예에서도 이러한 Managed 쓰레드의 메모리 사용이 걸림돌이 됩니다.
전에 최대 생성 가능 스레드를 얘기하며, 스택오버플로우 예외처리에 관해 잠시 언급하였다. 오늘은 이에 관해 자세히 짚어 보자.
사실 이 주제는 멀티스레드 프로그래밍과 어느 정도 연관성을 가지고 있다. 예외처리(Exception handling)는 정상적인 프로그램의 흐름을 바꾸는 상황을 처리하기 위해 프로그래밍 언어에서 지원되는 것이다. 간단한 예로 "page fault" 혹은 "access violation" 이라고 불리우는 잘못된 메모리를 참조하는 경우이다. 이 문제는 가장 흔하게 발생하는 문제이며, 가장 골칫 거리이기도 하다. 초기화가 잘못된 메모리 변수를 사용하거나, 잘못된 메모리의 동적 할당 및 해제, 버퍼 오버플로우(buffer overflow) 등등 여러가지 원인으로 나타난다. 또, "divde by zero"와 같은 것도 흔한 예외 상황 중의 하나이다.
이러한 예외가 발생하면 프로그램은 스스로 오류를 복구하여 계속 진행하는 것이 불가능해지며, 예외처리를 하지 않은 경우 프로그램은 종료되고, 윈도우즈 같은 경우 윈도우즈 에러 리포트 다이얼로그가 나타난다.
그런데 멀티스레드 프로그램에서, 어떤 하나의 스레드가 실행하는 코드에서 예외상황이 발생했을 경우를 생각해 보자. 특히, 이 프로그램이 안정성이 매우 중요시되는 서버 프로그램일 경우, 그리고, 보통 이러한 서버 프로그램은 스레드풀(thread pool)을 가지고 병렬수행이나 비동기 작업을 수행하고 있다고 생각하면, 하나의 스레드에서 발생한 예외상황으로 전체 프로그램이 종료된다면 큰 문제가 될 수 있다. 그래서, 멀티스레드 프로그램에서는 예외상황에서도 안전한 코드(exception safety)를 작성하는 것이 매우 중요하다.
Visual C/C++에서는 예외처리를 위한 try/catch를 비롯한 몇가지 기능을 제공한다. 여기서는 윈도우즈의 SEH(Structured Exception Handler)를 지원하는 __try/__except 를 사용하여 스택오버플로우 예외를 처리하는 방법을 알아보자. (일반 C++ try/catch 로는 스택오버플로우를 처리할 수 없다.)
다른 예외와는 달리 스택오버플로우의 특징은, 스택을 더 이상 사용할 수 없기 때문에 __except 구문 안에서 스택을 많이 사용하는 함수 호출이나 지역변수의 사용을 금해야한다는 것이다. 그리고 가장 중요한 것은 스택의 메모리 보호페이지(guard page)가 사라진다는 것이다. 이와 관련된 좋은 MSDN의 예제가 아래 웹페이지에 있다.
http://support.microsoft.com/kb/315937
위 MSDN에 나오는 예제를 보면 스택오버플로우를 두 번 이상 발생시키면 두번째부터는 access violation으로 바뀌는 것을 알 수 있다. 그 이유는 스택오버플로우 예외가 발생한 경우 스택이 손상되기 떄문이다. 스택의 맨 위 페이지는 PAGE_GUARD 페이지로 이 페이지를 액세스하려고 하면 윈도우즈는 이 guard page를 commit하여 사용가능하게 만들고 새로운 guard page를 그 바로 위에 만든다. 그런데 스택의 최대 크기는 프로그램 컴파일 당시 "/STACK:reserve[,commit]" 옵션으로 설정되거나 CreateThread API의 파라미터로 결정되며 이 크기 이상으로 더 커지려고할 경우 stack overflow가 발생되는 것이다. 이 경우 더이상 예약해놓은 메모리가 없으므로 guard page설정이 불가능해 진다.
프로그램에서 스택오버플로우가 생기는 주요인은 재귀함수의 잘못된 사용에 의한 것이다. 보통 디버깅을 위해서, __except 구문안에서 현재 콜스택을 로그 파일로 남기서나 덤프파일을 만드는데, 여기서 스택오버플로의 경우 특별히 처리해야할 사항은 다음과 같다.
이밖에도 멀티스레드 프로그램에서는 어떤 스레드에서 예외상황이 발생했을 경우 이를 정상적으로 복구하는 것은 쉽지 않다. 특히 그 스레드가 Critical Section과 같은 lock을 가지고 있다면, dead lock을 발생시킬 확률이 매우 높다. 왜냐면 예외처리 루틴은 그 스레드가 어떤 lock을 가지고 있었는지 알기 힘들기 때문이다. 또, 동적메모리 할당을 했을 경우 그것을 해제 하기 어렵기 때문에, memory leak이 발생한다.