Java의 구조적 동시성 이해

Java의 구조적 동시성 이해

일반적으로 프로그래밍의 복잡성은 작업을 하위 작업으로 분류하여 관리됩니다. 이 하위 작업은 동시에 실행될 수 있습니다.

Java 5 이후 ExecutorService API는 프로그래머가 이러한 하위 작업을 동시에 실행하도록 도와줍니다. 그러나 동시 실행의 특성을 고려할 때 각 하위 작업은 이들 사이의 암시 적 의사 소통없이 독립적으로 실패하거나 성공할 수 있습니다. 한 하위 작업의 실패는 다른 하위 작업을 자동으로 취소하지 않습니다.. 외부 취급을 통해이 취소를 수동으로 관리하려는 시도를 할 수 있지만, 특히 많은 수의 하위 작업이 관련 될 때 제대로 얻는 것은 매우 까다 롭습니다.

이로 인해 스레드가 느슨 할 수 있습니다 (대안으로 스레드 누출이라고합니다.). 가상 스레드 스레드를 각 (하위) 작업에 전념하는 비용 효율적인 방법입니다. 그들의 결과 여전히 도전으로 남아 있습니다.

Executor Service를 사용하면 하나의 스레드가이를 생성하고 다른 스레드는이 실행자 서비스에 작업을 제출할 수 있습니다. 작업을 수행하는 스레드는이 두 스레드와 관련이 없습니다. 또한, 완전히 다른 스레드는이 하위 작업에서의 실행 결과를 기다릴 수 있습니다. Future 오브젝트 – 임원 서비스에 작업 제출 직후에 제공됩니다. 따라서 효과적으로 하위 작업은 제출 한 작업으로 돌아갈 필요가 없습니다. 여러 스레드로 돌아가거나 없을 수도 있습니다.

또한 작업과 하위 작업의 관계는 논리적이며 코드 구조에서는 보이지 않습니다. 런타임에서 작업 서브타스크 관계의 시행 또는 추적은 없습니다.

Java의 구조적 동시성은 위의 모든 과제를 해결하는 것을 목표로합니다.

  • 하위 작업 취소를 확실하게 자동화하는 것; 스레드 누출 및 지연을 피합니다.
  • (하위) 작업이 제출 된 스레드로만 반환하는지 확인합니다.
  • 작업과 하위 작업 사이의 구조적 관계를 시행합니다.

구조화되지 않은 동시성

Java의 구조화되지 않은 동시성의 현재 상황에 대해 더 많이 이해합시다. 함수를 고려하십시오 handle() 사용자 정보와 관련 순서를 가져옵니다.

Response handle() throws ExecutionException, InterruptedException {
    Future  user  = esvc.submit(() -> findUser());
    Future order = esvc.submit(() -> fetchOrder());
    String theUser  = user.get();   // Join findUser
    int    theOrder = order.get();  // Join fetchOrder
    return new Response(theUser, theOrder);
}

언뜻보기에 코드는 단순 해 보이고 의도하는 일을합니다. 그러나 자세히 살펴보면 여러 가지 문제를 식별 할 수 있습니다.

만약에 findUser() 실행 된 스레드가 예외를 던집니다 fetchOrder() 후자의 누출은 전자의 실행 상태에 대한 정보 나 지식이 없습니다.

  • 스레드가 실행되는 경우 handle()방해가 된 다음 두 스레드가 실행됩니다 findUser() 그리고 fetchOrder() 이 스레드도 누출을 낳은 실에 대한 지식이 없습니다.
  • 만약에 findUser()그 동안 너무 오래 걸렸습니다 fetchOrder() 실패, 실패는 때에 만 식별됩니다 order.get() 호출됩니다.
  • 코드는 하위 작업과 관련된 작업으로 구성되어 있지만,이 관계는 단지 논리적이며 컴파일 시간에 명시 적으로 설명되거나 런타임 동안 시행되지 않습니다.

처음 세 상황은 다른 스레드를 취소하기위한 자동화 된 메커니즘이 없기 때문에 발생합니다. 이것은 잠재적으로 자원 낭비 (스레드가 계속 실행됨), 취소 지연으로 이어질 수 있으며, 최악의 경우 유출 된 스레드도 다른 스레드를 방해 할 수 있습니다. 우리는 수동으로 취소를 처리하려고 시도 할 수 있지만, 제대로 얻는 것은 까다로울뿐만 아니라 전체 프로그램을 복잡하게 만들고 생성합니다. 더 많은 오류 공간.

네 번째 상황은 공식적인 구문이 부족하여 발생하는데, 이는 스레드를 하위 태스크 계층 구조에 대한 작업을 위해 부모-자식 관계에 바인딩합니다. 그만큼 Future 이 경우 제공된 객체도 도움이되지 않습니다.

이러한 모든 한계는 동시성의 구조화되지 않은 특성으로 인한 것입니다. ExecutorService 그리고 Future 작업을 취소하거나 추적하는 자동화 된 방법이 부족합니다.

구조적 동시성

구조화 된 동시성 원리 :

작업이 동시 하위 작업으로 나뉘면 모두 같은 장소, 즉 작업의 코드 블록으로 돌아갑니다.

구조적 동시성에서 작업은 하위 작업의 결과를 기다려 실패를 위해 모니터링합니다. API는 또한 코드 블록을 통한 실행 흐름에 대해 잘 정의 된 입력 및 종료 지점을 정의합니다. API는 또한 코드에서 구문 둥지를 반영하는 방식으로 수명의 수명을 엄격하게 둥지를 시행하는 데 도움이됩니다.

구조적 동시성은 가상 스레드와 자연스러운 시너지 효과가 있습니다. 새로운 가상 스레드는 모든 작업에 전념 할 수 있으며 동시 실행을 위해 하위 작업을 제출하여 작업 팬이 각 하위 작업에 새 가상 스레드를 전용 할 수 있습니다. 또한, 작업-서브 타스크 관계는 각 가상 스레드에 대한 트리 구조가있어 고유 한 부모에 대한 참조를 가지고 있습니다.

가상 스레드는 풍부한 스레드를 제공하지만 구조화 된 동시성은이를 정확하고 강력하게 조정할 수 있습니다. 이를 통해 관측 성 도구는 개발자가 이해할 수있는 스레드를 표시 할 수 있습니다.

구조화 스크로프

구조화 된 동시성 API에서 StructuredTaskScope 주요 클래스입니다.

이전의 예 handle() 기능을 다시 작성합니다 StructuredTaskScope 아래에 나와 있습니다.

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Supplier  user  = scope.fork(() -> findUser());
        Supplier order = scope.fork(() -> fetchOrder());

        scope.join()            // Join both sub-tasks
             .throwIfFailed();  // ... and propagate errors

        // Here, both sub-tasks have succeeded, so compose their results
        return new Response(user.get(), order.get());
    }
}

이 API를 사용하면 지금까지 논의 된 구조화되지 않은 동시성의 단점을 다루는 아래의 모든 것을 달성합니다.

  • 어느 쪽이든 findUser() 또는 fetchOrder() 기타는 자동으로 취소됩니다. 아직 완료되지 않은 경우.
  • 스레드가 실행되는 경우 handle()호출 전 또는 호출 중 중단됩니다 join()서브 타스크 viz. findUser() 또는 fetchOrder() 취소됩니다. 아직 완료되지 않은 경우.
  • 작업 구조와 코드는 풍부한 명확성으로 서로를 반영합니다. 그만큼 scope 작업으로 간주됩니다 (조상) fork 하위 작업 (Subtasks) (어린이들). 작업은 하위 작업이 완료되거나 취소되기를 기다리고 수명주기 관리의 오버 헤드없이 성공하거나 실패하기로 결정합니다.
  • 하위 작업에 대한 작업의 계층 구조를 통해 얻은 추가 혜택은 관측 성이 크게 향상됩니다. 통화 스택 또는 스레드 덤프는 handle() 에게 findUser() 그리고 fetchOrder() 개발자가 쉽게 이해할 수 있습니다.

자동 취소/청소를 통해 ShutdownOnFailure 정책, 나사산 누출은 모두 피합니다. 이 정책은 사용자 정의 또는 다른 가용 정책으로 대체 될 수 있습니다.

아래는 몇 가지 중요한 특성입니다 StructuredTaskScope 개발자는 알고 있어야합니다

  1. JDK 24 기준으로, 이것은 여전히 ​​미리보기 기능이므로 기본적으로 비활성화됩니다.
  2. 범위를 만드는 스레드는 그것입니다 소유자.
  3. 모든 호출과 함께 fork(…) 기본적으로 하위 작업을 실행하기 위해 새로운 가상 스레드가 시작되었습니다.
  4. 하위 작업은 자체 중첩을 만들 수 있습니다 StructuredTaskScope 자체 하위 작업을 포크하여 계층 구조를 만듭니다. 범위가 닫히면 모든 하위 작업이 종료되도록 보장되므로 스레드가 누출되지 않도록합니다.
  5. 스코프 소유자 또는 (중첩) 하위 작업을 호출 할 수 있습니다. shutdown()범위. 따라서 다른 미완성 하위 작업의 취소를 시작합니다. 또한, 새로운 하위 작업의 포킹도 방지됩니다.
  6. 만약에 shutdown() 또는 fork()스코프 계층 구조의 일부가 아닌 스레드에 의해 호출됩니다. WrongThreadException 던져졌다.
  7. 전화 join() 또는 joinUntil(Instant) 범위 내에서 필수적인. 결합하기 전에 스코프의 블록이 종료되면 스코프는 모든 하위 작업이 종료되고 예외를 던질 때까지 기다립니다.
  8. 언제 join() 성공적으로 완료 한 다음 각 하위 작업이 성공적으로 완료되었거나 실패하거나 취소되었습니다.
  9. StructuredTaskScope 동시 작업시 구조와 순서를 시행합니다. 따라서 a 이외의 범위를 사용합니다 try-with-resources 전화하지 않고 막히고 돌아옵니다 close()또는 적절한 둥지를 유지하지 않고 close() 전화, 범위의 방법이 a를 던질 수 있습니다 StructureViolationException.

종료 정책

동시 서브 타스크 실행 중 불필요한 처리를 피하려면 사용하는 것이 일반적입니다. 단락 패턴. 구조적 동시성 종료에서 이러한 요구 사항에 정책을 활용할 수 있습니다.

두 개의 서브 클래스 StructuredTaskScope,,, ShutdownOnFailure 그리고 ShutdownOnSuccess첫 번째 하위 작업이 각각 실패하거나 성공할 때 범위를 종료하는 정책으로 이러한 패턴을 지원하십시오.

이전 예제는 이미 사용법을 보여줍니다 ShutdownOnFailure 아래 예제는 사용을 보여줍니다 ShutdownOnSuccess 첫 번째 성공적인 하위 작업의 결과를 반환합니다. 실제 예제는 여러 서버의 쿼리를 위해 모든 서버에서 첫 번째 응답의 응답 및 반환 결과를 쿼리하는 것입니다.꼬리 도마).

 T race(List> tasks, Instant deadline) 
        throws InterruptedException, ExecutionException, TimeoutException {
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
        for (var task : tasks) {
            scope.fork(task);
        }
        return scope.joinUntil(deadline)
                    .result();  // Throws if none of the sub-tasks completed successfully
    }
}

하나의 하위 작업이 성공 하자마자이 스코프가 자동으로 종료되어 미완성 하위 작업을 취소합니다. 모든 하위 작업이 실패하거나 주어진 마감일이 경과하면 작업이 실패합니다.

이 두 종료 정책은 상자 밖에서 제공되지만 요구 사항에 따라 사용자 정의 할 수 있습니다.

처리 결과

종료 정책은 예외를 처리하기위한 중앙 집중식 방법 및 성공적인 결과를 추가로 제공합니다. 이것은 전체 범위가 단위로 취급되는 구조적 동시성의 정신과 일치합니다.

스코프의 소유자는 Subtask 객체는 전화에서 반환되었습니다 fork(...) 정책에 의해 처리되지 않은 경우.

종료 정책 자체가 하위 작업 결과를 처리하는 경우 ( ShutdownOnSuccess – 그런 다음 Subtask 물체가 반환되었습니다 fork(...) 완전히 피해야합니다 fork(...) 방법이 반환되는 것처럼 취급되었습니다 void. 하위 작업은 그 결과 정책에 따라 중앙 집중식 예외 처리 후 스코프 소유자가 처리 해야하는 정보를 반환해야합니다.

다양한 프로그래밍 언어의 구조적 동시성

구조적 동시성은 여전히 ​​Java에서 미리보기 중이지만 이미 다양한 프로그래밍 언어로 제공됩니다. 다음은 이러한 언어에 대한 몰래 피크입니다.

코 틀린

  • 코 루틴. Kotlin은 스레드를 차단하지 않고 비동기 프로그래밍을 허용하는 가벼운 동시성 모델 인 코 루틴을 제공합니다. 코 루틴은 스코프를 통해 구조화 된 동시성을 제공하여 필요할 때 작업이 올바르게 관리되고 취소되도록합니다.
  • 구조적 동시성. Kotlin의 구조적 동시성은 코 루틴 프레임 워크에 내장되어있어 효율적이고 이해하기 쉬운 동시 코드를 쉽게 작성할 수 있습니다.

가다

  • goroutines. Go는 GO 런타임이 관리하는 경량 스레드 인 Goroutines를 사용합니다. 고 루틴은 쉽게 만들고 관리 할 수있어 동시성이 높을 수 있습니다.
  • 채널. GO는 고루틴 간의 통신을위한 채널을 제공하여 작업이 효과적으로 통신하고 동기화 할 수 있도록 구조적 동시성을 가능하게합니다.

파이썬

  • 비시오. Python의 Asyncio Library는 코 루틴을 사용한 비동기 프로그래밍을 지원합니다. Asyncio는 작업 및 이벤트 루프를 사용하여 구조적 동시성을 허용하여 작업이 필요할 때 올바르게 관리되고 취소되도록합니다.
  • 작업 그룹. Python의 Asyncio 라이브러리에는 작업 그룹이 포함되어있어 작업 그룹을 함께 관리하고 취소하는 방법을 제공하여 작업이 올바르게 조정되도록합니다.

기음#

  • 비동기/대기. C#은 비동기 및 비동기 프로그래밍을 지원하며 키워드를 기다립니다. 이를 통해 작업이 필요할 때 올바르게 관리되고 취소되도록하여 구조적 동시성이 가능합니다.
  • 작업 병렬 라이브러리 (TPL). TPL은 C#의 병렬 프로그래밍을 지원하여 구조화 된 방식으로 작업의 생성 및 관리를 허용합니다.

결론

결론적으로, Java의 구조적 동시성은 동시 프로그래밍에서 중요한 진화를 나타내며, 전통적인 구조화되지 않은 동시성 모델에서 발견 된 많은 단점을 다루고 있습니다.

Java의 구조적 동시성의 주요 장점은 다음과 같습니다.

  • 자동 취소. 실패시 하위 작업은 자동으로 취소되어 자원 낭비가 줄어들고 수동 취소 처리의 복잡성을 제거합니다.
  • 명확한 작업 계층. 계층 적 작업 구조는 코드 가독성, 유지 관리 및 관찰 가능성을 향상시켜 동시 작업을보다 쉽게 ​​디버깅하고 모니터링 할 수 있도록합니다.
  • 개선 된 오류 처리. 구조화 된 동시성을 통한 중앙 집중식 오류 처리는 예외가있을 때 예측 가능하고 강력한 행동을 보장합니다.
  • 향상된 관찰 가능성. 스레드 덤프 및 콜 스택에 표시되는 명확한 부모-자식 관계는 개발자가 동시 작업을 이해하고 관리하는 데 도움이됩니다.
  • 가상 스레드. 가상 스레드와의 시너지 효과는 효율적이고 확장 가능한 동시 프로그래밍을 가능하게하여 전통적인 스레드의 오버 헤드없이 많은 동시 작업을 처리 할 수 ​​있습니다.

구조적 동시성을 채택함으로써 Java 개발자는보다 효율적이고 신뢰할 수 있으며 유지 관리 가능한 동시 코드를 작성하여 궁극적으로 더 나은 소프트웨어 품질과 개발자 생산성을 향상시킬 수 있습니다.

참조 및 추가 읽기

출처 참조

Post Comment

당신은 놓쳤을 수도 있습니다