주제

소개 페이지 맨 위

우수한 설계 기술은 요구사항 세트를 만족시킬 수 있는 "최선"의 방법을 선택하는 기술입니다. 우수한 동시성 시스템 설계 기술은 동시성에 대한 요구사항을 만족시킬 수 있는 가장 간단한 방법을 선택하는 기술입니다. 설계자가 지켜야 할 첫 번째 규칙은 방향을 바꾸어서는 안 된다는 것입니다. 우수한 설계 패턴 및 설계 이디엄은 대부분의 문제점을 해결하도록 개발되었습니다. 복잡한 동시성 시스템의 경우, 잘 입증된 솔루션을 사용하고 설계를 단순화하려고 노력하는 것이 중요합니다.

동시성 접근 방법 페이지 맨 위

전적으로 컴퓨터 내에서 발생하는 병행 활동을 실행 스레드라고 합니다. 모든 동시성 활동와 마찬가지로 실행 스레드는 적시에 발생하기 때문에 추상적 개념입니다. 실제로 실행 스레드를 캡처하기 위해 할 수 있는 일은 적시에 특정 인스턴스에 해당 상태를 표시하는 것입니다.

컴퓨터를 사용하여 병행 활동을 표현하는 가장 직접적인 방법은 각 활동에 별도의 전용 컴퓨터를 배정하는 것입니다. 그러나 이는 비용이 많이 들어 항상 충돌 해결에 도움이 되는 것은 아닙니다. 그러므로 몇몇 멀티태스킹 양식을 통해 동일한 실제 프로세서에서 복수의 활동을 지원하는 것이 일반적입니다. 이 경우, 프로세서와 메모리 및 버스와 같은 해당 연관 자원을 공유합니다. (불행히도 이렇게 자원 공유하면 원래 문제점에는 없었던 새로운 충돌이 발생합니다.)

멀티태스킹의 가장 일반적인 양식은 "가상" 프로세서를 사용하여 각 활동을 재공하는 것입니다. 이 가상 프로세서를 일반적으로 프로세스 또는 타스크라고 합니다. 일반적으로 각 프로세스는 논리적으로 다른 가상 프로세서의 주소 공간과 별도의 주소 공간을 소유합니다. 이는 실수로 각 상대편의 메모리에 겹쳐쓰는 것에 대해 서로 충돌하는 프로세스에게 이의를 제기합니다. 불행히도 한 프로세스에서 다른 프로세서로 실제 프로세서를 전환하는 데 필요한 오버헤드는 종종 금지됩니다. 최신의 초고속 프로세서가 장착되어 마이크로초의 1/100 정도가 걸릴 수 있는 CPU 내에서의 레지스터 세트의 심각한 스와핑이 관련되어 있습니다(컨텍스트 전환).

이 오버헤드를 줄이려면 여러 작동 시스템이 단일 프로세스 내에 복수의 경량 스레드를 포함시킬 수 있는 기능을 제공합니다. 프로세스 내의 스레드는 해당 프로세스의 주소 공간을 공유합니다. 이는 컨텍스트 전환에 관련된 오버헤드는 줄어드나 메모리 충돌 가능성은 증가합니다.

일부 처리량이 많은 어플리케이션의 경우, 경량 스레드 전환의 오버헤드는 수용할 수 없을 정도로 높을 수 있습니다. 이러한 상황에서는 어플리케이션의 일부 특수 기능의 장점을 취하여 달성되는 보다 경량의 멀티태스킹 양식을 갖는 것이 일반적입니다.

시스템의 동시성 요구사항은 시스템의 구조에 극단적인 충격을 줄 수 있습니다. 단일 프로세스 구조에서 멀티 프로세스 구조로 기능성을 이동하도록 결정하면 시스템의 구조가 여러 차원에서 많이 변경됩니다. 사실상 시스템의 구조를 변경할 수 있는 추가 메커니즘(예: 원격 프로시저 호출)을 도입해야 할 수 있습니다.

추가 프로세스 및 스레드를 관리하기 위한 추가 오버헤드와 함께 시스템 사용 가능성 요구사항을 고려해야 합니다.

대부분의 구조적 결정에서와 같이 프로세스 구조를 변경하면 하나의 문제점 세트를 다른 것에도 효율적으로 이용할 수 있습니다.

접근 방법

장점

단점

단일 프로세스, 스레드 없음
  • 단순성
  • 신속한 내부 프로세스 메시징
  • 작업의 균형을 이루기 어려움
  • 다중 프로세스로 확장할 수 없음
단일 프로세스, 멀티 스레드됨
  • 신속한 내부 프로세스 메시지
  • 내부 프로세스 통신 없이 멀티태스킹
  • '과중량' 프로세스에 대한 과부하가 없는 향상된 멀티태스킹
  • 어플리케이션이 'thread-safe'이어야 함
  • 운영 체제가 충분한 thread-management를 갖고 있어야 함
  • 공유 메모리 실행을 고려해야 함
멀티 프로세스
  • 프로세서를 추가하면 확장이 쉬움
  • 노드를 통한 분배가 상대적으로 쉬움
  • 프로세스 경계에 민감: 내부 프로세스를 사용하면 성능이 많이 저하됨
  • 스와핑 및 컨텍스트 전환 비용이 비쌈
  • 설계하기가 어려움

일반적인 전개 경로는 단일 프로세스 구조에서 시작하는 것이며 동시에 발생해야 하는 작동 그룹에 대한 프로세스를 추가합니다. 이러한 광범위한 그룹 내에서 동시성을 증가시키기 위해 프로세스 내에 스레드를 추가하는 동시성에 대한 추가 요구사항을 고려하십시오.

초기 시작 지점은 특정 목적을 가지고 빌드된 활성 객체 스케줄러를 사용하여 단일 운영 체제 타스크 또는 스레드에 여러 활성 객체를 지정하는 것입니다. 이 방법은 일반적으로 아주 경량의 동시성 시뮬레이션을 달성할 수 있으나 단일 운영 체제 타스크 또는 스레드가 있다고 해도 다중 CPU가 있는 시스템은 이용할 수 없습니다.  핵심 결정사항은 별도의 스레드로 블로킹 작동을 분리하는 것입니다. 그래서 해당 블로킹 작동은 병목현상을 일으키지 않습니다. 이는 블로킹 작동을 사용하여 자신의 운영 체제 스레드로 활동 객체를 분리하게 됩니다.

실행 페이지 맨 위

불행히도 여러 구조적 결정과 마찬가지로 쉬운 해답은 없습니다. 올바른 솔루션에는 제대로 균형잡힌 접근 방법이 포함됩니다. 작은 구조적 프로토타입은 특정 선택사항 세트가 포함되었는지 여부를 탐색하는 데 사용할 수 있습니다. 프로세스 구조를 프로토타입화할 때 시스템에 대해 이론적으로 가능한 최대치까지 프로세스 수를 늘리는 데 중점을 두십시오. 다음 문제점을 고려하십시오.

  • 프로세스 수를 최대치까지 늘릴 수 있습니까? 시스템을 얼마나 확장할 수 있습니까? 잠재적 증가에 대한 허용치가 있습니까?
  • 프로세스를 변경할 경우, 공유 프로세스 주소 공간에서 작동하는 경량 스레드에 어떤 영향을 미칩니까?
  • 프로세스 수가 추가됨에 따라 응답 시간은 어떻게 변경됩니까? IPC(Inter-Process Communication)가 증가합니까? 현저히 저하됩니까?
  • 프로세스를 결합하거나 재구성하여 IPC의 양을 줄일 수 있습니까? 이렇게 변경을 하면 로드 밸런스를 유지하기 어려운 하나의 큰 프로세스가 됩니까?
  • IPC를 줄이기 위해 공유 메모리를 사용할 수 있습니까?
  • 시간 자원을 할당하면 모든 프로세스가 "동일한 시간"을 갖게 됩니까? 시간 할당을 수행할 수 있습니까? 스케줄 우선순위 변경에 대한 잠재적 결점이 있습니까?

내부 객체 통신 페이지 맨 위

활성 객체는 동기식 또는 비동기식으로 서로 통신할 수 있습니다. 동기식 통신은 순서를 엄격히 제어하여 복잡한 협력을 단순화할 수 있기 때문에 유용합니다. 즉, 활성 객체가 다른 활성 객체의 동기 호출을 포함하는 run-to-completion 단계를 실행하는 중에, 다른 객체에 의해 시작된 모든 동시 상호 작용은 전체 순서를 완료할 때까지 무시될 수 있습니다.

이는 어떤 경우에는 유용하지만, 더 중요한 높은 우선순위를 가진 이벤트가 기다려야 할 수 있으므로 (우선순위 반전) 문제점이 발생할 수도 있습니다. 이는 동기식으로 호출된 객체 자체가 동기 호출 자체에 대한 응답을 기다리다가 블록되는 가능성으로 인해 악화됩니다. 이로 인해 바인드되지 않은 우선순위 반전이 유발될 수 있습니다. 가장 극단적인 경우, 비동기 호출 체인에 순환성이 있을 경우 교착 상태를 유발할 수 있습니다.

비동기 호출은 바인드된 응답 시간을 가능하게 하여 이 문제점을 피합니다. 그러나 소프트웨어 구조에 따라 활성 오브젝트는 언제든지 여러 비동기 객체 (각 이벤트는 다른 활성 객체와 복잡한 순서를 가진 비동기 상호 작용을 필요로 할 수 있음)에 응답해야 할 수 있기 때문에 비동기 통신은 종종 복잡한 코딩이 요구합니다. 이는 구현하기가 매우 어렵고 오류도 발생할 가능성도 많습니다.  

메시지 전달이 보증된 비동기 메시징 기법을 사용하며 어플리케이션 프로그래밍 타스크를 단순화할 수 있습니다. 네트워크 연결 또는 원격 어플리케이션이 사용 불가능해도 어플리케이션은 조작을 계속할 수 있습니다. 비동기 메시징은 동기 모드에서도 이를 사용할 수 있게 합니다. 동기 기법을 사용하려면 어플리케이션이 사용 가능할 때마다 연결이 사용 가능해야 합니다. 연결이 존재한다고 알려져 있기 때문에 확약 처리의 핸들링이 훨씬 쉽습니다.

어용론 페이지 맨 위

활동 객체의 컨텍스트 전환 오버헤드가 매우 낮더라도 일부 어플리케이션은 바용을 수용하기가 어렵다는 사실을 알게 됩니다. 이는 일반적으로 고비율로 대량의 데이터를 처리해야 할 경우에 발생합니다. 이런 경우, 다시 세마포어와 같은 보다 전통적이나 위험은 높은 동시성 관리 기법 및 수동적인 객체를 사용해야 할 수 있습니다.

그러나 이러한 고려사항은 활성 객체 접근 방법도 함께 포기해야 한다는 것을 의미하는 것은 아닙니다. 이러한 데이터 중심 어플리케이션에서 종종 성능에 민감한 파트가 상대적으로 전체 시스템의 적은 부분만을 차지합니다. 이는 나머지 시스템은 활성 객체 패러다임을 이용할 수 있다는 것을 의미합니다.

일반적으로, 성능은 시스템 설계를 완성하는 설계 기준 중 하나일 뿐입니다. 시스템이 복잡할 경우, 유지보수성, 변경용이성, 이해용이성 등과 같은 기준은 훨씬 더 중요하지 않다면 동일합니다. 활성 객체 접근 방법은 낮은 레벨의 기술 특정 메커니즘과 반대로 설계를 어플리케이션 특정 용어로 표현할 수 있게 하과 동시에 대부분의 동시성 및 동시성 관리의 복잡도를 숨기기 때문에 명확한 장점을 지닙니다.

발견적 해결 방법 페이지 맨 위

현재 컴포넌트 간의 상호 작용에 중점 두기 페이지 맨 위

상호 작용이 없는 동시 컴포넌트는 거의 사소한 문제점입니다. 설계 목표의 거의 모두가 병행 활동 간의 상호 작용을 사용하여 수행해야 하므로 먼저 상호 작용을 이해하는 데 노력을 기울여야 합니다. 다음과 같은 몇 가지 질문을 해야 합니다.

  • 상호 작용이 단방향, 양방향 또는 여러 방향입니까?
  • 클라이언트 - 서버 또는 마스터 슬레이브 관계가 있습니까?
  • 일부 동기화 양식이 필요합니까?

상호 작용을 이해했으면 이를 구현할 방법을 생각할 수 있습니다. 시스템의 성능 목표와 일치하는 가장 간단한 설계를 생성하도록 구현을 선택해야 합니다. 성능 요구사항은 일반적으로 외부적으로 생성된 이벤트에 대한 응답으로 전반적인 처리량과 수용 가능한 대기 시간(latency) 모두를 포함합니다.

외부 인터페이스 분리 및 캡슐화 페이지 맨 위

어플리케이션 전체에 걸쳐 외부 인터페이스에 대해 특정 가정을 임베드하는 것은 좋지 않은 사례이며 이벤트를 기다리다가 여러 제어 스레드가 블록되는 것은 매우 비효율적입니다. 대신 이벤트를 발견하는 전용 타스크인 단일 객체를 지정하십시오. 이벤트가 발생하면 해당 객체는 이벤트에 대해 알아야 하는 모든 사람에게 이를 알릴 수 있습니다. 이 설계는 잘 알려져 있고 입증된 설계 패턴인 "관찰자" 패턴[GAM94]을 기본으로 합니다. 좀더 많은 유연성을 제공하기 위해 관찰자 패턴을 쉽게 "공개자 - 서브스크라이버 패턴"으로 확장할 수 있습니다. 이벤트 발견자와 이벤트에 관심이 있는 객체("서브스크라이버") 사이에서 매개체 역할을 합니다[BUS96].

블로킹 및 폴링 작동 분리 및 캡슐화 페이지 맨 위

시스템 내의 조치는 외부적으로 생성된 이벤트 어커런스에 의해 트리거될 수 있습니다. 한 가지 중요한 외부 생성 이벤트는 단순히 시각으로 표시되는 시간의 경과일 수 있습니다. 기타 외부 이벤트는 사용자 인터페이스 장치, 프로세스 센서 및 기타 시스템에 대한 통신 링크를 포함하여 외부 하드웨어에 연결된 입력 장치로부터 들어옵니다.

소프트웨어가 이벤트를 발견하려면 인터럽트를 기다리다가 블록되거나 정기적으로 하드웨어를 점검하여 이벤트가 발생했는지 확인해야 합니다. 후자의 경우, 정기적 주기는 수명이 짧은 이벤트 또는 다중 어커런스의 누락을 방지하거나 간단히 이벤트의 어커런스 및 발견 사이의 대기 시간(latency)을 최소화하기에는 시간이 부족할 수 있습니다.

여기서 흥미로운 점은 이벤트가 아무리 드물게 발생한다 하더라도 일부 소프트웨어는 이벤트를 기다리다가 블록되거나 자주 이를 점검해야 한다는 것입니다. 그러나 시스템이 다루어야 하는 많은 이벤트(대부분이 아님)가 자주 발생하지 않습니다. 대부분 제공된 시스템에서 중요한 이벤트가 하나도 발생하지 않습니다.

승강기 시스템이 이러한 좋은 예제를 많이 제공합니다. 승강기 수명에서 중요한 이벤트에는 서비스 호출, 탑승자의 층 선택, 탑승자가 손으로 문을 막는 것, 한 층에서 다른 층으로의 이동이 포함됩니다. 이러한 이벤트 중 일부는 시간을 다투는 응답이 필요하나 모두 원하는 응답 시간의 시간 스케일에 비하면 극히 드뭅니다.

단일 이벤트는 여러 조치를 트리거할 수 있으며 조치는 여러 객체의 상태에 따라 다릅니다. 더우기 시스템의 다른 구성이 동일한 이벤트를 다르게 사용할 수 있습니다. 예를 들어, 승강기가 한 층을 통과할 때 승강기 캡의 표시장치는 갱신되어야 하며 승강기 자체는 자신이 몇 층에 있는지를 알아야만 새로운 호출 및 탑승자의 층 선택에 응답할 방법을 알 수 있습니다. 각 층마다 승강기 위치의 표시장치가 있을 수도 있고 없을 수도 있습니다.

폴링 작동보다 재활성화 작동 선호 페이지 맨 위

폴링은 비용이 많이 듭니다. 이벤트가 발생했는지를 점검하려면 시스템의 일부 파트가 정기적으로 수행하고 있던 작업을 중지해야 합니다. 이벤트가 신속하게 응답해야 할 경우, 시스템은 이벤트가 도착했는지를 더 자주 점검하고 수행될 수 있는 다른 작업의 양을 더 제한합니다.

인터럽트에 의해 활성화되는 이벤트 종속 코드를 사용하여 이벤트에 인터럽트를 할당하는 것이 훨씬 더 효율적입니다. "비용이 많이 든다고" 생각하여 종종 인터럽트를 기피하기는 하지만 적절히 인터럽트를 사용하는 것이 폴링을 반복하는 것보다 효율적일 수 있습니다.

이벤트 공고 메커니즘으로 인터럽트를 선호하는 케이스는 이벤트가 무작위로 빈번하지 않게 도착하여 대부분의 폴링 노력이 이벤트가 발생하지 않았음을 발견하는 경우입니다. 폴링을 선호하는 케이스는 이벤트가 정기적으로 도착하고 특정 방법 및 대부분의 폴링 노력이 이벤트가 발생했다는 것을 찾는 경우입니다. 중간에 폴링 또는 재활성화 작동과 차별화되는 지점이 있습니다. 폴링 및 재활성화 작동 모두 똑같이 잘 작동되며 어느 것을 선택해도 문제가 되지 않습니다. 그러나 대부분의 경우 현실에서는 이벤트는 무작위로 발생하므로 재활성화 작동이 선호됩니다.

데이터 브로드캐스트보다 이벤트 공고 선호 페이지 맨 위

데이터 브로드캐스트(일반적으로 신호를 사용함)는 비용이 많이 들고 일반적으로 자원 낭비가 많습니다. 몇몇 객체만이 데이터에 관심이 있어도 이를 검토하려면 모든 객체(또는 많은 객체)를 중지해야 합니다. 기능은 더 좋고 자원은 덜 소비하는 접근 방법은 공고를 사용하여 어떤 이벤트가 발생했는지에 관심이 있는 객체에게만 알려주는 것입니다. 여러 객체가 관심을 가져야 하는 이벤트 (일반적으로 트리밍 또는 동기화 이벤트)로 브로드캐스트를 제한하십시오.

경량 메커니즘은 많이 사용하고 중량 메커니즘은 적게 사용함 페이지 맨 위

보다 자세히 설명하면 다음과 같습니다.

  • 동시성은 문제가 되지 않으나 즉각적인 응답은 문제가 될 경우에는 수동 객체 및 동기 메소드 호출을 사용하십시오.
  • 대부분의 어플리케이션 레벨의 동시성 개념에 대해서는 활성 객체 및 비동기 메시지를 사용하십시오.
  • OS 스레드를 사용하여 블로킹 요소를 분리하십시오. 활성 객체를 OS 스레드로 맵핑할 수 있습니다.
  • 최대한 분리하려며 OS 프로세스를 사용하십시오. 독립적으로 시작 및 종료해야 하는 프로그램과 분산되어야 하는 서브시스템의 경우, 별도의 프로세스가 필요합니다.
  • 실제 분배 또는 가공하지 않은 마력에는 별도의 CPU를 사용하십시오.

효율적인 동시 어플리케이션 개발에 가장 중요한 가이드라인은 가장 경량인 동시성 메커니즘의 사용을 최대화하는 것입니다. 하드웨어 및 운영 체제 소프트웨어 모두 동시성을 지원하는 데 중요한 역할을 하나 둘 다 상대적으로 중량의 메커니즘을 제공하여 어플리케이션 설계자에게 많은 작업을 남깁니다. 사용 가능한 툴 및 동시 어플리케이션 요구사항 사이에 놓인 간격을 메꾸어야 합니다.

활동 객체는 두 가지 핵심 기능을 사용하여 이 간격을 메꿉니다.

  • 이들은 OS 또는 CPU에서 제공하는 기본 메커니즘 중 하나를 사용하여 구현할 수 있는 기본 동시성 단위(제어 스레드)를 캡슐화하여 설계 추상을 단일화합니다.
  • 활동 오브젝트가 단일 OS 스레드를 공유하면 어플리케이션에서 직접 구현할 수 있는 대단히 효율성이 좋고 경량인 동시성 메커니즘이 됩니다. 그렇지 않으면 어플리케이션에서 직접 구현해야 합니다.

활성 객체는 프로그래밍 언어로 제공되는 수동 객체에 대해 이상적인 환경도 작성합니다. 프로그램 및 프로세스와 같은 절차적 결과물 없이 전적으로 동시 객체를 기반으로 시스템을 설계하면 좀더 모듈화되고 응집력이 있으며 이해하기 쉬운 설계가 생성됩니다.

성능 편중 방지 페이지 맨 위

대부분의 시스템에서는 코드의 10% 미만이 CPU 주기의 90% 이상을 사용합니다.

많은 시스템 설계자는 코딩된 모든 행이 최적화되어 있는 것처럼 행동합니다. 대신 가장 자주 실행되거나 가장 오래 실행되는 코드의 10%를 최적화하는 데 시간을 할애하십시오. 이해성, 유지보수성, 모듈 방식, 구현 용이성에 중점을 두면서 다른 90%를 설계하십시오.

메커니즘 선택 페이지 맨 위

비기능적 요구사항 및 시스템 구조는 원격 프로시저 호출을 구현하는 데 사용되는 메커니즘을 선택하는 데 영향을 미칩니다. 대안 간의 트레이드오프 유형에 대한 개요가 아래에 나와 있습니다.  

메커니즘 사용법 설명
메시징 엔터프라이즈 서버에 비동기 액세스 메시징 어플리케이션은 대기열, 시간 종료 및 복구/재시작 조건을 처리하여 어플리케이션 프로그래밍 타스크를 단순화할 수 있습니다. 슈도 비동기 모드에서 메시징 미들웨어를 사용할 수 있습니다. 일반적으로 메시징 기법은 길이가 긴 메시지를 지원할 수 있습니다. 일부 RPC 접근 방법은 메시지 크기를 제한하며 큰 메시지를 처리하려면 추가 프로그래밍이 필요합니다.
JDBC/ODBC 데이터베이스 호출 이는 동일한 서버 또는 다른 서버에 있을 수 있는 데이터베이스에 대한 호출을 작성하는 Java 서블릿 또는 어플리케이션 프로그램의 데이터베이스 독립 인터페이스입니다.
고유 인터페이스 데이터베이스 호출 대부분의 데이터베이스 벤더는 어플리케이션 이식성을 희생하면서 ODBC를 통해 성능상의 장점을 제공하는 자사의 데이터베이스에 대한 고유의 API(Application Program Interface)를 갖고 있습니다.
원격 프로시저 호출 원격 서버에서 프로그램을 호출합니다. 이를 처리하는 어플리케이션 빌더를 갖고 있을 경우 RPC 레벨에서 프로그램을 하면 안 됩니다.
상호 작용식 e-business 어플리케이션에서 거의 사용되지 않음 일반적으로 APPC 또는 소켓과 같은 프로토콜을 사용하는 하위 레벨의 프로그램간 통신입니다.

요약 페이지 맨 위

대부분의 시스템에 동시 작동 및 분배 컴포넌트가 필요합니다. 대부분의 프로그래밍 언어는 이러한 문제점을 해결하는 데 큰 도움을 주지 못합니다. 어플리케이션에서의 동시성에 대한 요구사항 및 소프트웨어에서 이를 구현할 때 제공되는 선택사항을 이해하려면 요약을 잘 해야 함을 알게 되었습니다. 그러나 동시 소프트웨어는 본래부터 비동기 소프트웨어보다 더 복잡하지만 현실 세계에서 동시성을 처리해야 하는 시스템 설계를 상당히 단순화할 수 있습니다.



Rational Unified Process   2003.06.15