개념:
|
참고: 모든 시스템에 적용될 수 있으므로 일반적으로 여기서 동시성에 대해 설명합니다. 그러나 동시성은 외부 이벤트에 실시간으로 반응해야 하며 종종 최종 기한을 맞추기 힘든 시스템에서 특히 중요합니다. 이 시스템 클래스의 특정 요구를 처리하기 위해 RUP에 실시간(반응) 시스템 확장자가 있습니다. |
동시성은 시스템에서 동시에 발생하는 경향을 말합니다. 동시성은 물론 자연스러운 현상입니다. 실제 상황에서는 다양한 일들이 주어진 시간에 동시에 발생합니다. 소프트웨어가 실제 시스템을 모니터하고 제어하도록 설계할 때 이러한 자연스러운 동시성을 다루어야 합니다.
소프트웨어 시스템에서의 동시성 문제를 다루는 경우 일반적으로 중요한 면은 임의의 순서대로 발생하는 외부 이벤트를 발견하고 이에 응답할 수 있다는 점과 해당 이벤트가 최소 필수 간격으로 응답한다는 점, 두 가지입니다.
각 병행 활동이 독립적으로 전개된 경우, 실제 병렬 방식에서 간단히 별도의 프로그램을 작성하여 각 활동을 처리할 수 있어 비교적 간단합니다. 병행 시스템 설계에 대한 요구는 대부분 병행 활동 간에 발생하는 상호 작용 때문에 발생합니다. 병행 활동이 상호 작용하는 데는 일부 조정이 필요합니다.
그림 1: 작업시 동시성의 예: 상호 작용하지 않는 병렬 활동에는 단순한 동시성 문제가 있습니다. 병렬 활동이 상호 작용을 하거나 같은 자원을 공유하는 경우 동시성 문제는 중요해 집니다.
차량 통행을 통해 유용한 유추를 할 수 있습니다. 상호 작용이 거의 없는 서로 다른 도로에서 병렬 통행 흐름은 몇 가지 문제점을 발생합니다. 인접한 도로의 병렬 흐름에는 안전한 상호 작용을 위해 조정이 필요하며 훨씬 더 심각한 상호 작용 유형이 발생하는 교차로에서는 주의 깊게 조정해야 합니다.
동시성을 강제 실행하도록 작동시키는 것 중에 일부는 외부적인 것입니다. 즉, 환경의 요구에 의해 강요되는 것입니다. 실제 시스템에서 많은 일들이 동시에 발생하며 소프트웨어에서 "실시간으로"라고 표시되어야 합니다. 따라서 수 많은 실시간 소프트웨어 시스템은 "반응적"이어야 합니다. 임의의 시간에, 임의의 순서로 또는 두 가지 모두의 상황에서 발생할 수 있는 외부에서 생성된 이벤트에 응답해야 합니다.
일반적인 프로시저 프로그램을 설계하여 이러한 상황을 처리하는 것은 매우 복잡합니다. 시스템을 병행 소프트웨어 요소로 나누어 이벤트 각각을 처리하는 것이 더 간단할 수 있습니다. 여기서 중요한 점은 "할 수 있다"는 것으로 이벤트 간 상호 작용 정도가 복잡도에도 영향을 주기 때문입니다.
또한 동시성에 대한 내부적 이유가 있을 수 있습니다[LEA97]. 다중 CPU를 사용할 수 있는 경우 타스크를 병렬로 수행하면 시스템의 계산 작업이 상당히 빨라질 수 있습니다. 단일 프로세서에서도 멀티태스킹은 I/O 대기 중 하나의 활동으로 인해 다른 활동을 블로킹하지 못할 경우 속도를 향상시킬 수 있습니다(예: 시스템이 시작하는 동안 발생되는 일반적인 상황). 종종 각각의 컴포넌트에 조작을 준비할 시간이 필요한 경우가 많습니다. 이러한 조작을 수행하면 속도가 상당히 느려질 수 있습니다.
시스템의 제어 능력 또한 동시성으로 강화될 수 있습니다. 예를 들어, 병행 컴포넌트없이 수행하기 매우 어려운 기타 병행 기능으로 하나의 기능을 시작, 중지할 수 있으며 그렇지 않으면 중간 스트림에서 영향을 받을 수 있습니다.
이러한 장점에도 불구하고 동시 프로그래밍을 항상 사용하지 않는 이유
대부분의 컴퓨터 및 프로그래밍 언어는 본래 순차적입니다. 프로시저 또는 프로세서는 한 번에 하나의 명령어를 실행합니다. 단일 순차 프로세서에서 동시성 착각은 다른 타스크의 실행을 끼워넣어서 작성해야 합니다. 이를 수행하는 기술은 그리 어렵지 않으나 서로 상호 작용하는 프로그램 세그먼트를 끼워넣는 시기와 방법을 결정하는 것이 어렵습니다.
다중 프로세서에서 동시성을 달성하는 것은 쉽지만 상호 작용은 더 복잡해집니다. 먼저, 다른 프로세서에서 실행 중인 타스크 간의 의사소통에 대한 문제가 있습니다. 일반적으로 복잡도를 증가시키고 시간 부하를 늘리는 여러 소프트웨어 층이 포함되어 있습니다. 시계 및 타이밍이 다르고 컴포넌트가 독립적으로 실패할 수 있으므로 결정성은 다중 CPU 시스템에서 감소합니다.
마지막으로 병행 시스템은 명확한 글로벌 시스템 상태가 부족하여 이해하기 더 어려울 수 있습니다. 병행 시스템 상태는 해당 컴포넌트 상태가 모인 것입니다.
논의할 개념을 설명하는 예로 승강기 시스템을 사용하겠습니다. 더 정확히 말하면, 빌딩의 한 위치에 있는 승강기 그룹을 제어하도록 설계된 컴퓨터 시스템을 의미합니다. 분명, 승강기 그룹에서는 동시에 많은 일이 진행되거나 또는 아무것도 진행되지 않을 수 있습니다. 어떤 층에 있는 사람이 승강기를 요구하면 다른 요청은 항상 보류됩니다. 다른 승강기가 승객을 태우거나 호출에 응답하는 동안 일부 승강기는 대기합니다. 적절한 시간에 문이 열리고 닫혀야 합니다. 승객이 문을 가로막거나 문을 열고 닫는 단추를 누르거나 또는 층을 선택한 후 생각을 바꿀 수 있습니다. 승강기 제어 시스템의 관리 아래 표시장치를 갱신하고 모터 등을 제어해야 합니다. 전반적으로 동시성 개념을 탐구하기에 좋은 모델이며 용어를 이해하고 작업하는 데 있어 공통 수준을 합리적으로 공유하는 모델입니다.
그림 2: 두 개의 승강기와 11개 층에 분포되어 있는 다섯 명의 가상 승객이 있는 시나리오.
가상 승객이 각기 다른 시간에 시스템을 요구하면 시스템은 현재 상태 및 계획된 응답 시간을 기반으로 호출에 응답할 승강기를 선택하여 총체적으로 최상의 서비스를 제공하려고 합니다. 예를 들어, 첫 번째 가상 승객 Andy가 내려가는 승강기를 호출할 때 두 개의 승강기 모두가 대기 중인 경우, Andy를 태우기 위해 먼저 위로 올라가야 하지만 가장 가까이 있는 승강기 2가 응답합니다. 반대로, 잠시 후 두 번째 가상 승객 Bob이 올라가는 승강기를 요청하면 아래에서 위로 올라 오라는 호출에 응답하기 전에 승강기 2가 알 수 없는 목적지로 내려가야 하므로 다소 멀리 있는 승강기 1이 응답합니다.
승강기 시스템에 하나의 승강기만 있고 이 승강기가 한 번에 한 명의 승객에게만 제공되는 경우, 일반적인 순차 프로그램을 사용하여 이를 처리할 수 있습니다. 이렇게 "간단한" 경우에도 프로그램에는 여러 가지 조건을 조절하는 수 많은 분기가 필요합니다. 예를 들어, 승객이 전혀 타지 않고 층을 선택하지 않는 경우 다른 호출에 응답할 수 있도록 승강기를 다시 설정하려고 합니다.
여러 가상 승객의 호출 및 요청을 처리하는 일반적인 요구사항은 이전에 논의한 동시성을 외부에서 강제로 작동시키는 좋은 예가 됩니다. 가상 승객은 자체적으로 동시에 생활을 하기 때문에 승강기 상태와 관계없이 아무 때나 승강기를 요구합니다. 이전 결정에 따라 승강기를 운행하면서 언제든지 외부 이벤트 모두에 응답할 수 있는 순차 프로그램을 설계하는 것은 매우 어렵습니다.
병행 시스템을 효과적으로 설계하려면 시스템에서 동시성의 역할을 판단할 수 있어야 하며 이를 위해서는 동시성 자체에 대한 요약이 필요합니다.
병행 시스템의 기초가 되는 빌딩 블록은 서로에 대해 다소 독립적으로 진행되는 "활동"입니다. 이러한 활동을 고려한 유용한 그래픽 요약은 Buhr의 "timethreads"입니다. [BUH96] 그림 3에 있는 승강기 시나리오는 실제로 이러한 양식을 사용합니다. 각 활동은 활동이 이동함에 따라 선으로 표시됩니다. 큰 점은 활동이 시작하거나 진행하기 전에 이벤트가 발생하기를 기다리는 위치를 표시합니다. 하나의 활동을 수행함으로써 계속해서 다른 활동을 진행할 수 있습니다. 여기서, 다른 timethread의 대기 중인 지점을 터치하여 timethread 표기법에 표시됩니다.
그림 3: 실행 스레드 표시
소프트웨어의 기본 빌딩 블록은 프로시저 및 데이터 구조이지만 단독으로 사용될 경우, 동시성으로 인해 적당하지 않습니다. 프로세서가 프로시저를 실행할 때 현재 조건에 따라 특정 경로를 따르게 됩니다. 이 경로를 "실행 스레드" 또는 "제어 스레드"라고 합니다. 제어 스레드는 그 시점에 존재하는 조건에 따라 다른 분기 또는 루프를 수행할 수 있으며, 실시간 시스템은 특정 기간 동안 일시정지하거나 다시 시작하기 위해 예정된 시간에 맞쳐 대기할 것입니다.
프로그램 설계자의 관점에서 보면 실행 스레드는 프로그램 논리에 따라 제어되고 운영 체제에 의해 스케줄됩니다. 소프트웨어 설계자가 하나의 프로시저가 다른 프로시저를 호출하도록 선택한 경우, 실행 스레드는 하나의 프로시저에서 다른 프로시저로 건너뛴 후 return문 발생시 정지한 곳으로 다시 돌아가서 계속 실행합니다.
CPU 관점에서 하나의 주요 실행 스레드만이 소프트웨어를 통해서 전달됩니다. 여기서, 소프트웨어는 하드웨어 인터럽트에 응답하여 실행되는 별도의 짧은 스레드를 추가하였습니다. 기타 모든 것도 이 모델에서 빌드되기 때문에 설계자는 이 사실을 알아야 합니다. 최하위 레벨에서 시스템이 작동하는 방법에 대해 실시간 시스템 설계자는 다른 유형의 소프트웨어 설계자보다 좀 더 상세히 알아야 합니다. 그러나 이 모델은 간단하게 추상화하므로 동시성은 CPU의 대략적인 정보만을 나타냅니다. 복잡한 시스템을 설계하려면 다양한 관점에서 추상화하는 것이 유용합니다. 물론 추상화는 불필요한 세부사항을 감추고 뷰 및 모델을 작성하여 문제점의 중요한 사항에 중점을 둡니다.
레벨을 한 단계 위로 이동할 경우, 일반적으로 소프트웨어의 계층에 대해 고려합니다. 가장 기본 레벨에서 운영 체제(OS)는 하드웨어와 어플리케이션 소프트웨어 사이에 층을 이룹니다. 이는 하드웨어 기반 서비스(예: 메모리, 타이밍 및 I/0)가 포함된 어플리케이션을 제공하지만 실제 하드웨어 형상과 무관한 가상 시스템을 작성하기 위해 CPU를 추상화합니다.
동시성을 지원하려면 시스템에서 다중 제어 스레드를 제공해야 합니다. 제어 스레드 요약은 하드웨어 및 소프트웨어에서 다양한 방법으로 구현할 수 있습니다. 가장 일반적인 메커니즘은 다음 중 하나를 수정한 것입니다[DEI84], [TAN86].
운영 체제가 멀티태스킹을 제공하는 경우 동시성의 일반적인 단위는 프로세스입니다. 프로세스는 프로그램을 실행하는 환경을 제공하는 하나의 목적을 갖는 운영 체제에서 제공하고 지원 및 관리하는 엔티티입니다. 프로세스는 해당 어플리케이션이 독점적으로 사용할 수 있는 메모리 공간, 실행을 위한 실행 스레드 및 다른 프로세스와 메시지를 주고 받는 방법을 제공합니다. 실제로 프로세스는 어플리케이션의 동시 부분 실행을 위한 가상 CPU입니다.
각 프로세스에는 다음과 같은 세 가지 가능한 상태가 있습니다.
또한 프로세스에는 종종 관련 우선순위가 지정됩니다. 운영 체제 커널은 상태, 우선순위 및 스케줄 정책에 기반하여 주어진 시간에 실행할 프로세스를 결정합니다. 멀티태스킹 운영 체제는 실제로 모든 프로세스에서 하나의 제어 스레드를 공유합니다.
참고: 용어 '타스크' 및 '프로세스'는 종종 바꿔 사용할 수 있습니다. 그러나 '멀티프로세싱'이 다중 프로세서(CPU)가 있는 시스템을 나타내는 반면, 용어'멀티태스킹'은 일반적으로 다중 프로세스를 동시에 관리하는 성능을 의미하는 데 사용됩니다. 이 규칙이 가장 일반적으로 사용되므로 이 규칙을 따릅니다. 그러나 용어 '타스크'는 거의 사용하지 않으며 사용하는 경우는 완료 중인 작업의 단위(타스크)와 이에 대한 자원 및 환경을 제공하는 엔티티(프로세스)를 확실하게 구별하기 위해서입니다.
전에 언급한 바와 같이 CPU 관점에서 보면 실행 스레드는 단 하나입니다. 어플리케이션이 서브루틴을 호출하여 하나의 프로시저에서 다른 프로시저로 건너뛸 수 있으므로 운영 체제는 인터럽트 발생, 프로시저 완료 또는 일부 다른 이벤트 발생시 하나의 프로세스에서 다른 프로세스로 제어를 전송할 수 있습니다. 프로세스에서 제공하는 메모리 보호로 인해 "타스크 전환"은 상당한 오버헤드를 수반할 수 있습니다. 더욱이 스케줄 정책 및 프로세스 상태에는 어플리케이션 시점으로 수행할 것이 거의 없으므로 프로세스 끼워넣기는 일반적으로 어플리케이션에 중요한 일종의 동시성으로 간주하기에 최하위 레벨의 추상화입니다.
동시성을 명확하게 판단하기 위해서는 실행 스레드의 개념과 타스크 전환의 개념을 정확하게 구분하는 것이 중요합니다. 각 프로세스는 자체 실행 스레드를 유지보수한다고 간주할 수 있습니다. 운영 체제에서 프로세스를 전환하는 경우 실행 스레드 중 하나는 임시적으로 인터럽트되며 다른 하나는 이전에 정지된 곳에서 시작하거나 재개합니다.
다양한 운영 체제, 특히 실시간 어플리케이션에 사용되는 운영 체제는 프로세스 대안으로 "스레드" 또는 "가벼운 무게 스레드"라는 "더 가벼운 무게"를 제공합니다.
스레드를 사용하여 프로세스에서 조금 더 뛰어난 세분성이 있는 동시성을 달성할 수 있습니다. 각 스레드는 하나의 프로세스에 속하며 프로세스에 있는 모든 스레드는 하나의 메모리 공간 및 프로세스에서 제어하는 다른 자원을 공유합니다.
일반적으로 각 스레드는 프로시저를 실행하도록 지정됩니다.
참고: 용어 '스레드'가 과부하되면 실패합니다. 여기서와 같이 단어 '스레드'만을 사용하는 경우 이는 운영 체제에서 제공하고 관리하는 '실제 스레드'를 나타냅니다. 앞서 설명한 '실행 스레드', '제어 스레드' 또는 'timethread'를 언급하는 경우 이는 실제 스레드와 연관되지 않아도 되는 요약을 의미합니다.
물론, 다중 프로세서는 정확한 동시 실행의 기회를 제공합니다. 가장 일반적으로 각 타스크는 특정 프로세서에 있는 프로세스에 영구적으로 지정되지만, 일부 환경에서는 타스크를 사용 가능한 다음 프로세서에 동적으로 지정할 수 있습니다. 가능한 경우 "대칭 멀티프로세서"를 사용하여 이를 수행하는 것이 가장 쉬운 방법입니다. 이러한 하드웨어 형상에서 다중 CPU는 공통 버스를 통해 메모리에 액세스할 수 있습니다.
대칭 멀티프로세서를 지원하는 운영 체제는 사용 가능한 모든 CPU에 스레드를 동적으로 지정할 수 있습니다. 대칭 멀티프로세서를 지원하는 운영 체제의 예를 들면 SUN Solaris 및 Microsoft Windows NT입니다.
이전에 이 문서에서 동시성이 소프트웨어 복잡도를 증가시키기도 감소시키기도 한다는 역설적으로 보이는 주장을 했습니다. 근본적으로 동시 소프트웨어는 병행 활동 간의 "분리된 관계"를 허용하므로 복잡한 문제점에 대한 더 간단한 솔루션을 제공합니다. 이 점에 있어 동시성은 소프트웨어의 모듈성을 증가시키는 하나 이상의 툴일 뿐입니다. 시스템에서 주로 독립적인 활동을 수행하거나 주로 독립적인 활동에 응답해야 하는 경우 활동을 개별 동시 컴포넌트에 지정하면 설계를 자연스럽게 단순화합니다.
동시 소프트웨어와 연관된 추가의 복잡도는 거의 대부분 병행 활동이 완전히는 아니지만 거의 독립적인 상황에서 발생합니다. 즉, 복잡도는 활동의 상호 작용에서 발생합니다. 실제로 비동기 활동 간의 상호 작용에는 반드시 일부 신호 또는 정보 양식의 교환이 포함됩니다. 동시 제어 스레드 간의 상호 작용은 동시 시스템에 대해 고유하며 시스템이 올바르게 작동함을 보증하도록 지정해야 하는 실행 세트를 발생시킵니다.
IPC(inter-process communication) 또는 스레드 내 의사소통 메커니즘의 다양한 특정 인식이 많지만 결국 모두 두 개의 범주로 분류할 수 있습니다.
비동기 의사소통에서 활동을 보내면 수신측이 수신할 준비가 되어 있는지 여부와 관계없이 해당 정보를 전달합니다. 해당 방법으로 정보를 시작한 후 송신측은 다음에 수행해야 하는 모든 것을 진행합니다. 수신측이 정보를 수신할 준비가 되어 있지 않은 경우 정보는 나중에 수신측에서 검색할 수 있는 큐에 놓여집니다. 송신측 및 수신측 모두 서로를 비동기적으로 조작하므로 서로의 상태에 대해 가정할 수 없습니다. 비동기 의사소통을 종종 메시지 전달이라고 합니다.
동기 의사소통에는 정보 교환 이외에도 송신측 및 수신측 간의 동기화가 포함됩니다. 정보를 교환하는 동안 두 개의 병행 활동은 실제로 공유된 코드 세그먼트를 실행하여 서로 병합된 후 의사소통이 완료되면 다시 분할됩니다. 따라서 이 간격 동안 활동은 서로 동기화되며 상호간의 동시성 충돌에 영향을 받지 않습니다. 하나의 활동(송신측 또는 수신측)이 다른 활동 전에 통신할 준비가 되면 다른 활동도 준비될 때까지 일시중단됩니다. 이 때문에 이 통신 모드를 종종 랑데뷰라고 합니다.
동기 의사소통에서 가능한 문제점은 피어가 준비되기를 기다리는 동안 활동은 다른 이벤트에 반응할 수 없다는 것입니다. 많은 실시간 시스템에서 중요한 상황에 대한 적시의 응답을 보증할 수 없으므로 동기 의사소통이 항상 채택되지는 않습니다. 또다른 약점은 교착 상태가 되기 쉽다는 것입니다. 교착 상태는 두 개 이상의 활동이 서로를 기다리는 잘못된 주기에 관련되는 경우 발생합니다.
병행 활동 간의 상호 작용이 필요한 경우 설계자는 동기 또는 비동기 양식 중에서 선택해야 합니다. 동기는 두 개 이상의 동시 제어 스레드가 동시에 랑데뷰해야 함을 의미합니다. 이는 일반적으로 하나의 제어 스레드는 다른 제어 스레드가 요청에 응답하는 것을 기다리는 것을 말합니다. 가장 간단하고 일반적인 동기 상호 작용 양식은병행 활동 A가 작업을 진행하는 데 병행 활동 B의 정보가 필요한 경우 발생합니다.
동기 상호 작용은 물론, 비동시 소프트웨어 컴포넌트의 기준입니다. 일반 프로시저 호출은 동기 상호 작용의 기본적인 예입니다.하나의 프로시저가 다른 프로시저를 호출하면 호출자는즉각 제어를 호출된 프로시저로 전송하고 제어가 다시 전달될 때까지 실제로 "대기"합니다. 그러나 동시 작업에는 동기화에 추가의 장치 또는 독립 제어 스레드가 필요합니다.
비동기 상호 작용은 때에 맞춰 랑데뷰하지 않아도 되지만 두 제어 스레드 간의 의사소통을 지원하기 위한 추가의 장치가 필요합니다. 종종 이 장치는 메시지를 비동기적으로 주고 받을 수 있도록 메시지 대기열이 있는 의사소통 채널 양식을 취합니다.
하나의 어플리케이션은 메시지 수신측이 메시지를 처리하는 동안 작업할 수 있는 다른 작업이 있는지 또는 응답을 기다려야 하는지 여부에 따라 동기 및 비동기 의사소통을 혼합할 수도 있습니다.
실제 프로세스 또는 스레드 동시성은 프로세스 또는 스레드가 동시 실행하는 다중 프로세서에서만 가능함에 주의하십시오. 단일 프로세서에서의 프로세스 또는 스레드 동시 실행에 대한 착각은 여러 스레드 또는 프로세스가 동시에 실행 중인 것으로 나타나도록 사용 가능한 처리 자원을 작은 조각으로 자르는 운영 체제 스케줄러로 인한 것입니다. 잘못된 설계는 자주 동기적으로 통신하는 다중 프로세스 또는 스레드를 작성하고 프로세스 또는 스레드가 효과적으로 블록화된 "시간 구획"을 많이 소요하도록 하며 다른 프로세스 또는 스레드의 응답을 대기하여 이러한 시간 구획화를 무효로 합니다
병행 활동은 활동 간에 공유해야 하는 부족한 자원에 따라 달라질 수 있습니다. 일반적인 예는 I/O 장치입니다. 다른 활동에서 사용 중인 자원이 활동에 필요한 경우 순서를 기다려야 합니다.
"레이스 조건"을 피하는 것이 동시 시스템 설계의 가장 기본적인 문제가 될 수 있습니다. 시스템 파트에서 상태 의존 함수(결과가 현재 시스템 상태에 따라 다른 함수)를 수행해야 하는 경우, 조작 동안 상태가 안정적인지 확인해야 합니다. 즉, 일부 조작이 "원자적"이어야 합니다. 두 개 이상의 제어 스레드가 같은 상태 정보에 액세스할 수 있을 때마다 하나의 스레드가 원자 상태 의존 조작을 수행하는 동안 다른 스레드가 상태를 수정하지 않도록 하기 위해 "동시성 제어" 양식이 필요합니다. 내부적으로 불일치한 상태를 만들 수 있는, 동시에 같은 상태 정보에 액세스하는 것을 "레이스 조건"이라고 합니다.
레이스 조건의 일반적인 예는 승강기 시스템에서 승객이 층을 선택하는 경우 쉽게 발생할 수 있습니다. 승강기는 위 아래 각각의 위치로 이동할 때 방문할 층 목록에 따라 작업합니다. 승강기가 층에 도착할 때마다 하나의 제어 스레드는 그 층을 해당 목록에서 제거하며 다음 목적지를 목록에서 가져옵니다. 목록이 비는 경우 승강기는 다른 목록에 층이 있으면 방향을 바꾸고 두 목록 모두 비어 있는 경우에는 대기합니다. 다른 제어 스레드는 승객이 층을 선택하면 층 요청을 적절한 목록에 배치해야 합니다. 각 스레드는 본래 원자가 아닌 목록에 있는 조작의 조합을 수행합니다(예: 사용 가능한 다음 슬롯을 확인한 후 슬롯 채우기). 스레드가 조작을 끼워넣으면 목록에 있는 같은 슬롯을 쉽게 겹쳐쓸 수 있습니다.
교착 상태는 두 개의 제어 스레드 모두가 서로 다른 스레드가 조치를 수행하기를 기다리며 블록화되어 있는 조건입니다. 아이러닉하게 교착 상태는 레이스 조건을 피하기 위해 동기화 메커니즘을 적용하기 때문에 종종 발생합니다.
승강기의 레이스 조건에 대한 예는 비교적 양호한 교착 상태의 경우를 쉽게 일으킬 수 있습니다. 승강기 제어 스레드는 목록이 비어 있다고 간주하여 다른 층을 방문하지 않습니다. 층 요청 스레드는 승강기가 목록을 비우기 위해 작업 중이므로 대기 상태를 떠나도록 승강기에 알릴 필요가 없다고 간주합니다.
"근본" 문제 이외에도 동시 소프트웨어 설계에 명확하게 지정해야 하는 실제 문제가 있습니다.
타스크를 전환하여 동시성을 시뮬레이션하는 데 필요한 메커니즘은 단일 CPU에서 사용하지 않으면 어플리케이션 자체에서 소요될 수 있는 CPU 주기를 사용합니다. 반면 예를 들어, 소프트웨어가 I/O 장치를 기다려야 하는 경우 동시성이 제공하는 성능 향상은 추가된 오버헤드를 훨씬 능가할 수 있습니다.
동시 소프트웨어에는 순차 프로그래밍 어플리케이션에서는 필요하지 않는 조정 및 제어 메커니즘이 필요합니다. 따라서 동시 소프트웨어는 더 복잡해지고 오류가 발생할 기회도 많아집니다. 또한 동시 시스템의 문제점은 다중 제어 스레드로 인해 본래 진단하기 더 어렵습니다. 반면, 이전에 지적한 대로 외부에서 작동시키는 강제 실행 자체가 동시적인 경우 다른 이벤트를 독립적으로 처리하는 동시 소프트웨어는 임의의 순서대로 이벤트를 조정해야 하는 순차 프로그램보다 훨씬 더 간단할 수 있습니다.
많은 인수가 동시 컴포넌트 실행을 끼워넣도록 결정하므로 같은 소프트웨어가 같은 순서의 이벤트에 다른 순서로 응답할 수 있습니다. 설계에 따라 이러한 순서의 변경은 다른 결과를 낳을 수 있습니다.
어플리케이션 소프트웨어는 동시성 제어에 관련되거나 관련되지 않을 수 있습니다. 가능한 전체 스펙트럼에는 다음이 포함되어 있으며 관련성을 증가시키는 순서대로 입니다.
이러한 가능성은 철저한 세트도 상호 독점적인 것도 아닙니다. 제공된 시스템에서 이들의 조합이 채택됩니다.
동시 시스템 설계 시의 일반적인 실수는 설계 프로세스에서 특정 메커니즘을 동시성에 사용하도록 너무 빨리 선택하는 것입니다. 각 메커니즘에는 장점 및 단점이 있으며 특정 상황에 대한 "최상"의 메커니즘 선택은 종종 정교한 트레이드오프 및 절충으로 결정됩니다. 메커니즘을 일찍 선택할 수록 선택에 기반한 정보는 줄어듭니다. 메커니즘을 고정시키는 것 또한 다른 상황에 대한 설계의 유연성 및 적용성을 감소시키는 경향이 있습니다.
가장 복잡한 설계 타스크에서와 같이 다중 요약 레벨을 채택하면 동시성을 가장 잘 이해할 수 있습니다. 먼저 요구된 작동에 대해 시스템의 기능 요구사항을 잘 알아야 합니다. 다음으로 동시성의 가능한 역할을 탐색해야 합니다. 특정 구현에 커미트하지 않고 스레드 요약을 사용하면 가장 훌륭히 완료됩니다. 가능한 범위까지 동시성을 인식하기 위한 마지막 메커니즘 선택은 우수한 성능 튜닝 및 유연성을 허용하여 다양한 제품 형상에 맞게 컴포넌트를 다르게 분배하도록 개방되어 있어야 합니다.
문제점 도메인(예: 승강기 시스템) 및 솔수션 도메인(소프트웨어 구성) 간 "개념적 차이"는 시스템 설계의 가장 큰 어려움 중 하나입니다. "비주얼 형식"은 동시 작동과 같은 복잡한 개념을 이해하고 통신하며 사실상 개념적 차이를 연결하는 데 매우 유용합니다. 이러한 문제점에 대한 증명된 값이 있는 툴은 다음과 같습니다.
동시 소프트웨어 시스템을 설계하려면 소프트웨어의 빌딩 블록(프로시저 및 데이터 구조)을 동시성의 빌딩 블록(제어 스레드)과 결합해야 합니다. 병행 활동의 개념에 대해 논의했으나 활동에서 시스템을 구성하지는 않습니다. 컴포넌트에서 시스템을 구성하며 동시 컴포넌트에서 동시 시스템을 구성합니다. 별도로 수행하는 경우, 프로시저 및 데이터 구조 또는 제어 스레드 모두는 그다지 자연스러운 동시 컴포넌트 모델을 작성하지 않지만 객체는 모든 필수 요소를 하나의 정돈된 패키지로 결합하는 자연스러운 방법처럼 보입니다.
객체는 자체 상태 및 작동과 함께 프로시저 및 데이터 구조를 결합력 있는 컴포넌트로 패키지합니다. 이는 특정 상태 및 작동 구현을 캡슐화하고 다른 객체 또는 소프트웨어가 상호 작용할 수 있는 인터페이스를 정의합니다. 일반적으로 객체는 실제 엔티티 또는 개념을 나타내며 메시지를 교환하여 다른 객체와 상호 작용합니다. 복잡한 시스템을 구성하는 가장 좋은 방법으로 많은 곳에서 채택되고 있습니다.
그림 4: 승강기 시스템에 대한 간단한 객체 세트.
승강기 시스템에 대한 객체 모델을 가정하십시오. 각 층에 있는 호출 스테이션에서는
해당 층의 위 및 아래로의 호출 단추를 모니터합니다. 예상 승객이 단추를 누르면
호출 스테이션 객체는 가장 빠른 서비스를 제공하고 승강기를 디스패치하며
호출을 인식할 것같은 승강기를 선택하는 승강기 디스패처 객체로
메시지를 보내서 응답합니다.
각 승강기 객체는 승객의 층 선택 및 디스패처의 호출에 응답하여
동시에 독립적으로 실제 승강기 일부를 제어합니다.
동시성은 이러한 객체 모델에서 두 개의 양식을 수행할 수 있습니다. 두 개 이상의 객체가 별도의 제어 스레드를 통해 독립적으로 활동을 수행 중인 경우 객체 내 동시성이 발생합니다. 객체 간 동시성은 다중 제어 스레드가 하나의 객체에서 사용 중인 경우 발생합니다. 오늘날 대부분의 객체 지향 언어에서 객체는 자체 제어 스레드가 없는 "수동"입니다. 외부 환경에서 제어 스레드를 제공해야 합니다. 가장 일반적으로 환경은 C++ 또는 Smalltalk와 같은 언어로 작성된 객체 지향 "프로그램"을 실행하도록 작성된 표준 OS 프로세스입니다. OS에서 멀티스레딩을 지원하는 경우 같거나 다른 객체에서 다중 스레드를 사용할 수 있습니다.
아래 그림에서 순환 요소는 수동 객체를 표시합니다. 각 객체의 어두운 안쪽 부분은 상태 정보이며 분리된 외부 고리는 객체 작동을 정의하는 프로시저(메소드) 세트입니다.
그림 5: 객체 상호 작용 설명.
객체 간 동시성은 다중 제어 스레드가 같은 메모리 공간에 액세스할 수 있는 경우(이 경우 데이터는 객체에 캡슐화됨) 레이스 조건 가능성과 같은 동시 소프트웨어의 모든 문제를 수반합니다. 데이터 캡슐화가 이 문제점에 대한 솔루션을 제공한다고 생각할 수 있습니다. 문제는 물론 객체가 제어 스레드를 캡슐화하지 않는다는 점입니다. 객체 간 동시성이 대부분의 파트에서 이러한 문제점을 피하지만 여전히 하나의 까다로운 문제점이 존재합니다. 두 개의 동시 객체가 메시지를 교환하며 상호 작용하는 데는 최소한 두 개의 제어 스레드가 메시지를 처리하고 이를 전달하기 위해 같은 메모리 공간에 액세스해야 합니다. 이와 관련된 훨씬 더 어려운 문제점은 다른 프로세스 또는 다른 프로세서에서의 객체 분배입니다. 다른 프로세스에 있는 객체 간 메시지에는 프로세스 간 의사소통에 대한 지원이 필요하며 일반적으로 메시지는 프로세스 경계를 넘어 전달할 수 있는 데이터로 인코드 및 디코드되어야 합니다.
물론 이러한 문제점 모두 극복하기 어렵지는 않습니다. 사실, 이전 섹션에서 지적한 대로 모든 동시 시스템에는 증명된 솔루션이 있으므로 동시 시스템으로 처리해야 합니다. 이는 단지 "동시성 제어"가 추가 작업을 발생시키고 오류에 대한 추가 기회를 도입하는 것입니다. 더욱이 어플리케이션 문제점의 본질을 가립니다. 이러한 모든 이유로 어플리케이션 프로그래머에 대한 필요성을 최소화하여 이를 명확하게 처리하려 합니다. 이를 완성하는 한 가지 방법은 동시 객체(동시성 제어 포함) 간 메시지 전달을 지원하는 객체 지향 환경을 빌드하고 단일 객체 내의 다중 제어 스레드 사용을 최소화하거나 제거하는 것입니다. 요컨대, 이 방법은 데이터와 함께 제어 스레드를 캡슐화합니다.
자체 제어 스레드가 있는 객체를 "활성" 객체라고 합니다. 다른 활성 객체와의 비동기 의사소통을 지원하려면 각 활성 객체를 메시지 대기열 또는 "편지함"과 함께 제공해야 합니다. 객체가 작성되면 환경은 객체가 소멸될 때까지 객체가 캡슐화하는 제어 스레드를 제공합니다. 수동 객체와 같이 활성 객체도 외부에서 메시지가 도착할 때까지 대기합니다. 객체는 메시지 처리에 적절한 모든 코드를 실행합니다. 객체를 사용 중인 동안 도착한 메시지는 편지함에서 대기합니다. 객체에서 메시지 처리를 완료하면 편지함에서 다음 대기 메시지를 선택하기 위해 리턴하거나 메시지가 도착할 때까지 대기합니다. 승강기 시스템에 있는 활성 객체로 적당한 후보에는 승강기 자체, 각 층의 호출 스테이션 및 디스패처가 포함됩니다.
구현에 따라 활성 객체를 매우 효율적으로 작성할 수 있습니다. 활성 객체는 수동 객체보다 좀더 많은 오버헤드를 수반합니다. 따라서 모든 조작이 동시가 아니어도 되므로 같은 시스템서 활성 및 수동 객체를 혼합하는 것이 일반적입니다. 다른 의사소통 양식 때문에 피어로 만드는 것은 어렵지만 활성 객체는 이전에 사용한 OS 프로세스를 바꾸어 수동 객체에 필요한 이상적인 환경을 조성합니다. 사실상 활성 객체가 모든 작업을 수동 객체에 위임하면 프로세스 간 의사소통 기능이 있는 OS 프로세스 또는 스레드와 기본적으로 같습니다. 그러나 더 흥미로운 활성 객체는 다른 파트를 수동 객체에 위임하여 작업 파트를 수행하는 자체 작동이 있습니다.
그림 6: '활성' 객체는 수동 클래스에 필요한 환경을 제공합니다.
활성 승강기 객체 내부의 수동 객체에 대한 적당한 후보에는 올라가고 내려가는 동안 승강기가 멈춰야 하는 층 목록이 포함됩니다. 승강기는 다음 멈춤 목록을 요청하고 새로운 멈춤을 목록에 추가하며 수행된 멈춤을 제거할 수 있어야 합니다.
복잡한 시스템은 대부분 항상 서브시스템의 여러 레벨로 구성되므로 리프 레벨 컴포넌트를 가져오기 훨씬 전에활성 객체에 다른 활성 객체가 포함되도록 허용하면 활성 객체 모델로 자연스럽게 확장됩니다.
단일 스레드의 활성 객체는 실제 객체 간 동시성을 지원하지 않지만 작업을 포함된 활성 객체에 위임하면 많은 어플리케이션에서 합리적으로 대체됩니다. 이는 객체별 기반의 상태, 작동 및 제어 스레드의 완성된 캡슐화가 동시성 제어를 단순화하는 주요 장점을 유지합니다.
그림 7: 내포된 활성 객체를 표시하는 승강기 시스템.
예를 들어, 위에서 서술한 부분적인 승강기 시스템을 생각해 보십시오. 각 승강기에는 문, 승강기 및 제어판이 있습니다. 이러한 각 컴포넌트는 문 객체가 승강기 문 열기 및 닫기를 제어하는 동시 활성 객체에 의해 잘 설계되어 있으며, 승강기 객체는 기계적 승강기를 통해 승강기의 위치 지정을 제어하며 제어판 객체는 층 선택 단추 및 문 열기/닫기 단추를 모니터합니다. 동시 제어 스레드를 활성 객체처럼 캡슐화하면 이러한 모든 작동을 단일 제어 스레드로 관리하는 경우 달성할 수 있는 것보다 더 단순한 소프트웨어가 됩니다.
레이스 조건에 대해 설명하면서 언급한 대로 시스템이 올바르고 예측 가능한 방법으로 작동하도록 하려면 일부 상태 의존 조작이 원자적이어야 합니다.
객체가 올바르게 작동하려면 메시지 처리 전과 후에 반드시 상태가 내부적으로 일치해야 합니다. 메시지 처리 동안 조작이 부분적으로만 완료되어 객체 상태가 일시적인 조건에 있을 수 있으며 비결정적일 수 있습니다.
객체가 다른 메시지에 응답하기 전에 항상 하나의 메시지에 대한 응답을 완료하는 경우 일시적인 조건은 문제가 되지 않습니다. 하나의 객체가 다른 객체를 실행하는 것을 인터럽트하는 것 또한 각 객체가 상태를 세밀하게 캡슐화하므로 문제가 되지 않습니다. (엄밀히 말하면 완전히 그렇지는 않으며 이에 대해 곧 설명하게 됩니다.)
객체가 메시지 처리를 인터럽트하여 다른 메시지를 처리하는 상황에서는 레이스 조건이 될 가능성이 있으므로 동시성 제어를 사용해야 합니다. 다음으로 교착 상태가 될 가능성도 있습니다.
그러므로 동시 설계는 일반적으로 객체가 다른 메시지를 승인하기 전에 각 메시지 처리를 완료하면 더 간단합니다. 이 작동은 앞서 제시했던 일부 활성 객체 모델 양식에 내포되어 있습니다.
일치 상태에 관한 문제는 두 가지 다른 양식의 동시 시스템으로 나타나는데 이는 객체 지향 동시 시스템에 관해 이해하기 더 쉬울 수 있습니다. 첫 번째 양식은 이미 설명한 것입니다. 하나의 객체(수동 또는 활성) 상태가 두 개 이상의 제어 스레드에 액세스할 수 있는 경우 기본 CPU 조작의 자연적인 원자성 또는 동시성 제어 메커니즘으로 원자 조작을 보호해야 합니다.
일치 상태 문제의 두 번째 양식은 더 정교할 수 있습니다. 두 개 이상의 오브젝트(활성 또는 수동)에 같은 상태 정보가 있는 경우 객체는 최소한의 짧은 시간 간격 동안 불가피하게 상태에 대해 동의하지 않습니다. 잘못된 설계에서는 더 긴 기간 또는 계속 동의하지 않을 수 있습니다. 불일치 상태의 이러한 조짐은 다른 양식의 수학적 "이중"으로 간주될 수 있습니다.
예를 들어, 승강기 동작 제어 시스템(승강기)은 승강기가 움직이기 전에 문이 닫혀서 열 수 없는지 확인해야 합니다. 설계에 올바른 안전 장치가 없으면 승객이 문 열기 단추를 누르는 것에 응답하여 승강기가 움직이기 시작할 때 문이 열릴 수 있습니다.
상태 정보를 하나의 객체에만 상주하도록 허용하는 것이 이러한 문제점에 대한 쉬운 솔루션처럼 보이기도 합니다. 그러나 이 솔루션이 도움은 되지만 특히 분배된 시스템에서 성능에 해로운 영향을 줄 수도 있습니다. 더욱이 간단한 솔루션이 아닙니다. 특정 상태 정보가 하나의 객체에만 있는 경우에도 다른 동시 객체가 특정 시점에서 이 상태에 기반하여 결정을 하는 한 상태 변경은 다른 객체의 결정을 무효화할 수 있습니다.
일치 상태 문제점에 대한 완벽한 솔루션은 없습니다. 모든 실제 솔루션은 원자 조작을 식별해야 하며 적당히 짧은 기간 동안 동시 액세스를 블록화하는 일종의 동기화 메커니즘으로 보호해야 합니다. "적당히 짧은"은 컨텍스트에 따라 매우 다릅니다. 이는 CPU에서 모든 바이트를 부동 소수점 수로 저장하는 동안 또는 승강기가 다음으로 멈출 때까지 이동하는 데 걸리는 동안이 될 수 있습니다.
Rational Unified Process
|