반응형

쓰레딩과 동기화


쓰레딩과 동기화는 Java Language Specification (JLS)에 명시되어 있듯, 자바 프로그래밍 언어의 핵심 기능이다. RTSJ는 JLS의 핵심 기능들을 수 많은 방식으로 확장한다. (참고자료) 예를 들어, RTSJ는 일반 자바 쓰레드 보다 더욱 엄격한 스케줄링 정책을 따라야 하는 새로운 실시간(RT) 쓰레드 유형을 도입했다. 또 다른 예로, 우선 순위 상속(priority inheritance)이 있는데, 이는 잠금이 경쟁할 때 잠금 동기화가 관리되는 방법을 정의한 잠금 정책이다.

우선 순위와 우선 순위 큐의 관리를 이해하는 것이 RTSJ의 쓰레딩과 동기화 변화를 이해하는데 유용하다. 우선 순위 역시 RT 애플리케이션에는 중요한 장치이다. 이 글에서는 쓰레드 우선 순위와 우선 순위 큐가 관리되는 방법을 논하면서 RTSJ 쓰레딩과 동기화 측면을 설명하도록 하겠다. IBM WebSphere® Real Time을 사용하여 구현하는 것을 포함하여, RT 애플리케이션을 개발, 전개, 실행할 때 고려해야 하는 사항들도 설명한다. (참고자료).

일반 자바 쓰레드 이해하기

JLS에서 정의된 쓰레드는 일반 자바 쓰레드(regular Java threads)라고 한다. 일반 자바 쓰레드는 1부터 10까지 정수 우선 순위를 가진 java.lang.Thread 클래스의 인스턴스이다. 많은 실행 플랫폼을 수용하기 위해 JLS는 일반 자바 쓰레드의 우선 순위 구현, 스케줄링, 관리 방식에 많은 유연성을 가져왔다.

WebSphere Real Time을 포함하여, 리눅스 기반 WebSphere VM은 리눅스® OS가 제공하는 네이티브 쓰레딩 서비스를 사용한다. 리눅스 쓰레딩과 동기화를 이해함으로써 자바 쓰레딩과 동기화를 이해할 수 있다.

리눅스 쓰레딩과 동기화

리눅스 OS는 그 동안 다양한 사용자 레벨 쓰레딩 구현을 제공했다. Native POSIX Thread Library (NPTL)는 리눅스용 최신 전략적 쓰레딩 방향이고 WebSphere VM에서 사용된다. (참고자료) 전 구현에 비해 NPTL이 가진 장점은 POSIX 호환성과 더 나은 성능을 보인다는 점이다. POSIX 서버는 libpthread.so 동적 라이브러리와 기반 리눅스 커널 지원을 통해 런타임에서 사용할 수 있다. 리눅스 커널은 쓰레드 우선 순위 레벨 같은 정적 컨트롤과 시스템에서 실행되는 쓰레드의 동적인 조건을 기반으로 쓰레드 스케줄링을 수행한다.

POSIX에서는 다양한 스케줄링 정책과 우선 순위를 사용하여 POSIX 쓰레드(pthreads)를 만들어서 다양한 애플리케이션 필요를 채울 수 있다. 다음은 스케줄링 정책들이다.

  • SCHED_OTHER
  • SCHED_FIFO
  • SCHED_RR

SCHED_OTHER 정책은 프로그램 개발 툴, 오피스 애플리케이션, 웹 브라우저 같은 전통적인 사용자 태스크에 사용된다. SCHED_RRSCHED_FIFO는 보다 결정론적이며 시의성을 요구하는 애플리케이션에 사용된다. SCHED_RRSCHED_FIFO의 주요한 차이는 SCHED_RR 은 쓰레드의 실행에 타임 슬라이스 방식을 취하는 반면, SCHED_FIFO는 그렇지 않다는 점이다. SCHED_OTHERSCHED_FIFO 정책은 WebSphere Real Time에 의해 사용되며, 아래에 자세히 설명해 놓았다. (WebSphere Real Time이 사용하지 않는 SCHED_RR 정책에 대해서는 다루지 않겠다.

POSIX는 pthread_mutex 데이터 유형을 통해 잠금과 동기화 지원을 제공한다. pthread_mutex는 다양한 잠금 정책들로 만들어 질 수 있다. 잠금 정책은 한 개 이상의 쓰레드가 같은 잠금을 동시에 얻어야 할 때 실행 방식에 영향을 준다. 표준 리눅스 버전들은 하나의 기본 정책을 지원하지만, RT 리눅스 버전은 우선 순위 상속 잠금 정책도 지원한다. 동기화 개요 섹션에서 우선 순위 상속 정책에 대해 자세히 설명하겠다.

리눅스 스케줄링과 잠금에는 first in, first out (FIFO) 큐 관리가 포함된다.

일반 자바 쓰레드에서의 쓰레드 스케줄링

RTSJ에서는 일반 자바 쓰레드의 작동이 JLS에 정의된 것과 같다고 명시하고 있다. WebSphere Real Time에서, 일반 자바 쓰레드는 리눅스의 POSIX SCHED_OTHER 스케줄링 정책을 사용하여 구현된다. SCHED_OTHER 정책은 결정론을 필요로 하는 태스크 보다는 컴파일러와 워드 프로세서 같은 애플리케이션에 적용된다.

2.6 리눅스 커널에서, SCHED_OTHER 정책은 40 개의 우선 순위 레벨을 지원한다. 이러한 40 개의 우선 순위 레벨은 프로세서 기반으로 관리된다.

  • 리눅스는 캐시 성능 때문에 같은 프로세서에 하나의 쓰레드를 실행한다.
  • 쓰레드 스케줄링은 시스템 당 잠금 대신에 프로세서 당 잠금을 주로 사용한다.

필요할 경우, 리눅스는 하나의 프로세서에서 다른 프로세서로 쓰레드를 이동하여 워크로드의 균형을 맞춘다.

40개의 우선 순위 레벨에서, 리눅스는 활성 큐(active queue)와 종료 큐(expiration queue)를 관리한다. 각 큐에는 연결된 쓰레드 리스트가 포함되어 있거나 비어있다. 활성 큐와 종료 큐는 효율성, 로드 밸런싱, 기타 목적에 쓰인다. 이론상으로는, 시스템을 각 40 개의 우선 순위에 대해 실행 큐(run-queue)라고 하는 하나의 FIFO 큐를 관리하는 것으로 볼 수 있다. 쓰레드는 가장 높은 우선 순위를 가진 비어있지 않은 실행 큐부터 내보낸다. 이 쓰레드는 큐에서 제거되고 time quantum 또는 time slice라는 기간 동안 실행된다. 실행 쓰레드가 time quantum을 종료하면, 실행 큐의 끝에 배치되고 새로운 time quantum이 할당된다. 큐의 헤드에서 파견하고 종료된 쓰레드를 큐의 끝에 배치함으로써, 우선 순위 내에서 round-robin 방식으로 실행한다.

쓰레드에 주어진 time quantum은 쓰레드의 할당된 우선 순위에 기반한다. 높은 할당 우선 순위를 가진 쓰레드에게는 더 오랜 실행 time quantum이 주어진다. 쓰레드가 CPU를 차지하지 않도록 하기 위해, 리눅스는 쓰레드가 I/O 바운드 인지 또는 CPU 바운드인지에 따라서 쓰레드의 우선 순위를 높이거나 낮춘다. 쓰레드는 yielding (Thread.yield() 호출)에 의해 time slice를 자발적으로 포기하거나, 블로킹에 의해서 컨트롤을 포기한다. 이 시점에서 쓰레드는 이벤트 발생을 기다린다. 이 같은 이벤트는 잠금 해제를 통해 실행될 수 있다.

WebSphere Real Time의 VM은 40개의 SCHED_OTHER 리눅스 쓰레드 우선 순위 범위에서 10 개의 일반 자바 쓰레드 우선 순위는 명확히 할당하지 않는다. 모든 일반 자바 쓰레드는, 자바 우선 순위와 관계 없이, 기본 리눅스 우선 순위로 할당된다. 기본 리눅스 우선 순위는 40개의 SCHED_OTHER 우선 순위 범위의 중간에 있다. 기본을 사용함으로써, 일반 자바 쓰레드는 리눅스의 동적인 우선 순위 조정에 관계 없이, 실행 큐에 있는 모든 일반 자바 쓰레드가 실행되도록 한다. 이것은 RT 쓰레드를 실행하는 시스템을 실행하거나 실행하지 않는 일반 자바 쓰레드들만을 가진 시스템으로 가정한다.

WebSphere Real Time의 VM과 비 RT 버전의 WebSphere VM은 일반 자바 쓰레드에 SCHED_OTHER 정책과 기본 우선 순위 할당을 사용한다. 같은 정책을 사용함으로써, 두 개의 JVM들은 비슷하지만 동일하지는 않은 쓰레드 스케줄링과 동기화 특성을 갖추게 된다. RTSJ와 RT Metronome 가비지 컬렉터(참고자료 보기)의 도입으로 인한 WebSphere Real Time 클래스 라이브러리의 변경, JVM의 변경, JIT 컴파일러의 변경 때문에 두 JVM에서 동일한 타이밍과 성능 특성으로 애플리케이션이 실행될 수 없도록 한다. IBM WebSphere Real Time 테스팅 시, 타이밍 차이는 다른 JVM에서 수년 동안 잘 실행되었던 테스트 프로그램에서 경쟁 조건(다시 말해서, 버그)을 해결했다.




일반 자바 쓰레드를 사용한 코드 예제

Listing 1은 두 개의 쓰레드에 의해서 5초 간격으로 얼마나 많은 루프의 반복이 실행될 수 있는지를 보여주고 있다.


Listing 1. 일반 자바 쓰레드

                
class myThreadClass extends java.lang.Thread {
   volatile static boolean Stop = false;

   // Primordial thread executes main()
   public static void main(String args[]) throws InterruptedException {

      // Create and start 2 threads
      myThreadClass thread1 = new myThreadClass();
      thread1.setPriority(4);    // 1st thread at 4th non-RT priority
      myThreadClass thread2 = new myThreadClass();
      thread2.setPriority(6);    // 2nd thread at 6th non-RT priority
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      Thread.sleep(5*1000);
      Stop = true;
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

Listing 1의 프로그램에는 일반 자바 쓰레드들인 세 개의 사용자 쓰레드가 있다.

  • 기본 쓰레드
    • 프로세스 시작 시 생성된 메인 쓰레드이며, main() 메소드를 실행한다.
    • main()은 두 개의 일반 자바 쓰레드를 생성한다. 한 쓰레드는 우선 순위 4이고, 또 다른 쓰레드는 우선 순위 6이다.
    • 메인 쓰레드는 Thread.sleep()을 호출함으로써 5초 동안 수면(sleep)하는 것으로 차단을 수행한다.
    • 5초의 수면 후에, 이 쓰레드는 다른 두 개의 쓰레드 종료를 명령한다.
  • 우선 순위 4의 쓰레드
    • 이 쓰레드는 기본 쓰레드에 의해 생성되며, for 루프를 포함하고 있는 run() 메소드를 실행한다.
    • 이 쓰레드는,
      1. 각 루프 반복 시 카운트를 늘린다.
      2. Thread.yield()를 호출함으로써 타임 슬라이스를 자발적으로 포기한다.
      3. 메인 쓰레드가 요청될 때 종료한다. 종료 직전에, 쓰레드는 루프 카운트를 프린트 한다.
  • 우선 순위 6의 쓰레드: 이 쓰레드는 우선 순위 4 쓰레드와 같은 액션을 수행한다.

이 프로그램이 유니프로세서(uniprocessor) 또는 로딩되지 않은 멀티프로세서 시스템에서 실행된다면, 각 쓰레드는 for 루프 반복 카운트에 가까운 수를 프린트 한다. 프린트 내용은 다음과 같다.

Created thread
Created thread
Thread terminates. Loop count is 540084
Thread terminates. Loop count is 540083

Thread.yield()에 대한 호출이 제거되면, 두 개의 쓰레드에 대한 루프 카운트들은 거의 비슷하지만 동일하지는 않다. 두 쓰레드 모두 SCHED_OTHER 정책의 같은 기본 우선 순위가 할당되었기 때문에, 두 쓰레드에는 같은 타임 슬라이스가 주어진다. 이 쓰레드는 동일한 코드를 실행하기 때문에 비슷한 동적인 우선 순위 조정을 해야 하고 같은 실행 큐에서 round-robin 방식으로 실행되어야 한다. 하지만, 우선 순위 4의 쓰레드가 첫 번째로 실행되기 때문에, 5초 실행 간격에서 더 많은 부분을 차지하고 더 높은 루프 카운트로 출력되는 것이다.




RT 쓰레드 이해하기

RT 쓰레드는 javax.realtime.RealtimeThread의 인스턴스이다. RTSJ는 이 스팩이 RT 쓰레드에 최소한 28 개의 우선 순위를 제공해야 한다고 요구하고 있다. 이러한 우선 순위들을 실시간 우선 순위(real-time priorities)라고 한다. RT 우선 순위 범위의 시작 값에 대해서는 10보다 커야 한다고 스팩에서는 지정하고 있다. 이는 일반 자바 쓰레드에 주어진 가장 높은 우선 순위 값이다. 애플리케이션 코드는 새로운 PriorityScheduler 클래스의 getPriorityMin()getPriorityMax()를 사용하여 가용 RT 우선 순위 값의 범위를 정한다.

RT 쓰레드의 동기

JLS의 쓰레드 스케줄링은 정확하지 않고 단 10 개의 우선 순위 값을 제공한다. POSIX SCHED_OTHER는 다양한 애플리케이션의 필요를 채우고 있다. 하지만 SCHED_OTHER 정책에도 바람직하지 못한 특성들이 있다. 동적인 우선 순위 조정과 타임 슬라이스가 예견하지 못한 시간에 발생할 수 있다. 40개의 SCHED_OTHER 우선 순위도 충분하지 않으며, 이러한 우선 순위 범위는 이미 일반 자바 쓰레드와를 가진 애플리케이션과 동적 우선 순위 조정은 당연한 것이다. JVM 역시 가비지 컬렉션(GC) 같은 특별한 목적에 내부 쓰레드용 우선 순위를 필요로 한다.

결정론의 부족, 더 많은 우선 순위 레벨의 필요, 기존 애플리케이션과의 호환성 등이 새로운 스케줄링 기능을 제공하는 확장을 만드는 동기가 되었다. javax.realtime 패키지의 클래스들은 RTSJ에서 설명한 대로, 이러한 기능들을 제공한다. WebSphere Real Time에서, Linux SCHED_FIFO 스케줄링 정책은 RTSJ 스케줄링 필요를 다루고 있다.

RT 자바 쓰레드에 대한 쓰레드 스케줄링

WebSphere Real Time에서, 28개의 RT 자바 우선 순위들이 지원되고 11에서 38까지의 범위를 갖고 있다. PriorityScheduler 클래스의 API는 이러한 범위를 검색하는데 사용된다. 이 섹션에서는 RTSJ에서 설명하고 있는 쓰레드 스케줄링을 상세히 설명하고, RTSJ의 요구 사항들에 부합하는 Linux SCHED_FIFO 정책을 설명하도록 하겠다.

RTSJ는 RT 우선 순위들이 각 RT 우선 순위들을 위해 개별 큐를 관리하는 런타임 시스템에 의해 논리적으로 구현되고 있는 것으로 간주한다. 쓰레드 스케줄러는 비어있지 않은 가장 높은 우선 순위 큐의 헤드에서 파견된다. 어떤 쓰레드도 RT 우선 순위용 큐에 없다면, 일반 자바 쓰레드가 파견된다. (일반 자바 쓰레드에 대한 쓰레드 스케줄링).

RT 우선 순위로 파견된 쓰레드는 차단될 때까지 실행될 수 있고, yielding에 의해서 컨트롤을 자발적으로 포기하거나, 더 높은 RT 우선 순위를 가진 쓰레드로 선점된다. 자발적으로 양보하는 RT 우선 순위를 가진 쓰레드는 큐의 뒤쪽에 배치된다. RTSJ 역시 이 같은 스케줄링이 일정한 시간으로 수행되어야 함을 명시하고 있고 실행 중인 RT 쓰레드의 수 같은 요소에 기반하여 달라질 수 없다고 명시하고 있다. RTSJ의 1.02 버전은 이러한 규칙을 유니프로세서 시스템에 적용하고 있다. RTSJ에는 멀티프로세서 시스템에 스케줄링이 어떻게 발생하는지에 대해서는 언급되어 있지 않다.

리눅스는 모든 RTSJ 스케줄링 요구 사항에 SCHED_FIFO 정책을 제공한다. SCHED_FIFO 정책은 사용자 태스크가 아닌 RT용이다. SCHED_FIFOSCHED_OTHER 정책과 달리 99 개의 우선 순위 레벨을 제공한다. SCHED_FIFO는 쓰레드에 타임 슬라이스를 수행하지 않는다. 또한, SCHED_FIFO FIFO 정책은 우선 순위 상속 잠금 정책을 제외하고는 RT 쓰레드의 우선순위를 동적으로 조정하지 않는다. (동기화 개요 섹션) 우선 순위 상속 때문에 우선 순위 조정은 RTSJ에 필요한 것이다.

리눅스는 RT 쓰레드와 일반 자바 쓰레드에 일정한 시간 스케줄링을 제공한다. 멀티프로세서 시스템에서, 리눅스는 가용 프로세서로 파견된 RT 쓰레드의 하나의 글로벌 큐의 작동을 모방한다. 이는 RTSJ의 원리와 흡사하지만, 일반 자바 쓰레드에 사용되는 SCHED_OTHER 정책과는 다르다.

RT 쓰레드를 사용하는 문제가 많은 코드 예제

Listing 2는 Listing 1의 코드를 수정하여 일반 자바 쓰레드 대신 RT 쓰레드를 만든다. java.lang.Thread 대신 java.realtime.RealtimeThread를 사용한다는 것부터 다르다. 첫 번째 쓰레드는 4의 RT 우선 순위 레벨로 생성되고, 두 번째 쓰레드는 6의 RT 우선 순위 레벨로 생성된다. 이는 getPriorityMin() 메소드에 의해 결정된 것이다.


Listing 2. RT 쓰레드

                
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
   volatile static boolean Stop = false;

   // Primordial thread executes main()
   public static void main(String args[]) throws InterruptedException {

      // Create and start 2 threads
      myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
      // want 1st thread at 4th real-time priority
      thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
      myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
      // want 2nd thread at 6th real-time priority
      thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      Thread.sleep(5*1000);
      Stop = true;
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         // Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

Listing 2의 수정된 코드는 몇 가지 문제를 갖고 있다. 프로그램이 유니프로세서 환경에서 실행되면, 절대로 종료되지 않고 다음을 프린트 한다.

Created thread

이 결과는 RT 쓰레드 스케줄링 작동에 의해 설명될 수 있다. 기본 쓰레드는 일반 자바 쓰레드로 남아있고, 비 RT(SCHED_OTHER) 정책으로 실행된다. 기본 쓰레드가 첫 번째의 RT 쓰레드를 시작하자마자, RT 쓰레드는 기본 쓰레드를 선점하고, RT 쓰레드는 무한정 실행된다. Time quantum에 의해 한정되지 않았고 쓰레드 차단도 수행하지 않기 때문이다. 기본 쓰레드가 선점된 후에는 실행될 수 없기 때문에 두 번째 RT 쓰레드를 시작할 수 없다. Thread.yield()는 기본 쓰레드를 실행하는데 어떤 영향도 줄 수 없다. yielding은 논리적으로 RT 쓰레드를 실행 큐의 끝에 배치하지만, 이 쓰레드 스케줄러는 같은 쓰레드를 다시 파견한다. 이것이 가장 높은 우선 순위를 가지고 실행 큐의 앞에 있는 쓰레드이기 때문이다.

프로그램은 또한 Two-processor 시스템에서는 실패한다. 다음이 프린트 된다.

Created thread
Created thread

기본 쓰레드는 두 개의 RT 쓰레드를 만들 수 있다. 하지만, 두 번째 쓰레드를 만든 후에, 기본 쓰레드는 선점되고 쓰레드에게 종료를 명령할 수 없다. 두 개의 RT 쓰레드는 Two-processor 시스템에서 실행되고 절대로 차단하지 않기 때문이다.

프로그램은 끝까지 실행되고 세 개 이상의 프로세서를 가진 시스템에서 결과를 만들어 낸다.

단일 프로세서에서 실행되는 RT 코드 예제

Listing 3은 유니프로세서 시스템에서 올바르게 실행되도록 수정된 코드 모습이다. main() 메소드의 로직은 RT 우선 순위 8을 가진 "주" RT 쓰레드로 옮겨갔다. 이 우선 순위는 주 RT 쓰레드가 만든 다른 두 개의 RT 쓰레드의 우선 순위 보다 더 높다. 가장 높은 우선 순위를 지닌다는 것은 주 RT 쓰레드가 두 개의 RT 쓰레드를 성공적으로 생성할 수 있고, 5초 수면에서 깨어났을 때 주 RT 쓰레드가 현재 실행되는 쓰레드를 선점할 수 있다는 것을 의미한다.


Listing 3. 수정된 RT 쓰레드 예제

                
import javax.realtime.*;
class myRealtimeThreadClass extends javax.realtime.RealtimeThread {
   volatile static boolean Stop = false;

   static class myRealtimeStartup extends javax.realtime.RealtimeThread {

   public void run() {
      // Create and start 2 threads
      myRealtimeThreadClass thread1 = new myRealtimeThreadClass();
      // want 1st thread at 4th real-time priority
      thread1.setPriority(PriorityScheduler.getMinPriority(null)+ 4);
      myRealtimeThreadClass thread2 = new myRealtimeThreadClass();
      // want 1st thread at 6th real-time priority
      thread2.setPriority(PriorityScheduler.getMinPriority(null)+ 6);
      thread1.start();           // start 1st thread to execute run()
      thread2.start();           // start 2nd thread to execute run()

      // Sleep for 5 seconds, then tell the threads to terminate
      try {
                        Thread.sleep(5*1000);
      } catch (InterruptedException e) {
      }
      myRealtimeThreadClass.Stop = true;
      }
   }

   // Primordial thread creates real-time startup thread
   public static void main(String args[]) {
      myRealtimeStartup startThr = new myRealtimeStartup();
      startThr.setPriority(PriorityScheduler.getMinPriority(null)+ 8);
      startThr.start();
   }

   public void run() { // Created threads execute this method
      System.out.println("Created thread");
      int count = 0;
      for (;Stop != true;) {    // continue until asked to stop
         count++;
         // Thread.yield();   // yield to other thread
      }
      System.out.println("Thread terminates. Loop count is " + count);
   }
}

이 프로그램이 유니프로세서에서 실행되면, 다음과 같이 프린트 된다.

Created thread
Thread terminates. Loop count is 32767955
Created thread
Thread terminates. Loop count is 0

이 프로그램의 결과는 모든 쓰레드가 실행된 후 종료하지만, 두 쓰레드 중 단 하나만 for 루프를 반복한다는 것을 나타내고 있다. 이러한 결과는 RT 쓰레드의 우선 순위를 따져보면 이해될 수 있다. 주 RT 쓰레드는 Thread.sleep() 호출에 의해 차단될 때까지 실행된다. 주 RT 쓰레드는 두 개의 RT 쓰레드를 만들지만, RT 우선 순위 6의 두 번째 RT 쓰레드만이 주 RT 쓰레드가 수면하고 있는 동안 실행될 수 있다. 이 쓰레드는 주 RT 쓰레드가 종료되면, 우선 순위 6의 쓰레드는 실행 및 종료될 수 있다. 그리고 0외의 값으로 루프 카운트를 출력한다. 이 쓰레드가 종료된 후에, RT 우선 순위 4의 쓰레드가 실행될 수 있지만, 종료되어야 하는 명령을 받았기 때문에 for 루프를 우회한다. 이 쓰레드는 종료하기 전에 루프 카운트 값, 0을 출력한다.




위로


RT 애플리케이션을 위한 쓰레딩 고려 사항

이 섹션에서는 애플리케이션들을 포팅하여 RT 쓰레드를 사용하거나 RT 쓰레딩을 활용하는 새로운 애플리케이션을 작성할 때 고려해야 할 사항들을 설명한다.

RT 쓰레드의 새로운 확장

RTSJ는 RT 쓰레드를 만들어서 특정 시간 또는 적절한 시간에 시작하도록 하는 장치를 지정하고 있다. 지정된 시간 간격이나 주기로 특정 로직을 실행하는 쓰레드를 만들 수 있다. 로직이 지정된 기간 내에 완료되지 못할 때 AsynchronousEventHandler (AEH)를 실행하는 쓰레드를 정의할 수 있다. 또한, 쓰레드가 소비할 수 있는 메모리 유형과 양에 대한 한계도 정의할 수 있다. 쓰레드가 제한량 보다 더 많은 것을 소비할 때 OutOfMemoryError가 던져지는 방식이다. 이러한 장치들은 RT 쓰레드에서만 사용할 수 있고 일반 자바 쓰레드에서는 사용할 수 없다. RTSJ에는 이러한 장치들에 대한 자세한 정보가 수록되어 있다.

Thread.interrupt()과 보류(pending) 예외

Thread.interrupt() 작동은 RT 쓰레드를 위해 확장된다. 이 API는 JLS에서 기술된 것처럼 차단된 쓰레드를 인터럽트 한다. 이러한 예외는 사용자가 Throws AsynchronouslyInterruptedException문을 메소드 선언에 추가함으로써, 인터럽트 가능한 것으로 명확하게 표시했던 메소드에서도 발생할 수 있다. 이 예외는 사용자가 예외를 명확하게 처리해야 한다는 점에서, 이 쓰레드에는 성가신 존재이다. 그렇지 않으면, 쓰레드에 속한 채로 남아있는데, 이를 보류(pending)라고 한다. 사용자가 예외를 제거하지 않으면, 쓰레드는 골치 아픈 예외가 여전히 걸린 채로 종료될 수 있다. 이러한 에러는 RT 쓰레드 고유의 풀링 폼을 수행하는 애플리케이션이 아닌,"정상적인" 방식으로 종료한다면 비교적 양호한 것이다. 다시 말해서, 쓰레드는 InterruptedException 예외가 있는 상태로 풀로 리턴 될 수 있다. 이 경우, 쓰레드 풀링을 수행하는 코드는 예외를 명확히 제거한다. 그렇지 않으면, 예외는 풀링된 쓰레드가 다시 할당될 때 예외가 생길 것이다.

기본 쓰레드와 애플리케이션 디스패치 로직

기본 쓰레드는 언제나 RT 쓰레드가 아닌 일반 자바 쓰레드이다. 첫 번째 RT 쓰레드는 늘 일반 자바 쓰레드에 의해 생성된다. 이 RT 쓰레드는 RT 쓰레드와 일반 자바 쓰레드를 동시에 실행하는 프로세서가 불충분하다면 일반 자바 쓰레드를 즉각 선점한다. 선점으로 인해 일반 자바 쓰레드는 더 이상의 RT 쓰레드나 기타 로직을 만들 수 없어서 애플리케이션은 초기화된 상태가 된다.

높은 우선 순위 RT 쓰레드에서 애플리케이션 초기화를 수행함으로써 이런 문제를 해결할 수 있다. 이 기술은 고유한 쓰레드 풀링과 쓰레드 디스패치를 수행하는 애플리케이션이나 라이브러리에 필요하다. 쓰레드-디스패치 로직은 높은 우선 순위 또는 높은 우선 순위 쓰레드에서 실행되어야 한다. 쓰레드-풀링 로직을 실행하기에 적합한 우선 순위를 선택하면 쓰레드 인큐잉(enqueueing)과 디큐잉(dequeueing)에서 생기는 문제를 피할 수 있다.

폭주(Runaway) 쓰레드

일반 자바 쓰레드는 time quantum으로 실행되고, CPU 사용에 기반하여 스케줄러가 수행하는 동적 우선 순위 조정은 모든 일반 자바 쓰레드가 모두 실행될 수 있도록 한다. 반대로, RT 쓰레드는 time quantum에 의해 바운드 되지 않고, 쓰레드 스케줄러는 CPU 사용에 기반하여 어떤 형태의 동적 우선 순위 조정도 수행하지 않는다. 일반 자바 쓰레드와 RT 쓰레드간 스케줄링 정책에 있어서의 차이점은 폭주 RT 쓰레드가 발생할 수 있는 기회를 만들어 낸다. 폭주 RT 쓰레드는 시스템을 제어할 수 있고, 다른 애플리케이션들이 실행되지 못하게 하며, 사용자들이 시스템에 로그인 하지 못하게 한다.

개발과 테스팅 동안, 폭주 쓰레드의 영향력을 줄이는데 도움이 되었던 한 가지 기술은 프로세스가 사용할 수 있는 CPU 양에 한계를 설정하는 것이다. 리눅스에서, CPU 소비량을 제한하면 CPU 한계에 다다를 때 폭주 쓰레드는 죽는다. 또한, 시스템 상태를 감시하거나 시스템 로그인을 제공하는 프로그램들은 높은 RT 우선 순위로 실행되어 프로그램이 문제가 많은 쓰레드들을 선점할 수 있도록 한다.

자바 우선 순위와 OS 우선 순위 매핑하기

리눅스에서, POSIX SCHED_FIFO 정책은 1부터 99까지의 정수 범위에서 99의 RT 우선 순위를 제공한다. 이러한 시스템 범위에서, 우선 순위 11부터 89까지는 WebSphere VM 에 의해 사용되고, 이 범위의 일부는 28 RTSJ 우선 순위를 구현하는데 사용된다. 28 RT 자바 우선 순위를 POSIX 시스템 우선 순위로 매핑하는 것은 IBM WebSphere Real Time 문서에 설명되어 있다. 애플리케이션 코드는 이러한 매핑에 의존해서는 안되고, 자바 레벨에서 28 RT 우선 순위의 순서에 의존해야 한다. 이렇게 하면 JVM은 이 범위를 재 매핑하고, 앞으로의 WebSphere Real Time 릴리스에서는 이 부분이 향상될 것이다.

애플리케이션은 WebSphere Real Time에서 사용되었던 것 보다 높거나 낮은 RT 우선 순위를 요할 때 SCHED_FIFO 우선 순위 1 또는 우선 순위 90을 사용하여 데몬이나 기타 RT 프로세스를 구현할 수 있다.

JNI AttachThread()

Java Native Interface (JNI)에서는 JNI AttachThread() API를 사용하여 C 코드에서 생성된 쓰레드를 JVM에 붙일 수 있지만, RTSJ는 RT 쓰레드를 붙이기 위한 JNI 인터페이스를 변경하거나 제공하지 않는다. 따라서, 애플리케이션은 JVM에 붙일 POSIX RT 쓰레드를 C 코드로 만들지 않도록 해야 한다. 대신, 이 같은 RT 쓰레드는 자바 언어에서 생성되어야 한다.

분기된 프로세스와 RT 우선 순위

쓰레드는 또 다른 프로세스를 분기할 수 있다. 리눅스에서, 분기된 프로세스의 기본 쓰레드는 부모 쓰레드의 우선 순위를 상속 받는다. 분기된 프로세스가 JVM이라면, JVM의 기본 쓰레드는 RT 우선 순위로 생성된다. 이는 일반 자바 쓰레드(기본 쓰레드)의 순서를 위반한 것이다. 이러한 상황을 피하려면, JVM은 기본 쓰레드가 비 RT 우선 순위를 갖게끔 한다. (SCHED_OTHER 정책)

Thread.yield()

Thread.yield()는 같은 우선 순위에 있는 쓰레드에만 실행을 양보하고 더 높거나 낮은 우선 순위를 가진 쓰레드에는 절대로 양보하지 않는다. 같은 우선 순위를 가진 쓰레드에 대한 Yielding은 Thread.yield()가 한 개 이상의 RT 우선 순위를 사용하는 RT 애플리케이션에서 사용될 때 문제를 일으킨다는 것을 시사한다. 꼭 필요한 것이 아니라면 Thread.yield()를 사용하지 않는 것이 좋다.

NoHeapRealtimeThreads

javax.realtime.NoHeapRealtimeThread (NHRT)는 javax.realtime.RealtimeThread의 하위 클래스인 RTSJ의 새로운 쓰레드 유형이다. NHRT는 RT 쓰레드와 같은 스케줄링 특성을 갖고 있다. 단, NHRT는 GC에 의해 선점되지 않고, NHRT는 자바 힙을 읽고 쓸 수 없다. NHRT는 RTSJ의 중요한 측면이기 때문에 다음 기술자료에서 자세히 다루도록 하겠다.

AsynchronousEventHandlers

AsynchronousEventHandler (AEH) 역시 새로운 것이고 이벤트가 발생할 때 실행되는 RT 쓰레드이다. 예를 들어, AEH를 설정하여 지정된 시간 또는 알맞은 시간에 실행할 수 있다. AEH 역시 RT 쓰레드와 같은 스케줄링 특성을 갖고 있고 힙과 힙이 아닌 곳에 적용된다.




위로


동기화 개요

많은 자바 애플리케이션들은 자바 쓰레딩 기능을 직접 사용하거나, 개발 중인 애플리케이션들은 한 개 이상의 쓰레드를 포함하고 있는 라이브러리를 사용한다. 멀티쓰레디드 프로그래밍에서 중요한 것은 프로그램을 올바르게 실행하는 것이고, 여러 쓰레드들이 실행되는 시스템에서 쓰레드 보안을 확립하는 것이다. 안전한 프로그램 쓰레드를 만들기 위해서는 잠금 또는 최소한의 머신 연산 같은 동기화 명령들을 사용하여 한 개 이상의 쓰레드들 간 공유되는 데이터로의 액세스를 직렬화 해야 한다. RT 애플리케이션 프로그래머들은 특정 시간 제약 속에서 프로그램을 실행해야 하는 상황에 놓인다. 이를 위해서, 구현 상세, 함축적 의미, 컴포넌트의 성능 애트리뷰트를 알아야 한다.

이제부터는 자바 언어가 제공하는 기본적인 동기화 프리머티브의 측면, 이러한 프리머티브가 RTSJ에서는 어떻게 변화되는지, 이러한 프리머티브를 사용할 때 RT 프로그래머가 알아야 하는 것에 대해 설명하겠다.

자바 동기화 개요

자바는 세 가지 핵심적인 동기화 프리머티브를 제공한다.

  • 동기화 된 메소드와 블록은 쓰레드가 시작 시 객체를 잠그고 종료 시 잠금을 해제하도록 한다.
  • Object.wait()는 객체 잠금을 해제하고 쓰레드는 대기한다.
  • Object.notify()는 객체에 대해 wait()을 수행하는 쓰레드의 블록을 해제한다. notifyAll()은 모든 대기자들의 차단을 해제한다.

wait()notify()를 수행하는 쓰레드는 객체를 잠가야 한다.

잠금 경쟁(Lock contention)은 또 다른 쓰레드에 의해서 이미 잠긴 객체를 잠그려고 할 때 발생한다. 이러한 일이 발생하면, 잠금을 획득하는 것에 실패한 쓰레드는 객체에 대한 잠금 경쟁자들의 논리적 큐에 놓이게 된다. 이와 비슷하게, 여러 쓰레드들은 같은 객체에 대해 Object.wait()을 수행할 수 있기 때문에, 객체 대기자들의 논리적 큐가 있다. JLS는 큐가 관리되는 방식을 지정하지 않지만, RTSJ는 이러한 작동을 요구하고 있다.

우선 순위 기반 동기화 큐

RTSJ는 쓰레드의 모든 큐들이 FIFO 및 쓰레드 기반이다. 우선 순위 기반 FIFO 역시 잠금 경쟁자들 및 잠금 대기자들의 큐에 적용된다. 논리적 관점에서 볼 때, 실행을 기다리는 쓰레드의 실행 큐와 비슷한 잠금 경쟁자들의 FIFO 우선 순위 기반 큐들이 있다. 또한 잠금 대기자들의 비슷한 큐가 있다.

잠금이 해제될 때, 시스템은 가장 높은 우선 순위 큐의 경쟁자들의 앞에서 쓰레드를 선택하여 객체 잠금을 시도한다. notify()가 수행될 때, 가장 높은 우선 순위 큐의 대기자들의 앞에 있는 쓰레드는 블록이 해제된다. 잠금 해제 또는 잠금 notify() 연산은 가장 높은 우선 순위 큐의 힙에 있는 쓰레드가 실행된다는 점에서 스케줄링 디스패치 연산과 비슷하다.

우선 순위 기반 동기화를 지원하기 위해, RT 리눅스를 수정해야 한다. WebSphere Real Time의 VM이 notify() 연산이 수행될 때 어떤 쓰레드의 블록이 해제되어야 하는지를 선택하는 책임을 리눅스에 위임한다.

우선 순위 도치 및 우선 순위 상속

우선 순위 도치(Priority inversion)는 높은 우선 순위 쓰레드가 낮은 우선 순위 쓰레드가 갖고 있는 잠금에 대해 블록 되는 조건이다. 중간 우선 순위 쓰레드는 잠금을 갖고 있고 낮은 우선 순위 쓰레드에 기반하여 실행되는 동안 낮은 우선 순위 쓰레드를 선점한다. 우선 순위 도치는 낮은 우선 순위 쓰레드와 높은 우선 순위 쓰레드의 진행을 지연시킨다. 우선 순위 도치에 기인한 지연 때문에 중대한 데드라인을 맞출 수 다. 이러한 상황은 그림 1의 첫 번째 타임 라인에 나타나 있다.

우선 순위 상속(Priority inheritance)은 우선 순위 도치를 피하는 기술이다. 우선 순위 상속은 RTSJ에서 의무화 하고 있다. 우선 순위 상속의 개념은 잠금 경쟁의 상황에서, 잠금 보유자의 우선 순위가 그 잠금을 획득하고자 하는 쓰레드의 우선 순위로 올라가는 것이다. 잠금 보유자의 우선 순위가 잠금이 해제될 때 기본 우선 순위로 "강등"된다. 이러한 상황에서 낮은 우선 순위 쓰레드는 잠금 경쟁이 발생할 때 높은 우선 순위로 실행되다가 잠금이 해제될 때 끝난다. 잠금 해제 시, 높은 우선 순위 쓰레드는 객체를 잠그고 실행을 계속한다. 중간 우선 순위 쓰레드는 높은 우선 순위 쓰레드를 지연시키지 못하도록 되어있다. 그림 1의 두 번째 타임 라인은 우선 순위 상속이 실행될 때 첫 번째 타임 라인의 잠금 작동이 어떻게 변경되는지를 보여주고 있다.


그림 1. 우선 순위 도치와 우선 순위 상속
우선 순위 도치와 우선 순위 상속

높은 우선 순위 쓰레드가 낮은 우선 순위 쓰레드의 잠금을 획득하려고 할 때, 낮은 우선 순위 쓰레드는 또 다른 쓰레드가 보유한 잠금으로 차단될 수 있다. 이 경우, 낮은 우선 순위 쓰레드와 다른 쓰레드가 올라간다. 우선 순위 상속은 쓰레드 그룹의 우선 순위 상승과 강등을 필요로 할 수 있다.

우선 순위 상속

우선 순위 상속은 POSIX 잠금 서비스를 통해 사용자 공간으로 보내지는 리눅스 커널 기능을 통해 제공된다. 사용자 공간에서의 솔루션은 바람직하지 못하다. 이유는,

  • 리눅스 커널은 선점될 수 있고 우선 순위 도치가 될 가능성이 있다. 우선 순위 상속 역시 특정 시스템 잠금에 필요하다.
  • 사용자 공간에서 솔루션을 시도하면 풀기 어려운 경쟁 조건을 일으킨다.
  • 우선 순위 상승에는 커널 호출이 필요하다.

POSIX 잠금 유형은 pthread_mutex이다. pthread_mutex를 만드는 POSIX API는 mutex가 우선 순위 상속 프로토콜을 구현하도록 한다. pthread_mutex를 잠그고 pthread_mutex의 잠금을 해제하는 POSIX 서비스가 있다. 이것은 우선 순위 상속 지원이 되는 포인트이다. 리눅스는 경쟁 없이 사용자 공간에서 모든 잠금을 수행한다. 잠금 경쟁이 발생하면, 우선 순위 상승과 동기화 큐 관리가 커널 공간에서 수행된다.

WebSphere VM은 POSIX 잠금 API를 사용하여 우선 순위 상속 지원을 위해 자바 동기화 프리머티브를 구현한다. 사용자 레벨 C 코드 역시 POSIX 서비스를 사용한다. 자바 잠금 연산 시, 고유한 pthread_mutex가 할당되고 머신 연산을 사용하여 자바 객체로 바인딩 된다. 자바 레벨의 잠금 해제 연산을 할 때, 잠금 경쟁이 없을 경우, pthread_mutex는 원자 연산을 사용하여 객체로부터 바인딩을 해제한다. 경쟁이 있을 때, POSIX 잠금과 잠금 해제 연산은 리눅스 커널 우선 순위 상속을 지원한다.

mutex 할당과 잠금 시간을 최소화 하기 위해, JVM은 글로벌 잠금 캐시와 쓰레드당 잠금을 관리한다. 이곳에서 각 캐시는 할당되지 않은 pthread_mutex를 포함하고 있다. 쓰레드 캐시의 mutex는 글로벌 잠금 캐시로부터 획득된다. 이것이 쓰레드 잠금 캐시에 놓이기 전에, mutex는 쓰레드에 의해 미리 잠긴다. 경쟁하지 않은 잠금 해제 연산은 잠긴 mutex를 쓰레드 잠금 캐시로 리턴한다. 비경쟁 잠금이 기준이라면, 사전에 잠긴 mutex를 재사용 함으로써 POSIX 레벨 잠금은 줄어들거나 양도될 수 있다.

JVM은 쓰레드 리스트와 글로벌 잠금 캐시 같은 중요한 JVM 리소스로의 액세스를 직렬화 하는데 사용되는 내부 잠금을 갖고 있다. 이러한 잠금들은 우선 순위 상속에 기반하고 있고 단기간 보유된다.




위로


RT 애플리케이션을 위한 동기화 고려 사항

이 섹션에서는 애플리케이션을 포팅하여 RT 쓰레드를 사용하거나 새로운 애플리케이션을 작성하여 RT 쓰레딩을 활용하는 개발자를 위해 일부 RT 동기화 기능을 제공한다.

일반 자바 쓰레드들과 RT 쓰레드간 잠금 경쟁

RT 쓰레드는 일반 자바 쓰레드가 보유한 잠금으로 차단될 수 있다. 이러한 일이 발생하면, 잠금을 보유하고 있는 한 우선 순위 상속이 발생하여 일반 자바 쓰레드의 우선 순위가 RT 쓰레드의 우선 순위로 상승한다. 일반 자바 쓰레드는 RT 쓰레드의 모든 스케줄링 특성을 상속받는다.

  • 일반 자바 쓰레드는 SCHED_FIFO 정책을 실행하기 때문에, 쓰레드는 타임 슬라이스를 수행하지 않는다.
  • 디스패치와 yielding은 상승한 우선 순위의 RT 실행 큐에서 발생하지 않는다.

이러한 작동은 일반 자바 쓰레드가 잠금을 해제할 때 SCHED_OTHER로 복귀한다. Listing 1에서 만들어진 쓰레드들 중 어떤 것이라도 RT 쓰레드에서 요구하는 잠금을 보유하고 있는 동안 실행되었다면, 그 프로그램은 종료되지 않고, RT 쓰레드를 사용한 문제 코드 예제에 설명했던 것과 같은 문제를 나타냈을 것이다. 이러한 상황이 가능하기 때문에, SPIN 루프와 yielding을 수행하는 것은 실시간 JVM에서 실행되는 쓰레드에는 권장하지 않는다.

NHRT와 RT 쓰레드 간 잠금 경쟁

NHRT는 RT 쓰레드(또는 일반 자바 쓰레드)가 보유한 잠금으로 차단될 수 있다. RT 쓰레드가 잠금을 보유하고 있는 동안, GC는 RT를 선점하고 NHRT를 간접적으로 선점할 수 있다. NHRT는 RT가 GC에 의해서 더 이상 선점되지 않을 때까지 기다린 다음 NHRT가 실행할 기회를 갖기 전에 잠금을 해제해야 한다. GC에 의한 NHRT 선점은 NHRT가 시간에 민감한 기능을 수행할 경우 심각한 문제가 된다.

WebSphere Real Time의 결정론적 가비지 컬렉터는 1 밀리초 이하로 중지 시간을 유지하면서, NHRT 선점을 더욱 결정적으로 만든다. 이와 같은 중지 기간을 참을 수 없다면, NHRT와 RT 쓰레드 간 공유되는 잠금을 피하여 이러한 문제를 해결할 수 있다. 잠금이 의무적인 것이라면, RT와 NHRT 지정 리소스와 잠금을 갖는 것도 고려해 볼만하다. 예를 들어, 쓰레드 풀링을 실행하는 애플리케이션은 NHRT와 RT 쓰레드를 위한 개별 풀과 풀 잠금을 고려해 볼 수 있다.

또한, javax.realtime 패키지는 다음을 제공한다.

  • WaitFreeReadQueue 클래스는 RT 쓰레드에서 NHRT로 객체를 전달한다.
  • WaitFreeWriteQueue 클래스는 NHRT에서 RT 쓰레드로 객체를 전달한다.

이러한 클래스들은 비 대기(wait-free) 연산을 수행하는 동안 GC에 의해 차단될 수 있고, 비 대기 연산을 수행할 때 NHRT에서 요구하는 잠금을 보유하지 않는다.

javax.realtime 패키지의 동기화

특정 javax.realtime 메소드는 잠금이 경쟁하지 않을 때 조차 동기화의 오버헤드가 발생하기 때문에 의도적으로 동기화 되지 않는다. 동기화가 필요하다면, 콜러(caller)는 필요한 javax.realtime 메소드를 동기화 된 메소드 또는 블록으로 래핑한다. 프로그래머는 java.realtime 패키지의 메소드가 사용될 때 이와 같은 동기화를 추가하는 것을 고려해야 한다.

핵심 JLS 패키지에서의 동기화

반대로, java.util.Vector 같은 핵심 JLS 서비스들은 이미 동기화 된다. 또한, 특정 JLS 서비스들은 내부 잠금을 수행하여 특정 공유 리소스들을 직렬화 한다. 이러한 동기화 때문에, 핵심 JLS 서비스를 사용할 때, GC에 의한 NHRT 선점의 문제를 피해야 한다. (NHRT와 RT 쓰레드간 잠금 경쟁).

비경쟁 잠금 성능

비 RT 애플리케이션의 벤치마킹과 계측을 통해 잠금이 경쟁하지 않는다는 것을 알 수 있다. 비경쟁 잠금은 RT 애플리케이션의 뛰어난 부분이고, 특히 기존 컴포넌트나 라이브러리들이 재사용 될 때 그렇다. 비경쟁으로 알려진 잠금에 대한 결정적인 비용을 갖는 것이 좋지만, 동기화 지시문들은 피하거나 제거하기 어렵다.

앞서 설명했던 것처럼, 비경쟁 잠금 연산에는 설정과 원자 머신 명령이 포함된다. 비잠금 연산에는 원자 머신 연산이 포함된다. 잠금 연산용 설정에는 미리 잠긴 mutex의 할당이 포함된다. 이 할당은 비 경쟁 잠금 연산에서 가장 많은 변수 비용을 차지한다. RealtimeSystem.setMaximumConcurrentLocks()는 이러한 변수 비용을 제어할 수 있다.

RealtimeSystem.setMaximumConcurrentLocks(int numLocks)는 WebSphere Real Time의 VM이 numLocks mutex를 글로벌 잠금 캐시로 사전 할당하도록 한다. 글로벌 잠금 캐시는 쓰레드 당 잠금 캐시를 쓰레드 당 잠금 캐시들을 제공한다. RealTimeSystem API를 제공함으로써, 시간에 민감한 코드 영역 내에서 잠금이 발생할 기회를 줄일 수 있다. RealTimeSystem.getMaximumConcurrentLocks()setMaximumConcurentLocks() 호출에 어떤 숫자가 사용되어야 하는지를 결정하는데 사용되지만, getMaximumConcurrentLocks()는 최고 수위가 아닌 호출 시점에 잠금을 제공한다. 미래의 RTSJ 버전은 고수위 마크를 제공하는 API를 선보일 예정이다. numLocks 값에 터무니 없이 큰 값을 제공하지 않도록 하라. setMaximimConcurrentLocks()에 대한 호출은 많은 잠금을 만드는데 불규칙하고 정해지지 않은 시간과 메모리를 소비한다. 이러한 API는 JVM 스팩이 되도록 정의되기 때문에, 다른 JVM은 호출을 무시하거나 다른 작동을 제공한다.

경쟁 잠금 성능

쓰레드는 한 개 이상의 잠금을 동시에 가질 수 있고, 이러한 잠금은 특정 순서대로 획득된다. 이러한 잠금 패턴은 잠금 계층(lock hierarchy)을 형성한다. 우선 순위 상속은 쓰레드 그룹의 상승과 강등을 의미할 수 있다. 이 그룹의 쓰레드 수는 시스템에서 가능한 최대한의 잠금 계층보다 커서는 안된다. 잠금 계층을 좁게 유지함으로써, 가능한 적은 객체들을 잠글 수 있고, 이는 우선 순위 조정이 필요한 최대 쓰레드의 수에 영향을 미친다.

동기화 연산 시간

Object.wait(long timeout, int nanos)는 대기 연산에 나노초의 세분성을 제공한다. HighResolutionTime.waitForObject() API는 Object.wait()과 비슷하고 나노초 세분성으로 지정될 수 있는 상대 시간과 절대 시간을 제공한다. WebSphere Real Time에서, 두 API들은 기본적인 POSIX 잠금 대기 서비스들로 구현된다. 이러한 서비스들은 최대한 마이크로초 세분성을 제공한다. 이식성이 필요할 경우 javax.realtime 패키지의 Clock 클래스의 getResolution() 메소드는 실행 플랫폼을 위한 해상도를 검색하는데 사용된다.




위로


요약

RTSJ는 javax.realtime 패키지의 새로운 RT 클래스와 API를 통해 쓰레딩 및 동기화 기능을 확장 및 강화한다. WebSphere Real Time에서, 이러한 기능들은 RT 버전의 리눅스 커널, POSIX 쓰레딩 수정, JVM 수정을 통해 구현된다. RTSJ 쓰레딩과 동기화를 이해하면 RT 애플리케이션을 작성 및 전개할 때의 문제를 피하는데 도움이 된다.



 

참고자료

교육


제품 및 기술 얻기


토론



 

필자소개

Patrick Gallop은 1984년 워털루대학교(University of Waterloo)를 졸업했다. 졸업 직후 IBM Toronto Lab에 입사했으며, 다양한 컴파일러와 컴파일러 툴 프로젝트에 참여했다. IBM Java 프로젝트에서 일했으며, 최근에는 IBM Real-time Java 프로젝트의 리더로 활동했다.


Mark Stoodley

Mark Stoodley는 2001년 토론토대학교(University of Toronto)에서 컴퓨터 엔지니어링 박사 학위를 받았고, 2002년에 IBM Toronto Lab에 합류하여 Java JIT 컴파일 기술 분야에서 일했다. 2005년 초반부터 IBM WebSphere Real Time용 JIT 기술에 대해 작업했고, 기존 JIT 컴파일러를 실시간 환경에 적용하는 것과 관련한 작업을 했다. 현재 자바 컴파일 컨트롤 팀의 리더이며, 실행 환경의 코드 컴파일의 효과를 증진하기 위해 노력하고 있다.

+ Recent posts