2014년 3월 19일 수요일

Java 와 멀티쓰레드.


자바는 멀티쓰레드 코딩을 위해 만들어진 언어이다?
단지 "synchronized" 키워드 하나 추가한 것밖에 없는데 지나친 과장이 아니냐고 반문할 수 있다. 그러나 이는 멀티쓰레드 코딩의 편리성을 위해 자바가 얼마나 커다란 대가를 치르고 있는지 잘 모르기 때문일 것이다.

애초에 자바는 Process 가 지원되지 않는 저급한 RTOS 상에서 실행될 수 있도록 설계되었고, 프로세스 대신에 쓰레드 단위로 어플리케이션을 관리할 수 있어야만 했다.

이에, 자바 플랫폼에 내장된 기본 클래스(SDK)들이 멀티쓰레드 환경에서도 사용 가능할 수 있도록 다음과 같이 파격적인 설계를 가지게 되었다.

1) Java 의 모든 객체는 동기화 가능하다.
모든 자바 객체는 최소 4byte이상의 숨겨진 동기화구조체(Monitor)를 내부에 가지고 있다. (참고로 모니터는 mutex 와 condvar 를 합쳐 놓은 것과 같다.).
JVM의 메모리가 꽉 차서 GC가 발생하는 상황을 생각해보자. 자바는 모든 객체를 Heap 에 할당하기 때문에, 큰 어레이를 제외하곤 일반적인 객체의 평균 데이터 크기는 수십 byte 에 불과하다.
프로그램마다 실행 시의 평균 객체 크기는 다양하겠지만 40 byte 라면 무려 메모리의 10%, 80 byte라고 해도 5% 이상 동기화구조체를 위해 사용되는 것이다.
실로 엄청난 양이 아닐 수 없다.

2) 자바가 제공하는 대부분의 기본 클래스는 자체적으로 동기화를 처리한다. 
예전에 피쳐폰용 자바 플랫폼(MID)의 화면 출력용 클래스인 Canvas  함수들의 동기화 처리 속도를 분석해 본 적이 있다. 특정 데모 프로그램의 경우, 단지 20개 정도 함수의 동기화 처리 여부에 따라 무려 20%나 속도 차이가 발생했다.
그 외 일반 어플들의 profile 통계를 개인적으로 집계해 본 결과 CPU 타임의 평균 15% 정도가 동기화 처리에 사용되고 있었다.
당시 MIDP프로그램들은 대부분 단일 쓰레드 프로그램이었다. 그럼에도 불구하고, 약 5~10%의 메모리와 15% 정도의 CPU 타임을 동기화 처리에 사용하고 있었다.
(참고로, 근래에 자바의 기본 Collection 클래스들의 함수는 동기화처리가 생략되어 있다. 대신 별도의 Wrapper 클래스를 통해 동기화처리를 제공하도록 변경되었으므로 위의 통계치는 다소 달라졌을 것이다.)
가용 메모리가 줄어든 만큼 GC도 더욱 자주 발생해서 퍼포먼스 문제는 가중될 수밖에 없다.
Java 언어 설계 당시 모든 클래스에 동기화 기능을 넣는 문제로 관련자들이 얼마나 심사숙고했을지 능히 짐작할 수 있는 부분이다.

3) 가장 단순하고 쉬운 동기화 방식을 지원한다.
최근에는 세마포어나 AtomicValue 뿐 아니라 병렬처리용 클래스 들도 자바 플랫폼에 많이 추가되었다. 그러나 이들 클래스는 일종의 확장 utility에 해당하고, 여전히 자바가 제공하는 기본적인 동기화 방법은 synchronized 키워드를 사용하는 것이다.
특정 함수 또는 특정 block 을 synchronized 영역으로 선언하면, JVM 내부에서 동기화를 위한 lock 과 unlock 을 자동으로 처리한다. 즉, 프로그래머의 실수에 의해 동기화 설정 및 해제가 잘못될 가능성이 전혀 없다.
이 외에 최상위 클래스인 Object 에 추가된 동기화 관련 API 5개와, Thread 클래스에 속한 10여개 API가 자바로 멀티쓰레드 코딩을 하기 위해 알아야 할 전부이다. 더 이상 없다. 이 얼마나 단순한가!
물론, 해당 기능 정도는 C++ 로도 충분히 동일하게 구현할 수 있고 상황에 맞춰 훨씬 더 최적화할 수 있을 것이다. 그렇다면 자바는 메모리와 속도 감소를 감수하고, 단지 동기화 관련 API 를 좀 더 쉽게 쓸 수 있게 한 것에 불과할까?
메모리와 속도 문제는 애초 자바의 출발점이었던 임베디드 환경에서는 훨씬 더 치명적인 문제이다. 그런 큰 희생은 쉽고 단순함 그 이상의 의미를 위한 것일 것이다. 그건 바로 자바를 최상의 멀티쓰레드 개발 플랫폼으로 만들기 위한 것이었음을 이어서 이야기하고자 한다.


4) Java 는 클래스 단위로 내부 데이터를 동기화한다.자바의 제공하는 모든 기본 클래스는 그 내부 데이터 동기화를 스스로 처리한다. 또한 이 원칙은 어플리케이션 개발 시에도 그대로 적용될 수 있다. 모든 객체가 동기화 처리가 가능한 구조이기 때문이다. 이는 멀티 쓰레드 시스템 개발시 매우 큰 차이를 만들어낸다.

이런 동기화 기능을 내장하지 않는 언어 사용시에는 각 객체를 사용할 때마다 동기화처리를 각각 해주어야만 하며, 그 중 단 하나라도 실수한다면 해당 프로그램은 정상적인 수행이 불가능해진다. C/C++ 코딩 시 자바처럼 객체 단위로 동기화 범위를 잘게 나누어 많은 수의 동기화 객체를 다루는 것은 거의 불가능에 가까운 난해한 코드가 될 것이다.

좁은 의미에서 멀티쓰레드 프로그램 개발 용이성이란, 단일 쓰레드용으로 개발된 코드를 멀티쓰레드용으로 변경하는 것이 얼마나 쉬운가를 나타낸다고 할 수 있다.

C/C++ 의 경우, 이 문제는 정말 난해한 것이다. 구조체를 다루는 거의 모든 함수가 동기화 처리가 되어 있지 않을 뿐 아니라, 어떻게 동기화 기능을 넣을지 암담할 것이다. 특히 다른 쓰레드와 공유된 객체의 메모리 해제 시점을 정확히 정하는 것도 큰 문제가 된다. 이에 제한된 코드 영역을 따로 나누어 쓰레드화하게 되는데, 만일 해당 쓰레드가 사용하는 코드와 구조체가 명확하고 양이 적지 않다면 엄두를 낼 수 없는 일이 되고 만다.

자바의 경우, 기본 클래스는 이미 동기화 처리가 되어 있고, 새로 추가한 클래스들도 함수명 앞에 synchronized 란 키워드만 추가하여 쓰레드 동기화 문제를 쉽게 처리할 수 있다. 정확히 말해 그 이상 할 수 있는 것이 아예 없다. 또한, 특별히 메모리 관리에 따로 신경 쓸 필요도 전혀 없다.
단지 멀티 쓰레드 환경에 맞게 기존 코드를 변경하는 문제만 비교한다면 자바와 필적할 만한 다른 플랫폼을 찾기 어려울 것이다.

실제로 멀티쓰레드 프로그램 개발이 어려운 이유는 동기화 API 사용법 때문이 아니라 디버깅이 어려운 것이 주 요인이다. 관련 버그는 재현도 어렵고, 추적도 매우 어렵다.

자바의 단순함이 빛을 발하는 것은 이때이다. 소스 코드만 읽어서 멀티쓰레드 관련 버그를 찾아내는 것이 다른 언어보다 월등하게 쉬운 것이 자바이다. 그것은 관련 구조의 간명함 때문에 가능해진다.

5) 결론: 자바는 쉬운 멀티쓰레드 코딩을 위해 만들어진 언어이자 플래폼이다.
병렬 컴퓨팅이란 예외적인 연구 분야를 제외하면, 일반적인 멀티쓰레드 코딩이란 펑펑 노는 CPU 자원을 효율적으로 나눠쓰도록 코딩하는 것을 말한다.
대부분의 인터넷 서버가 이에 해당하는데, DB 서버 연결을 포함한 네트웍 통신 속도나 하드디스크 IO속도가 CPU 보다 수백만배 이상 느리다. 이런  서버 환경에서는 자바의 실행 속도가 전혀 문제되지 않는다. 한 트랜젝션을 수행하는 동안 실제 CPU 동작 시간은 1~2%도 채 되지 않으므로 멀티쓰레드를 적절히 사용하여 서버 가용성을 높히는 것이 중요하기 때문이다.

이러한 멀티쓰레드 아키텍쳐 설계 시 반드시 필요한 두 가지 구조체가 있는데, 그 둘은 버퍼와 큐이다.
버퍼는 실행 처리 속도가 다른 두 쓰레드 간에 데이터를 교환하기 위한 수단이고, 큐는 서로 개수가 다른 입력 쓰레드들과 출력 쓰레드들간에 m:n 방식의 태스크 데이터를 공유하는 방식이다.
버퍼는 단일 쓰레드 환경에서도 사용되는 범용적인 것이지만 큐는 멀티쓰레드 환경에 최적화된 시스템에서 주로 사용된다. 쓰레드 수가 어느 한계선 이상 많아지면 그 자체만으로 컨텍스트 스위칭 부하가 더 커지고, 동기화 충돌이 자주 일어나 오히려 처리 속도가 더 감소하는 문제가 발생할 수 있다. 쓰레드 풀과 큐를 이용하면 이러한 문제를 적절히 처리할 수 있다.

큐나 버퍼에 넣을 데이터 생성 및 처리 시간이 매우 짧지 않는 한, 데이터 전달 시의 동기화 처리 속도는 문제가 되지 않는다. 결과적으로 멀티쓰레드 시스템의 효율성은 동기화 관련 API 의 속도가 아니라 관련 아키텍쳐의 효율성에 의해 크게 좌우되게 된다.

즉, 멀티쓰레드 코딩의 편리성은 아키텍쳐 변경의 용이성과 밀접한 연관성을 가진다.

Java 는 어떤 언어보다 리팩토링이 쉽다. 자바의 동기화는 늘 객체 단위로 처리되며 한 함수 또는 한 함수 내의 일정 영역 내에서만 이루어지기 때문이다. 이러한 단순함이 다소 불편할 수도 있으나 대신에 디버깅과 리펙토링 부분에서 큰 보상을 얻을 수 있다.

C/C++로 작성된 멀티쓰레드 서버를 예를 들어보자. 그러저럭 베타 수준 작업이 된 상태에서 누군가 아키텍쳐 개선 얘기를 꺼낸다면 다른 개발자의 반응이 어떨까? 이것이 가장 핵심적이고 심각한 문제이다. 만약 매우 중요한 제안이었음에도 불구하고 단지 위험을 회피하고, 그대로 출시하였다면, 결국 그 상태에서 코드에 더께가 계속 쌓여 나중엔 조금만 수정해도 문제가 발생하는 최악의 상황으로 치달을 수 있다.

단지 멀티쓰레드 코딩이 쉽다고해서 해당 언어가 인터넷 서버 개발에 적합하다고 말하는 것은 성급할 것이다. 그러나 근래엔 어떠한 서버 프로그램이든 그 서비스가 성공적으로 운영되는 시점에는 거의 필수적으로 멀티쓰레드 아키텍쳐를 필요로 하게 되었다. 만약 선호도나 숙련도의 문제를 떠나 여러 언어 중에 하나를 서버 개발용으로 선택하고자 한다면, 개인적으로는 자바를 강권하고 싶다.
개인적인 좁은 경험 내에서 아래의 내용은 사실이기 때문이다.
“멀티쓰레드를 많이 사용하는 인터넷 서버는 자바 언어를 쓸 때 개발 속도가 빠르며, 그 성능도 더 빠르다”.

2014년 3월 10일 월요일

격리된 쓰레드 메모리 모델 (1)

- 들어가는 말 : Thread-Safety 와 Transaction

Thread-Safety 란 상호 연관된 두 개 이상 데이터를 참조하거나 변경하는 일련의 코드를 다중 쓰레드 환경에서 수행할 때, 관련 데이터간의 연관성을 정확히 유지할 수 있는가를 가리킨다.


한 쓰레드가 일련의 데이터 변경을 마치기 전에 다른 쓰레드가 일부 데이터를 변경한다면 데이터간 연관성을 유지할 수 없다. 이런 문제는 특정 함수를 통해서만 해당 데이터들의 변경이 가능하도록 강제하고, 해당 함수 내에서 적절한 동기화(synchronization) 처리를 함으로써 쉽게 해결할 수 있다.


그러나 데이터 참조 시에는 위와 같이 쉽게 해결되지 않는다. 데이터에 대한 참조는 읽어오는 과정만이 아니라 그 사용이 끝나야 종료되기 때문이다. 이로 인해 큰 크기의 Vector 형 객체나 Tree 형 객체의 내부 데이터를 안전하게 사용하기 위해선 긴 시간의 동기화가 필요하다. 긴 시간의 동기화는 퍼포먼스 감소 및 내부의 다른 동기화 과정과 얽혀 쓰레드 데드락이 발생하는 원인이 되기도 한다. 이런 경우 비동기 처리방식을 주로 사용하게 되는데, 도중에 데이터 변경이 발생하더라도 처리의 정합성을 보장하는 것은 까다로운 작업에 속한다.


이렇듯 Thread-Safety 문제는 데이터를 읽고 쓰는 과정만이 아니라, 연관성을 가진 일련의 데이터를 처리하는 전체 과정으로 확대된다. 입력된 데이터와 그 결과물의 정합성이 필요한 데이터 처리 단위를 Transaction 이라 한다면, Thread-Safety 란 다중 쓰레드 환경에서 특정 Transaction 수행의 안정성을 의미하게 된다.


아래에 소개하고자 하는 Isolated Thread Memory Model (ITM : 격리된 쓰레드 메모리 모델) 은 위에 언급된 Transaction 단위의 Thread-Safety 를 용이하게 확보하는 방법에 대한 개인적 아이디어를 정리해 본 것이다.




- Isolated Thread Memory Model (ITM)

ITM 은 아래의 규칙을 가진 개발 방법론과 그 적용 방법에 관한 것이다. 
  1. Thread-Safe 함수는 그 내부에서 Thread-Safe Access 만이 허용된다.
  2. Thread-Safe Access 는 아래와 같으며, Thread-Safety 를 보장한다. - 다른 Thread 에 의해 변경될 수 없는 메모리의 참조 및 변경 - Thread-Safe 함수 호출.
  3. thread_managed 함수는 규칙 1의 제약을 받지 않는 예외적인 Thread-Safe 함수이다. - 즉, Thread-Safe 함수에 의해 호출될 수 있다. - 내부 구현시 Thread-Safety 문제를 스스로 해결하여야 한다.
  4. 트랜젝션 관리 클래스 내부에 한해 thread_managed 함수를 추가할 수 있다. 
  5. 트랜젝션 관리 클래스의 객체를 Thread-Safe 함수의 인자로 전달할 수 있다.
위의 규칙을 이용하면 Thread-Safe 한 코드 영역과 그렇지 않은 영역을 명확하게 나눌수 있다. Thread-safety 와 관련된 문제를 트랜젝션 관리 클래스 내로 한정하여 집중시킴으로써 분석 및 처리의 효율성을 높히게 된다. 아울러 트랜젝션 단위의 Thread Safety 처리 또한 트랜젝션 해당 클래스 내부로 한정해서 처리하는 것이 가능해진다.
그러나, 일반적인 언어로 코딩 시엔  위의 규칙 2항을 개발자가 직관적으로 판단하여 준수하는 것이 불가능하다. 이에, 위의 방법론을 적용하기 위해선 특정 함수가 위의 규칙을 만족하는지 즉, Thread-Safety 를 보장하는지 여부를 증빙할 수 있는 자동화된 도구가 필수적으로 필요하다.
아래에 C++, Java 등의 OOP 언어에 몇 가지 문법적 요소와 특별한 클래스를 추가함으로써 Thread-Safety 여부를 컴파일 단계에서 명확히 확인할 수 있는 방법을 제시해 보았다.  
아래 내용은 초안이며, 사용된 용어나 정의들은 이후 계속 수정될 수 있음을 미리 밝혀둔다. 아래보다 더 좋은 규격안을 제시하거나 잘못되거나 부족한 점에 대한 지적은 기쁘게 환영하겠다.

- Special classes

아래 나열된 클래스들의 하위 클래스들은 아래와 같이 특별 처리한다.
  • StackLocal class - 해당 객체는 다른 쓰레드와 공유될 수 없다.
    - 해당 객체는 스택 내부에 생성된다. - 해당 객체는 항상 Thread-Safe 객체 상태를 갖는다. - StackLocal 객체 만이 StackLocal 객체를 필드로 가질 수 있다.
  • SynchronizedAtomic class - 해당 객체가 동기화되어 있는 동안 외부 쓰레드가 데이터를 변경할 수 없다.
    - 해당 객체가 동기화되어 있는 동안 Thread-Safe 객체 상태를 갖는다.
    - java 의 SynchronizedCollection 이 이에 해당.
  • Immutable class - 해당 객체는 생성이 완료된 후, 내부 데이터 변경이 불가능하다. - java 의 String, Enum 이 이에 해당.

  • ITMTransaction class - thread_managed 함수를 가질 수 있다. - StackLocal 클래스의 하위 클래스이다. 즉 스택 내부에만 생성된다. - 중요한 특수 함수. protected: void cancelTransaction(Exception e) thread_managed; // Exception 에 의해 비정상 종료시 자동 호출된다. public: thread_safe<T> getSnapshot(T*) thread_managed; // 주어진 객체의 복사본을 반환한다. protected: thread_safe<T> guaranteeThreadSafe(T*) thread_managed; // 주어진 포인터를 thread_safe 포인터로 변환한다. protected: thread_safe<T> lock(SynchronizedAtomic*) thread_managed; // 주어진 객체를 동기화 상태로 만들고 thread_safe 포인터를 반환한다. protected: void lock(SynchronizedAtomic*) thread_managed; // 주어진 객체를 동기화 상태를 해제한다.


- Thread-Safe 객체 상태 변경과 ITMTransaction

특정 객체가 외부 쓰레드에 의한 데이터 변경이 불가능한 상태에 있는 경우 해당 객체를 Thread-Safe 객체라 부른다.
특정 객체의 포인터 값을 다른 쓰레드와 공유된 메모리에 기록하는 순간 해당 객체는 Thread-Safe 상태를 잃게 된다.  
Thread-Safe 하지 않은 객체를 Thread-Safe 상태로 변경하는 가장 바람직한 방법은 getSnapshot() 함수를 이용해 해당 객체의 복사본을 얻는 것이다. 새로운 복사본의 변경 사항은 트랜젝션이 성공적으로 종료한 경우에 한해서 실제 메모리에 반영되므로 Thread-Safety 문제를 발생시키지 않는다. (이에 대해서는 다음 글에 설명).
특정 객체가 동기화(synchronized)된 상태일 때 그 데이터 변경이 불가능하다면, 해당 상태 동안 Thread-Safe 객체로 취급되며, 명시적 변환없이 해당 포인터를 thread_safe 포인터로 사용할 수 있다. 이를 사용하기 위해선 Java 의 synchronized 키워드처럼 동기화 영역을 명확히 설정할 수 있는 방법이 다른 언어에도 필요하다.
단, Thread-Safe 함수 내에서는 동기화 처리가 금지된다. 해당 함수를 잠깐 수행하는 동안의 동기화 처리는 문제되지 않더라도, 전체 트랙젝션 단위에서 볼 때는 Thread-Safe 하지 않을 수 있기 때문이다. 트랜젝션 단위의 긴 동기화는 lock(), unlock() 함수를 통해 처리한다. 참고로, Exception 발생시 unlock 처리는 자동으로 이루어진다.
또한, 특정 객체에 대한 포인터가 다른 쓰레드와 공유된 상태이기는 하나, 해당 객체에 대한 독점적인 변경 권한을 가지고 있으며, 내부 데이터 변경시 동기화가 필요없는 것을 확증할 수 있을 때는 해당 객체를 Thread-Safe 상태로 변경하여도 무방하다. 이런 경우 guaranteeThreadSafe() 함수를 사용한다.  
(이러한 강제 변환 시에는 Snapshot 이 원본 객체와 동일하게 생성된다.만약 미리
만들어진 Snapshot 이 존재하는 경우에는 오류가 발생한다)


- Thread-Safety Type 선언

변수, 필드, 함수 선언시 아래와 같이 Thread-Safety 유형을 선언한다.
  • thread_safe 포인터 ex) thread_safe <Foo> foo; - Thread-Safe 객체만을 가리키는 포인터이다. - 필드 선언 시에는 StackLocal 유형 클래스 내에서만 사용 가능하다.
  • thread_unsafe 포인터) - Thread-Safety 유형이 명시되지 않은 일반 포인터를 따로 구분하기 위하여 thread_unsafe 포인터라 칭한다.
    - 해당 포인터가 가르키는 객체의 Thread-Safe 상태를 확인할 수 없음을 의미한다.
  • local 함수 - ex) Foo* getValue() local; - Thread-Safe 객체 상태인 경우에 한해, Thread-Safety 가 보장되는 함수이다. - Thread-Safe-Access 외에 추가로 내부 데이터의 참조/변경이 허용된다.
  • thread_safe 함수 - ex) int getType() thread_safe; - Thread Safe 객체 상태와 관계없이 Thread-Safety 를 보장하는 함수이다. - StackLocal 객체 외에는 내부 데이터의 참조/변경이 허용되지 않는다. 단, 변경 불가능한 데이터 참조는 허용된다.
  • local thread_safe 함수 - ex) void setValue(String* value) local thread_safe; - Thread-Safe 객체 상태에서만 호출 가능한 local 함수이다. - 내부 데이터 참조/변경 시에도 Thread-Safety가 보장된다.
  • thread_unsafe 함수 - ex) void setParent(Node* node, Node* parent) thread_unsafe; - thread_unsafe 를 명시하기 위해 사용된다.
  • thread_managed 함수 - ex) void setParent(Node* node, Node* parent) thread_managed; - Thread-Safety 문제 처리를 담당하는 함수이다. - ITMTransaction 및 그 하위 클래스 내에서만 선언가능하다.
  • 함수의 Thread Safety 기본값 - Immutable 유형 클래스 내 함수 : thread_safe - ITMTransaction 유형 클래스 내 함수 : thread_safe - 그 외 : thread_unsafe
  • 사용 예 class Buffer { char* data; int length; Buffer(int length) { data = new char[length]; this->length = length; } char* getBuffer() local; int getLength() local; int getType() thread_safe; void init(thread_safe<char> data, int len) local thread_safe; };

- thread_safe 포인터 속성 변환

  • StackLocal 객체에 대한 포인터는 명시적 선언이 없어도 항상 thread_safe 포인터로 취급된다.

  • thread_unsafe 포인터는 ITMTransaction 내부 함수를 통해서만 thread_safe 포인터로 전환될 수 있다.
  • thread_safe 포인터는 thread_unsafe 포인터로 변환될 수 있다.
  • new operator, clone 등 객체 생성용 함수는 thread_safe 포인터를 반환한다.



- 해설과 문제점.

이로써 기존 언어에 추가되어야 하는 문법적 요소와 규칙에 대한 정의는 마무리되었다.  
위 문법적 요소는 임의의 thread_safe 포인터가 가리키는 객체의 Thread-Safe 상태가 항상 유지됨을 보장하기 위한 것으로 요약될 수 있다.
위 문법은 소스 분석만으로 Thread-Safety를 증빙하기 위해선 필수적으로 필요하긴 하나 그로 인해 코드 개발에 있어 큰 제약을 가지게 되어 실용성에 문제를 안고 있다.
StackLocal 유형을 제외한 모든 클래스 필드는 thread_safe 포인터를 필드로 가질 수 없다. 이 때문에 거의 모든 함수가 thread_unsafe 포인터를 반환하게 된다. 실제로 특정 Thread-Safe 함수 내에서 ITMTransaction 객체를 사용하지 않았다면, 해당 함수는 -그 복잡성 여부를 떠나- 함수형 언어로 변환이 가능할 정도로 Thread-Safe 함수의 제약 조건은 가혹하다.
Thread-Safe 함수 내에서 공유된 객체의 Sanpshot 생성을 자동으로 처리해주면 Thread-Safe 함수의 구현이 상당히 용이해질 수 있다. 이러한 자동처리는 기존의 Transactional Memory Model 과 개념상 동등한 Transaction 단위의 메모리 관리 기술이 필요하게 된다. 다음 글에서는 이와 관련된 메모리 버젼 관리 시스템과 기존의 표준 라이브러리 활용 방안에 대해서도 다뤄보고자 한다.
그에 앞서 이번 글은 위의 방법론과 문법적 요소에 대해 객관적 검증의 기회를 얻고자 쓰여진 것이다. 이 글은 최소의 문법적 요소를 추가함으로써 특정 함수의 Thread-Safety를  검증하는 것이 가능하다는 것을 증명하는 것이 주목적이다. 이에 내부의 메모리 관리 방식 등 좀 복잡한 내용은 다음로 미루고자 한다.
이 글의 논리적 오류에 대한 구체적인 지적과 조언을 기다린다.
끝으로 이해를 돕고자 아래에 간단한 Java 예제 코드를 추가하였다.



- Java 코드 예제


class Result { ... } interface Inspector { void inspect(String content) thread_safe; }; class ConcurrentTransaction extends ITMTransaction { Vector targetUrls; thread_safe<Inspector> inspector;  thread_safe<Vector> results ;
ConcurrentTransaction(Vector searchUrls, Inspector inspector) { this.targetUrls = searchUrls; this.inspector = inspector.clone(); this.results = new Vector; } public thread_safe<Vector> runTask() local throws Exception { while (true) { String url = popData(); if (url == null) { return; } try { Result res = doTask(url); if (res != null) { results.add(res); } } catch (Exception e) { logDebugInfo(e); } } return results; } Result doTask(String url) thread_safe { // 특정 Web-Page 열어, 내용을 다운로드 받아 문자열 생성. return inspector.inspect(webContents); } String popData() thread_managed { synchronized (targetUrls) { int cntRemainTask = targetUrls.size(); if (cntRemainTask == 0) { return null; } String url = targetUrls.lastElement(); targetUrls.setSize(cntRemainTask - 1); return url; } } void addResult(Result res) thread_managed { results.add(res); } private void commitResultToDBMS() thread_managed { // 결과를 DB 에 기록. } // Exception 에 의한 비정상 종료 시 자동 호출된다. protected void cancelTransaction(Exception e) thread_managed { logDebugInfo(e); } private void logDebugInfo(Exception e) thread_managed { // 로그 기록 } }