Concurrency Programming Guide

Groot·2022년 6월 27일
0

TIL

목록 보기
29/153
post-thumbnail
post-custom-banner

TIL

🌱 난 오늘 무엇을 공부했을까?

📌 Concurrency Programming Guide

  • 동시성은 동시에 여러 일이 발생하는 개념입니다.
  • 애플리케이션에 동시성을 도입하려면 하나 이상의 추가 스레드를 생성해야 합니다.
  • 스레드는 수동으로 관리해야 하는 저수준 도구이다. 하지만, OS X과 iOS는 모두 스레드 기반 시스템 및 애플리케이션에서는 시시스템에 스레드를 관리한다.
    • 과거
      - 애플리케이션에 대한 최적의 스레드 수가 현재 시스템 부하와 기본 하드웨어에 따라 동적으로 변경될 수 있다는 점을 감안할 때, 올바른 스레딩 솔루션을 구현하는 것은 불가능하지는 않더라도 극도로 어려워집니다.
      - 또한 일반적으로 스레드와 함께 사용되는 동기화 메커니즘은 성능 향상을 보장하지 않고 소프트웨어 설계에 복잡성과 위험을 추가합니다.
      - 현재
      - 응용 프로그램은 스레드를 직접 생성하는 대신 특정 작업을 정의하고 수행은 시스템에서 하도록 하면 됩니다.
      - 시스템이 스레드를 관리하도록 함으로써 응용 프로그램은 예전 스레드에서는 불가능한 수준의 확장성을 얻습니다.

      thread 라는 용어 는 코드에 대한 별도의 실행 경로를 나타내는 데 사용
      process 라는 용어 는 여러 스레드를 포함할 수 있는 실행 중인 실행 파일을 나타내는 데 사용
      task 이라는 용어 는 수행해야 하는 작업의 추상적 개념을 나타내는 데 사용

📍 Concurrency and Application Design

  • 컴퓨팅 초기에는 컴퓨터가 수행할 수 있는 단위 시간당 최대 작업량이 CPU의 클럭 속도에 의해 결정되었습니다. 그러나 기술이 발전하고 프로세서 설계가 더 컴팩트해짐에 따라 열 및 기타 물리적 제약이 프로세서의 최대 클록 속도를 제한하기 시작했습니다
  • 칩의 전체 성능을 높일 수 있는 방법을 찾음 -> 코어 수를 늘리면 CPU 속도를 높이거나 칩 크기 또는 열 특성을 변경하지 않고도 단일 칩에서 초당 더 많은 명령을 실행할 수 있습니다

📍 The Move Away from Threads

  • 스레드에 의존하는 대신 OS X 및 iOS는 비동기식 설계 접근 방식 을 취합니다.

  • 작업을 비동기적으로 시작하는 기술 중 하나는 GCD(Grand Central Dispatch) 입니다. 이 기술은 일반적으로 자체 응용 프로그램에서 작성하는 스레드 관리 코드를 가져와서 해당 코드를 시스템 수준으로 이동합니다. 실행하려는 작업을 정의하고 적절한 디스패치 대기열에 추가하기만 하면 됩니다.

  • GCD는 필요한 스레드를 만들고 해당 스레드에서 실행되도록 작업을 예약합니다. 스레드 관리가 이제 시스템의 일부이기 때문에 GCD는 작업 관리 및 실행에 대한 전체적인 접근 방식을 제공하여 기존 스레드보다 더 나은 효율성을 제공합니다.

  • Operation Queues

    • 직렬 디스패치 대기열은 한 번에 하나의 작업만 실행하고 대기열에서 제거하고 작업을 시작하기 전에 해당 작업이 완료될 때까지 기다립니다.
  • Dispatch Queues

    • 디스패치 큐 는 작업을 순차적으로 또는 동시에 실행하지만 항상 선입선출 순서로 실행합니다. (즉, 디스패치 대기열은 항상 대기열에 추가된 것과 동일한 순서로 작업을 대기열에서 빼고 시작합니다.)
    • 디스패치 큐에는 다음과 같은 다른 이점이 있습니다.
  • Dispatch Sources

  • 디스패치 소스는 특정 유형의 시스템 이벤트를 비동기적으로 처리하기 위한 C 기반 메커니즘입니다. 디스패치 소스는 특정 유형의 시스템 이벤트에 대한 정보를 캡슐화하고 해당 이벤트가 발생할 때마다 특정 블록 개체 또는 기능을 디스패치 대기열에 제출합니다. 디스패치 소스를 사용하여 다음 유형의 시스템 이벤트를 모니터링할 수 있습니다.

📍 Asynchronous Design Techniques

  • 동시성은 기본 스레드가 사용자 이벤트에 자유롭게 응답할 수 있도록 하여 코드의 응답성을 향상시킬 수 있습니다.
  • 더 많은 코어를 활용하여 같은 시간에 더 많은 작업을 수행함으로써 코드의 효율성을 향상시킬 수도 있습니다.
  • 그러나 또한 오버헤드가 추가되고 코드의 전반적인 복잡성이 증가하여 코드를 작성하고 디버그하기가 더 어려워집니다.

🔗 올바른 선택을 하는 데 도움이 되는 몇 가지 지침

1. Define Your Application’s Expected Behavior

애플리케이션에 동시성을 추가하는 것에 대해 생각하기 전에 항상 애플리케이션의 올바른 동작이라고 생각하는 것을 정의하는 것으로 시작해야 합니다. 애플리케이션의 예상 동작을 이해하면 나중에 디자인을 검증할 수 있습니다. 또한 동시성을 도입하여 얻을 수 있는 예상 성능 이점에 대한 아이디어도 제공해야 합니다.

가장 먼저 해야 할 일은 애플리케이션이 수행하는 작업과 각 작업과 관련된 개체 또는 데이터 구조를 열거하는 것입니다. 처음에는 사용자가 메뉴 항목을 선택하거나 버튼을 클릭할 때 수행되는 작업으로 시작할 수 있습니다. 이러한 작업은 별개의 동작을 제공하며 시작점과 끝점이 잘 정의되어 있습니다. 또한 타이머 기반 작업과 같이 응용 프로그램이 사용자 상호 작용 없이 수행할 수 있는 다른 유형의 작업도 열거해야 합니다.

상위 수준 작업 목록을 얻은 후에는 각 작업을 작업을 성공적으로 완료하기 위해 수행해야 하는 단계 집합으로 더 세분화하기 시작합니다. 이 수준에서는 데이터 구조 및 개체에 대해 수행해야 하는 수정 사항과 이러한 수정 사항이 응용 프로그램의 전체 상태에 미치는 영향에 주로 관심을 기울여야 합니다. 또한 개체와 데이터 구조 간의 종속성을 기록해야 합니다. 예를 들어, 작업이 객체 배열에 대해 동일한 변경을 수행하는 경우, 한 객체에 대한 변경이 다른 객체에 영향을 미치는지 여부를 주목할 가치가 있습니다. 개체를 서로 독립적으로 수정할 수 있는 경우 해당 수정을 동시에 수행할 수 있습니다.

2. Factor Out Executable Units of Work

애플리케이션의 작업에 대한 이해를 바탕으로 코드가 동시성을 통해 이점을 얻을 수 있는 위치를 이미 식별할 수 있어야 합니다. 작업에서 하나 이상의 단계 순서를 변경하면 결과가 변경되는 경우 해당 단계를 연속적으로 계속 수행해야 할 수 있습니다. 그러나 순서를 변경해도 출력에 영향을 주지 않는다면 해당 단계를 동시에 수행하는 것을 고려해야 합니다. 두 경우 모두 수행할 단계를 나타내는 실행 가능한 작업 단위를 정의합니다. 그런 다음 이 작업 단위는 블록 이나 작업 개체를 사용하여 캡슐화하고 적절한 대기열에 전달하는 것이 됩니다.

식별한 각 실행 가능한 작업 단위에 대해 최소한 처음에는 수행되는 작업의 양에 대해 너무 걱정하지 마십시오. 스레드를 회전시키는 데는 항상 비용이 있지만 디스패치 큐와 작업 큐의 장점 중 하나는 많은 경우 이러한 비용이 기존 스레드보다 훨씬 적다는 것입니다. 따라서 스레드를 사용하는 것보다 대기열을 사용하여 더 작은 작업 단위를 더 효율적으로 실행할 수 있습니다. 물론 항상 실제 성과를 측정하고 필요에 따라 작업 크기를 조정해야 하지만 처음에는 작업이 너무 작게 간주되어서는 안 됩니다

3. Identify the Queues You Need

작업이 개별 작업 단위로 분할되고 블록 개체 또는 작업 개체를 사용하여 캡슐화되었으므로 해당 코드를 실행하는 데 사용할 대기열을 정의해야 합니다. 주어진 작업에 대해 생성한 블록 또는 작업 개체와 작업을 올바르게 수행하기 위해 실행해야 하는 순서를 검사합니다.

블록을 사용하여 작업을 구현한 경우 직렬 또는 동시 디스패치 대기열에 블록을 추가할 수 있습니다. 특정 주문이 필요한 경우 항상 직렬 디스패치 대기열에 블록을 추가합니다. 특정 주문이 필요하지 않은 경우 필요에 따라 동시 디스패치 대기열에 블록을 추가하거나 여러 다른 디스패치 대기열에 추가할 수 있습니다.

작업 개체를 사용하여 작업을 구현한 경우 대기열 선택이 개체 구성보다 덜 흥미로운 경우가 많습니다. 연산 개체를 순차적으로 수행하려면 관련 개체 간의 종속성을 구성해야 합니다. 종속성은 종속된 개체가 작업을 완료할 때까지 한 작업이 실행되지 않도록 합니다.

Tips for Improving Efficiency
  • 단순히 코드를 더 작은 작업으로 분해하고 대기열에 추가하는 것 외에도 대기열을 사용하여 코드의 전체 효율성을 향상시키는 다른 방법이 있습니다.
    • Consider computing values directly within your task if memory usage is a factor. 응용 프로그램이 이미 메모리 바인딩된 경우 지금 직접 값을 계산하는 것이 기본 메모리에서 캐시된 값을 로드하는 것보다 빠를 수 있습니다. 값을 계산하는 것은 주 메모리보다 훨씬 빠른 주어진 프로세서 코어의 레지스터와 캐시를 직접 사용합니다. 물론 테스트 결과 이것이 성능상의 이점으로 나타난 경우에만 이 작업을 수행해야 합니다.
    • Identify serial tasks early and do what you can to make them more concurrent. 작업이 일부 공유 리소스에 의존하기 때문에 직렬로 실행되어야 하는 경우 아키텍처를 변경하여 해당 공유 리소스를 제거하는 것을 고려하십시오. 리소스가 필요한 각 클라이언트에 대해 리소스 복사본을 만들거나 리소스를 모두 제거하는 것을 고려할 수 있습니다.
    • Avoid using locks. 디스패치 큐 및 작업 큐에서 제공하는 지원으로 인해 대부분의 상황에서 잠금이 필요하지 않습니다. 일부 공유 리소스를 보호하기 위해 잠금을 사용하는 대신 직렬 대기열을 지정하거나 작업 개체 종속성을 사용하여 올바른 순서로 작업을 실행합니다.
    • Rely on the system frameworks whenever possible. 동시성을 달성하는 가장 좋은 방법은 시스템 프레임워크 에서 제공하는 기본 제공 동시성을 활용하는 것입니다 . 많은 프레임워크는 내부적으로 스레드 및 기타 기술을 사용하여 동시 동작을 구현합니다. 작업을 정의할 때 기존 프레임워크가 원하는 작업을 정확히 수행하고 동시에 수행하는 기능이나 메서드를 정의하는지 확인하십시오. 해당 API를 사용하면 노력을 절약할 수 있고 가능한 최대 동시성을 제공할 가능성이 더 큽니다.

📍 Performance Implications

  • 더 많은 코드를 동시에 더 쉽게 실행할 수 있도록 작업 대기열, 디스패치 대기열 및 디스패치 소스가 제공됩니다. 그러나 이러한 기술이 애플리케이션의 효율성이나 응답성을 향상시키는 것은 아닙니다. 귀하의 요구에 효과적이면서 애플리케이션의 다른 리소스에 과도한 부담을 주지 않는 방식으로 큐를 사용하는 것은 여전히 귀하의 책임입니다. 예를 들어 10,000개의 작업 개체를 만들어 작업 대기열에 제출할 수 있지만 그렇게 하면 애플리케이션이 잠재적으로 중요하지 않은 양의 메모리를 할당하게 되어 페이징 및 성능 저하로 이어질 수 있습니다.
  • 대기열을 사용하든 스레드를 사용하든 상관없이 코드에 동시성을 도입하기 전에 항상 애플리케이션의 현재 성능을 반영하는 기준 메트릭 세트를 수집해야 합니다. 변경 사항을 도입한 후에는 추가 메트릭을 수집하고 기준선과 비교하여 애플리케이션의 전반적인 효율성이 개선되었는지 확인해야 합니다. 동시성의 도입으로 인해 애플리케이션의 효율성이 떨어지거나 응답성이 떨어지는 경우 사용 가능한 성능 도구를 사용하여 잠재적 원인을 확인해야 합니다.

📍 Concurrency and Other

코드를 모듈식 작업으로 분해하는 것은 애플리케이션에서 동시성을 시도하고 개선하는 가장 좋은 방법입니다. 그러나 이 설계 접근 방식은 모든 경우에 모든 응용 프로그램의 요구 사항을 충족하지 않을 수 있습니다. 작업에 따라 응용 프로그램의 전체 동시성을 추가로 개선할 수 있는 다른 옵션이 있을 수 있습니다. 이 섹션에서는 디자인의 일부로 사용할 다른 기술에 대해 간략히 설명합니다.

🔗 OpenCL and Concurrency

OS X에서 OpenCL(Open Computing Language) 은 컴퓨터의 그래픽 프로세서에서 범용 계산을 수행하기 위한 표준 기반 기술입니다. OpenCL은 대규모 데이터 세트에 적용하려는 잘 정의된 계산 세트가 있는 경우 사용하기에 좋은 기술입니다. 예를 들어 OpenCL을 사용하여 이미지의 픽셀에 대한 필터 계산을 수행하거나 한 번에 여러 값에 대한 복잡한 수학 계산을 수행하는 데 사용할 수 있습니다. 즉, OpenCL은 데이터를 병렬로 조작할 수 있는 문제 세트에 더 적합합니다.

OpenCL은 대규모 데이터 병렬 작업을 수행하는 데 적합하지만 보다 범용적인 계산에는 적합하지 않습니다. GPU에서 작동할 수 있도록 데이터와 필요한 작업 커널을 모두 준비하고 그래픽 카드로 전송하는 데는 적지 않은 노력이 필요합니다. 마찬가지로 OpenCL에 의해 생성된 결과를 검색하는 데도 적지 않은 노력이 필요합니다. 결과적으로 시스템과 상호 작용하는 모든 작업은 일반적으로 OpenCL과 함께 사용하지 않는 것이 좋습니다. 예를 들어 OpenCL을 사용하여 파일이나 네트워크 스트림의 데이터를 처리하지 않습니다. 대신 OpenCL을 사용하여 수행하는 작업은 그래픽 프로세서로 전송되고 독립적으로 계산될 수 있도록 훨씬 더 독립적이어야 합니다.

🔗 When to Use Threads

작업 대기열과 디스패치 대기열이 작업을 동시에 수행하는 데 선호되는 방법이지만 만병 통치약은 아닙니다. 응용 프로그램에 따라 사용자 지정 스레드를 만들어야 하는 경우가 있을 수 있습니다. 사용자 정의 스레드를 생성하는 경우 가능한 한 적은 수의 스레드를 직접 생성하도록 노력해야 하며 다른 방법으로 구현할 수 없는 특정 작업에만 해당 스레드를 사용해야 합니다.

스레드는 여전히 실시간으로 실행되어야 하는 코드를 구현하는 좋은 방법입니다. 디스패치 큐는 가능한 한 빨리 작업을 실행하기 위해 모든 시도를 하지만 실시간 제약을 해결하지는 않습니다. 백그라운드에서 실행되는 코드에서 더 예측 가능한 동작이 필요한 경우 스레드가 여전히 더 나은 대안을 제공할 수 있습니다.

스레드 프로그래밍과 마찬가지로 항상 스레드를 신중하게 사용해야 하며 절대적으로 필요한 경우에만 사용해야 합니다.

참고

profile
I Am Groot
post-custom-banner

0개의 댓글