왜 코 루틴이 돌아 왔나요? [닫은]


19

코 루틴에 대한 기초는 대부분 60 년대 / 70 년대에 일어 났으며 대안 (예 : 실)을 위해 중단되었습니다.

파이썬과 다른 언어로 발생하고있는 코 루틴에 대한 관심이 새로워 질만한 물질이 있습니까?



9
나는 그들이 떠난 것을 확신하지 못한다.
Blrfl

답변:


26

코 루틴은 떠나지 않았으며, 그 동안 다른 것들에 의해 가려졌습니다. 최근 비동기 프로그래밍과 코 루틴에 대한 관심이 증가한 것은 크게 세 가지 요소, 즉 함수형 프로그래밍 기술에 대한 수용 증가, 진정한 병렬 처리 (JavaScript! Python!)에 대한 지원이 부족한 도구 세트 및 가장 중요한 요소 인 스레드와 코 루틴 간의 다른 트레이드 오프 때문입니다. 일부 사용 사례의 경우 코 루틴이 객관적으로 좋습니다.

80 년대, 90 년대, 그리고 오늘날 가장 큰 프로그래밍 패러다임 중 하나는 OOP입니다. OOP의 역사와 특히 Simula 언어의 개발을 살펴보면 클래스가 코 루틴에서 진화 한 것을 볼 수 있습니다. 시뮬라는 개별 이벤트가있는 시스템의 시뮬레이션을 위해 고안되었습니다. 시스템의 각 요소는 하나의 시뮬레이션 단계 동안 이벤트에 응답하여 실행 된 다음 다른 프로세스가 작업을 수행하도록하는 별도의 프로세스였습니다. Simula 67을 개발하는 동안 수업 컨셉이 소개되었습니다. 이제 코 루틴의 지속적 상태가 객체 멤버에 저장되고 이벤트는 메소드를 호출하여 트리거됩니다. 자세한 내용 은 Nygaard & Dahl 의 SIMULA 언어 개발 논문을 참조하십시오 .

그래서 우리는 재미난 방식으로 코 루틴을 계속 사용했습니다. 우리는 그것들을 객체와 이벤트 중심 프로그래밍이라고 부릅니다.

병렬 처리와 관련하여 적절한 메모리 모델이있는 언어와 그렇지 않은 언어의 두 가지 언어가 있습니다. 메모리 모델은“변수에 쓰고 다른 스레드에서 해당 변수를 읽은 후에 이전 값이나 새 값 또는 유효하지 않은 값을 볼 수 있습니까? '전'과 '후'는 무엇을 의미합니까? 어떤 작업이 원 자성으로 보장됩니까?”

좋은 메모리 모델을 만드는 것은 어렵 기 때문에 이러한 노력은 Perl, JavaScript, Python, Ruby, PHP와 같이 지정되지 않은 구현 정의 동적 오픈 소스 언어의 대부분에 대해 수행 된 적이 없습니다. 물론 모든 언어는 원래 작성된 "스크립트"를 훨씬 뛰어 넘었습니다. 이러한 언어 중 일부에는 일종의 메모리 모델 문서가 있지만 충분하지 않습니다. 대신 해킹이 있습니다.

  • Perl은 스레딩 지원으로 컴파일 할 수 있지만 각 스레드에는 완전한 인터프리터 상태의 개별 복제본이 포함되어있어 스레드가 엄청나게 비쌉니다. 유일한 이점으로서,이 무 공유 접근 방식은 데이터 경쟁을 피하고 프로그래머가 대기열 / 신호 / IPC를 통해서만 통신하도록합니다. Perl은 비동기 처리에 대한 강력한 이야기가 없습니다.

  • JavaScript는 항상 함수형 프로그래밍을 풍부하게 지원하므로 프로그래머는 비동기 작업이 필요한 프로그램에서 연속체 / 콜백을 수동으로 인코딩합니다. 예를 들어 Ajax 요청 또는 애니메이션 지연이 있습니다. 웹은 본질적으로 비동기이기 때문에 많은 비동기 JavaScript 코드가 있으며 이러한 모든 콜백을 관리하는 것은 매우 고통 스럽습니다. 따라서 이러한 콜백을 더 잘 조직하거나 (약속) 완전히 제거하려는 많은 노력이 있습니다.

  • 파이썬에는 Global Interpreter Lock이라는 불행한 기능이 있습니다. 기본적으로 파이썬 메모리 모델은“병렬화가 없기 때문에 모든 효과가 순차적으로 나타납니다. 한 번에 하나의 스레드 만 Python 코드를 실행합니다.”따라서 Python에는 스레드가 있지만 코 루틴만큼 강력합니다. [1] 파이썬은로 제너레이터 함수를 통해 많은 코 루틴을 인코딩 할 수 있습니다 yield. 올바르게 사용하면 JavaScript로 알려진 대부분의 콜백 지옥을 피할 수 있습니다. Python 3.5의 최신 비동기 / 대기 시스템은 Python에서 비동기 관용구를 더 편리하게 만들고 이벤트 루프를 통합합니다.

    [1] : 기술적으로 이러한 제한은 Python 참조 구현 인 CPython에만 적용됩니다. 자이 썬 (Jython)과 같은 다른 구현은 병렬로 실행할 수있는 실제 스레드를 제공하지만 동등한 동작을 구현하려면 많은 시간이 소요된다. 기본적으로 모든 변수 또는 객체 멤버는 휘발성 변수이므로 모든 변경 사항이 원자 적이며 모든 스레드에서 즉시 볼 수 있습니다. 물론 휘발성 변수를 사용하는 것이 일반 변수를 사용하는 것보다 훨씬 비쌉니다.

  • 나는 루비와 PHP에 대해 제대로 구울만큼 충분하지 않습니다.

요약하자면, 이러한 언어 중 일부는 멀티 스레딩을 바람직하지 않거나 불가능하게 만드는 기본 설계 결정을 가지고있어 코 루틴과 같은 대안과 비동기 프로그래밍을보다 편리하게 만드는 방법에 더 중점을 둡니다.

마지막으로 코 루틴과 스레드의 차이점에 대해 이야기하겠습니다.

프로세스 내부의 여러 스레드가 메모리 공간을 공유한다는 점을 제외하면 스레드는 기본적으로 프로세스와 같습니다. 이는 스레드가 메모리 측면에서 결코 "경량"이 아님을 의미합니다. 스레드는 운영 체제에 의해 사전 예약됩니다. 이는 작업 스위치의 오버 헤드가 높고 불편한 시간에 발생할 수 있음을 의미합니다. 이 오버 헤드에는 스레드 상태를 일시 중단하는 비용과 사용자 모드 (스레드의 경우)와 커널 모드 (스케줄러의 경우) 사이의 전환 비용이 있습니다.

프로세스가 자체 스레드를 직접 협력 적으로 예약하는 경우 컨텍스트를 커널 모드로 전환 할 필요가 없으며 간접 작업 호출에 비해 작업을 전환하는 비용이 상당히 저렴합니다. 이러한 경량 실은 다양한 세부 사항에 따라 녹색 실, 섬유 또는 코 루틴이라고 할 수 있습니다. 녹색 스레드 / 섬유의 주목할만한 사용자는 초기 Java 구현이었으며, 최근에는 Golang의 Goroutines였습니다. 코 루틴의 개념적 장점은 코 루틴 사이에서 명시 적으로 앞뒤로 전달되는 제어 흐름의 측면에서 실행을 이해할 수 있다는 것입니다. 그러나 이러한 코 루틴은 여러 OS 스레드에서 예약되지 않으면 진정한 병렬 처리를 달성 할 수 없습니다.

싼 코 루틴은 어디에 유용합니까? 대부분의 소프트웨어에는 gazillion 스레드가 필요하지 않으므로 일반적으로 비싼 스레드는 정상입니다. 그러나 비동기 프로그래밍은 때때로 코드를 단순화 할 수 있습니다. 자유롭게 사용 되려면이 추상화가 충분히 저렴해야합니다.

그리고 웹이 있습니다. 위에서 언급했듯이 웹은 본질적으로 비동기 적입니다. 네트워크 요청은 단순히 오랜 시간이 걸립니다. 많은 웹 서버가 작업자 스레드로 가득 찬 스레드 풀을 유지 관리합니다. 그러나 디스크에서 파일을로드 할 때 I / O 이벤트를 대기하거나 클라이언트가 응답의 일부를 승인 할 때까지 또는 데이터베이스까지 대기하기 때문에 일부 스레드를 대기하기 때문에 이러한 스레드는 대부분 유휴 상태입니다. 쿼리가 완료됩니다. NodeJS는 결과적으로 이벤트 기반 및 비동기 서버 디자인이 매우 잘 작동한다는 것을 놀랍게 입증했습니다. 분명히 JavaScript는 웹 응용 프로그램에 사용되는 유일한 언어와는 거리가 멀기 때문에 비동기 웹 프로그래밍을보다 쉽게하기 위해 다른 언어 (Python 및 C #에서 주목할만한)에 대한 큰 인센티브도 있습니다.


표절 위험을 피하기 위해 네 번째 단락부터 마지막 ​​단락까지 소싱하는 것이 좋습니다. 필자가 읽은 다른 출처와 거의 동일합니다. 또한 스레드보다 수십 배 작은 오버 헤드가 있지만 코 루틴 성능을 "간접 함수 호출"로 단순화 할 수 없습니다. 코 루틴 구현에 대한 자세한 내용은 여기여기를 참조하십시오 .
whn

1
@snb 제안 된 편집과 관련하여 : GIL은 CPython 구현 세부 사항 일 수 있지만 근본적인 문제는 Python 언어 에 데이터의 병렬 돌연변이를 지정하는 명시 적 메모리 모델이 없다는 것입니다. GIL은 이러한 문제를 회피하기위한 해킹입니다. 그러나 진정한 병렬 처리를 사용하는 Python 구현은 예를 들어 Jython 책 에서 논의 된 것과 동일한 의미를 제공하기 위해 많은 시간을 거쳐야합니다 . 기본적으로 모든 변수 또는 객체 필드는 값 비싼 휘발성 변수 여야합니다 .
amon

3
@snb 표절에 관하여 : 표절은 특히 학문적 맥락에서 아이디어를 자신의 것으로 잘못 제시합니다. 그것은 심각한 혐의 이지만, 당신이 그런 말을하지 않았다고 확신합니다. "스레드는 기본적으로 프로세스와 같습니다"단락은 운영 체제에 대한 강의 나 교과서에서 가르치는 잘 알려진 사실을 반복합니다. 이러한 사실을 간결하게 표현할 수있는 방법은 매우 많기 때문에이 단락이 여러분에게 친숙하게 들리는 것은 놀랍지 않습니다.
amon

파이썬을 의미하는 의미를 바꾸지 않았습니다. 메모리 모델이있다. 또한 휘발성을 사용 한다고해서 성능이 저하되는 것은 아닙니다. 휘발성은 컴파일러가 현재 컨텍스트에서 명시적인 연산없이 변수가 변경되지 않는다고 가정 할 수있는 방식으로 변수를 최적화 할 수 없음을 의미합니다. Jython 세계에서는 VM JIT 컴파일을 사용하기 때문에 실제로 중요 할 수 있지만 CPython 세계에서는 JIT 최적화에 대해 걱정하지 않아도 휘발성 변수는 인터프리터 런타임 공간에 존재하므로 최적화 할 수 없습니다. .
whn

7

코 루틴은 운영 체제가 선제 적 스케줄링을 수행하지 않았기 때문에 유용했습니다 . 그들이 선제 적 스케줄링을 제공하기 시작하면, 프로그램에서 주기적으로 제어권을 포기해야했습니다.

멀티 코어 프로세서가 널리 보급됨에 따라 코 루틴은 작업 병렬 처리 를 달성 하고 시스템 활용도를 높이기 위해 사용됩니다 (한 실행 스레드가 리소스를 기다려야하는 경우 다른 스레드가 대신 실행될 수 있음).

NodeJS는 코 루틴을 사용하여 IO에 병렬로 액세스하는 특수한 경우입니다. 즉, 여러 스레드가 IO 요청을 처리하는 데 사용되지만 단일 스레드가 자바 스크립트 코드를 실행하는 데 사용됩니다. signle 스레드에서 사용자 코드를 실행하는 목적은 뮤텍스를 사용할 필요가 없도록하는 것입니다. 이것은 위에서 언급 한 것처럼 시스템 활용률을 높이려고하는 범주에 속합니다.


4
그러나 코 루틴은 OS 관리되지 않습니다. OS는 코 루틴은 C ++ 섬유와는 달리 무엇인지 모르는
overexchange가

많은 OS에는 코 루틴이 있습니다.
Jörg W Mittag

파이썬 및 Javascript ES6 +와 같은 코 루틴은 다중 프로세스가 아닙니다. 그것들은 어떻게 작업 병렬 처리를 달성합니까?
whn

1
@Mael 최근 코 루틴의 "부흥"은 파이썬과 자바 스크립트에서 나왔는데, 둘 다 내가 이해하는 것처럼 코 루틴과 병렬화를 달성하지 못합니다. 즉, 작업 병렬화는 코 루틴이 전혀 "돌아 오는"이유가 아니기 때문에이 답변이 틀렸다는 말입니다. 또한 Luas도 다중 프로세스가 아닌가? 편집 : 나는 당신이 병렬 처리에 대해 이야기하고 있지 않다는 것을 깨달았지만 왜 처음에 나에게 답장을 했습니까? 분명히 이것에 대해 잘못했기 때문에 dlasalle에 회신하십시오.
whn

3
@dlasalle 아니요 "병렬에서 실행"이라는 말에도 불구하고 코드가 물리적으로 동시에 실행되는 것은 아닙니다. GIL은 중지하고 비동기는 CPython (다중 GIL)의 다중 처리에 필요한 별도 프로세스를 생성하지 않습니다. 비동기는 단일 스레드에서 수율로 작동합니다. 그들은 "parralel"다른 기능 작업에 yeilding하고 실제로 평균 몇 가지 기능 말할 때 interleving 기능을 실행합니다. impl 때문에 Python 비동기 프로세스 를 병렬로 실행할 수 없습니다 . 나는 parralel coroutines, Lua, Javascript 및 Python을 수행하지 않는 세 가지 언어를 가지고 있습니다.
whn

5

초기 시스템은 동시성을 제공하기 위해 코 루틴을 사용했습니다. 스레드는 운영 체제로부터 상당한 양의 지원을 필요로하며 (사용자 레벨에서 구현할 수 있지만 시스템이 주기적으로 프로세스를 중단하도록 배열 할 수있는 방법이 필요함) 지원이있는 경우에도 구현하기가 어렵습니다. .

스레드는 70 ~ 80 년대에 모든 심각한 운영 체제가 (그리고 90 년대, 심지어 Windows까지) 지원했으며 더 일반적이기 때문에 나중에 인수를 시작했습니다. 그리고 사용하기가 더 쉽습니다. 갑자기 모든 사람들이 스레드가 다음 큰 일이라고 생각했습니다.

90 년대 후반에 균열이 나타나기 시작했으며 2000 년대 초에는 실에 심각한 문제가 있음이 분명해졌습니다.

  1. 그들은 많은 자원을 소비합니다
  2. 상황에 맞는 스위치는 시간이 많이 걸리고 비교적 말이 많으며 종종 불필요합니다
  3. 그들은 참조의 지역성을 파괴
  4. 단독 액세스가 필요할 수있는 여러 리소스를 조정하는 올바른 코드를 작성하는 것은 예기치 않은 어려움

시간이 지남에 따라 프로그램이 일반적으로 언제라도 수행해야하는 작업 수가 빠르게 증가하여 위의 (1) 및 (2)로 인해 발생하는 문제가 증가했습니다. 프로세서 속도와 메모리 액세스 시간의 불일치가 증가하여 문제가 악화되었습니다 (3). 그리고 필요한 자원의 수와 종류에 따라 프로그램의 복잡성이 증가하면서 문제의 관련성이 높아졌습니다 (4).

그러나 약간의 일반성을 잃고 프로그래머가 프로세스를 어떻게 작동시킬 수 있는지에 대해 약간의 생각을함으로써 코 루틴은 이러한 모든 문제를 해결할 수 있습니다.

  1. 코 루틴은 대부분의 스레드 구현보다 훨씬 적은 수의 페이지를 스택에 비해 더 현명하게 요구합니다.
  2. 코 루틴은 프로그래머가 정의한 지점에서만 컨텍스트를 전환하므로 필요할 때만 의미가 있습니다. 또한 일반적으로 스레드만큼 많은 컨텍스트 정보 (예 : 레지스터 값)를 보존 할 필요가 없습니다. 즉, 각 스위치가 더 빠를뿐 아니라 더 적은 수의 스위치가 필요합니다.
  3. 생산자 / 소비자 유형 작업을 포함한 일반적인 코 루틴 패턴은 지역성을 적극적으로 증가시키는 방식으로 루틴간에 데이터를 전달합니다. 또한 컨텍스트 전환은 일반적으로 내부에없는 작업 단위간에 만 발생합니다. 즉, 일반적으로 지역성이 최소화되는 시점에 발생합니다.
  4. 일상적인 작업 중에 작업을 임의로 중단 할 수 없다는 사실을 알고 있으면 간단한 구현이 올바르게 작동 할 수 있으므로 리소스 잠금이 덜 필요합니다.

5

머리말

나는 코 루틴 부활, 병렬 처리를 얻지 못하는 이유를 진술하면서 시작하고 싶습니다 . 일반적으로 현대 코 루틴은 작업 기반 병렬 처리를 수행하는 수단 이 아닙니다 . 현대 구현에서는 다중 처리 기능을 사용하지 않기 때문입니다. 가장 가까운 것은 섬유 와 같은 것 입니다.

현대적인 사용법 (왜 돌아 왔는지)

현대 코 루틴은 haskell과 같은 기능적 언어에서 매우 유용한 게으른 평가 를 달성하는 방법으로 왔으며, 전체 세트를 반복하여 작업을 수행하는 대신 필요한만큼만 작업을 수행 할 수 있습니다. 무한한 아이템 세트 또는 조기 종료 및 서브 세트가있는 큰 세트에 유용합니다.

Python 및 C #과 같은 언어로 생성기 (자체 평가 요구의 일부를 충족시키는) 를 생성하기 위해 Yield 키워드를 사용 함으로써 현대 구현에서 코 루틴은 가능했을뿐만 아니라 언어 자체의 특별한 구문 없이도 가능했습니다. (파이썬은 결국 몇 가지 비트를 추가하여 도움을주었습니다). 의 아이디어와 게으른 evaulation와 공동 루틴 도움이 미래 당신이 그 시간에 변수의 값을 필요로하지 않는 경우에, 당신은 당신이 명시 적으로 그 값을 요청할 때까지 획득 실제로 지연시킬 수 있습니다 (이 값을 사용할 수 있도록하고 인스턴스화와 다른 시간에 느리게 평가하십시오 .)

그러나 특히 웹 스피어에서 게으른 평가 외에도 이러한 공동 루틴은 콜백 지옥을 해결하는 데 도움이 됩니다. 코 루틴은 데이터베이스 액세스, 온라인 트랜잭션, UI 등에 유용합니다. 클라이언트 시스템 자체의 처리 시간으로 인해 필요한 것에 더 빠르게 액세스 할 수 없습니다. 스레딩은 같은 것을 완전히 채울 수 있지만이 영역에서 더 많은 오버 헤드가 필요하며 코 루틴과 달리 실제로 작업 병렬 처리에 유용합니다 .

요컨대, 웹 개발이 성장하고 기능적 패러다임이 명령형 언어와 더 많이 통합됨에 따라 코 루틴은 비동기 문제 및 지연 평가에 대한 솔루션으로 등장했습니다. 코 루틴은 일반적으로 다중 프로세스 스레딩 및 스레딩이 불필요하거나 불편하거나 불가능한 문제가있는 공간에옵니다.

현대의 예

Javascript, Lua, C # 및 Python과 같은 언어의 코 루틴은 모두 주 함수를 다른 함수에 대한 제어를 포기 하는 개별 함수 (운영 체제 호출과 무관)로 구현을 파생시킵니다 .

에서 이 파이썬 예를 들어 , 우리는 뭔가라는 재미 있은 파이썬 기능이 await그 안에 있습니다. 이것은 기본적으로 yield이며, loop다른 함수 (이 경우 다른 factorial함수) 를 실행할 수있는 실행을 생성 합니다. "병렬로 실행되는 작업"이라는 말이 잘못된 경우 실제로는 병렬로 실행되지 않고 await 키워드를 사용 하여 인터리빙 기능이 실행 된다는 점 에 유의하십시오 (이는 특별한 유형의 산출량입니다).

그들은 허용되지 않은 단일 병렬에 대한 제어의 수익률 동시 아닌 프로세스 작업 병렬 이러한 작업이 작동하지 않는다는 의미에서, 지금까지 같은 시간에. 코 루틴은 현대 언어 구현의 스레드 가 아닙니다 . 이러한 모든 언어 루틴 공동 구현은 이러한 함수 수율 호출 (프로그래머가 실제로 수동으로 공동 루틴에 입력해야 함)에서 파생됩니다.

편집 : C ++ Boost coroutine2는 같은 방식으로 작동하며 설명은 내가 yeilds와 이야기하고있는 것에 대한 더 나은 시각을 제공해야 합니다 . 여기를 참조하십시오 . 보시다시피 구현에는 "특별한 경우"가 없으며 부스트 섬유 와 같은 것은 예외이며 명시적인 동기화가 필요합니다.

EDIT2 : 누군가가 C # 작업 기반 시스템에 대해 이야기하고 있다고 생각했기 때문에 그렇지 않았습니다. Unity의 시스템 과 순진한 C # 구현에 대해 이야기했습니다.


@ T.Sar 저는 C #에 "자연적인"코 루틴이 있다고 말하지 않았으며, C ++ (변경 될 수 있음)도 파이썬 (그리고 여전히 가지고 있음)도 없었으며 세 가지 모두 공동 루틴 구현을 가지고있었습니다. 그러나 코 루틴의 모든 C # 구현 (예 : 단일체의 구현)은 내가 설명한대로 수율을 기반으로합니다. 또한 여기서 "hack"을 사용하는 것은 의미가 없습니다. 모든 프로그램이 항상 언어로 정의 된 것은 아니기 때문에 모든 프로그램이 핵이라고 생각합니다. 나는 C # "태스크 기반 시스템"을 어떤 것과도 섞지 않고 있으며, 언급조차하지 않았다.
whn

답을 좀 더 명확하게하는 것이 좋습니다. C #에는 Await 명령 개념과 작업 기반 병렬 처리 시스템이 있습니다. C #과 그 단어를 사용하면서 파이썬에서 실제로 병렬이 아닌 방법에 대한 예제를 제공하면서 많은 혼란을 일으킬 수 있습니다. 또한 첫 번째 문장을 제거하십시오. 그런 식으로 다른 사용자를 직접 공격 할 필요는 없습니다.
T. Sar-복원 모니카
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.