프로그래밍 언어가 동기 / 비동기 문제를 자동으로 관리하지 않는 이유는 무엇입니까?


27

나는 이것에 관한 많은 자료를 찾지 못했다. 동기 방식으로 비동기 코드를 작성할 수 있는지 / 좋은 아이디어인지 궁금했다.

예를 들어, 데이터베이스에 저장된 사용자 수를 검색하는 일부 JavaScript 코드는 다음과 같습니다 (비동기 작업).

getNbOfUsers(function (nbOfUsers) { console.log(nbOfUsers) });

다음과 같이 쓸 수 있으면 좋을 것입니다.

const nbOfUsers = getNbOfUsers();
console.log(getNbOfUsers);

따라서 컴파일러는 자동으로 응답을 기다렸다가 실행 console.log합니다. 결과가 다른 곳에서 사용되기 전에 항상 비동기 작업이 완료 될 때까지 대기합니다. 콜백 약속, 비동기 / 대기 등을 훨씬 덜 사용하며 작업 결과가 즉시 제공되는지 여부를 걱정할 필요가 없습니다.

nbOfUserstry / catch 또는 Swift 언어 와 같은 선택 사항을 사용하여 오류를 여전히 관리 가능합니다 ( 정수 또는 오류가 발생 했습니까?) .

가능합니까? 끔찍한 생각 일 수도 있고 유토피아 일 수도 있습니다 ... 잘 모르겠습니다.


58
나는 당신의 질문을 정말로 이해하지 못합니다. "항상 비동기 작업을 기다립니다"는 비동기 작업이 아닌 동기 작업입니다. 당신은 명확히 할 수 있습니까? 찾고있는 행동 유형에 대한 사양을 제공 할 수 있습니까? 또한 "어떻게 생각하십니까?"는 소프트웨어 엔지니어링 에 대한 주제가 아닙니다 . 구체적인 문제의 맥락에서 질문을 공식화해야합니다. 구체적인 문제는 명확하고 정직하며 객관적인 정답이 하나입니다.
Jörg W Mittag

4
@ JörgWMittag 나는 그것을 암시 적으로 awaitsa Task<T>로 변환 하는 가상의 C #을 상상한다T
Caleth

6
당신이 제안하는 것은 할 수 없습니다. 결과를 기다릴 것인지 아니면 해고 할 것인지를 결정하는 것은 컴파일러의 책임이 아닙니다. 또는 백그라운드에서 실행하고 나중에 기다립니다. 왜 그렇게 자신을 제한합니까?
괴물

5
예, 끔찍한 생각입니다. 대신 async/ await를 사용 하면 실행의 비동기 부분이 명시 적으로 나타납니다.
Bergi

5
두 가지 일이 동시에 발생한다고 말할 때, 이러한 일이 어떤 순서로든 발생하면 괜찮습니다. 코드를 재정렬하여 코드의 기대치를 어기는 것이 무엇인지 명확하게 알 수 없다면 코드를 동시에 만들 수 없습니다.

답변:


65

Async / await는 두 개의 추가 키워드가 있지만 제안한 자동화 된 관리입니다. 왜 중요한가요? 이전 버전과의 호환성 외에도?

  • 코 루틴이 중단되고 재개 될 수있는 명백한 지점이 없다면 , 기다릴 수있는 값을 기다려야하는 곳을 감지하기 위한 유형 시스템 이 필요 합니다. 많은 프로그래밍 언어에는 이러한 유형 시스템이 없습니다.

  • 값을 기다리는 것을 명시 적으로함으로써, 우리는 일류 객체로서의 값을 약속 할 수 있습니다. 이것은 고차 코드를 작성할 때 매우 유용 할 수 있습니다.

  • 비동기 코드는 언어의 예외 유무와 유사하게 언어의 실행 모델에 매우 깊은 영향을 미칩니다. 특히, 비동기 기능은 비동기 기능으로 만 대기 할 수 있습니다. 이것은 모든 호출 기능에 영향을 미칩니다! 그러나이 의존성 체인의 끝에서 함수를 비동기가 아닌 비동기에서 비동기로 변경하면 어떻게 될까요? 모든 기능이 비동기 적이며 모든 기능 호출이 기본적으로 대기하지 않는 한 이전 버전과 호환되지 않는 변경입니다.

    이는 성능에 매우 나쁜 영향을 미치기 때문에 바람직하지 않습니다. 값싼 값을 단순히 반환 할 수는 없습니다. 모든 함수 호출은 훨씬 비싸 질 것입니다.

비동기는 훌륭하지만 어떤 종류의 암시 적 비동기는 실제로 작동하지 않습니다.

Haskell과 같은 순수 기능 언어는 실행 순서가 크게 지정되지 않고 관찰 할 수 없기 때문에 약간의 탈출구가 있습니다. 또는 다르게 표현 : 특정 작업 순서는 명시 적으로 인코딩해야합니다. 실제 프로그램, 특히 비동기 코드가 매우 적합한 I / O가 많은 프로그램에서는 다소 번거로울 수 있습니다.


2
타입 시스템이 반드시 필요한 것은 아닙니다. ECMAScript, Smalltalk, Self, Newspeak, Io, Ioke, Seph와 같은 투명한 미래는 시스템이나 언어 지원없이 쉽게 구현할 수 있습니다. 스몰 토크와 그 자손에서 객체는 투명성을 식별 할 수 있으며, ECMAScript에서는 투명하게 모양을 변경할 수 있습니다. 그것이 미래를 투명하게하기 위해 필요한 모든 것, 비동기를위한 언어 지원이 필요하지 않습니다.
Jörg W Mittag

6
@ JörgWMittag 나는 당신이 말하는 것과 그것이 어떻게 작동하는지 이해하지만 타입 시스템이없는 투명한 미래는 일류 선물을 동시에 얻는 것이 다소 어려워집니다. 미래 또는 미래의 가치에 메시지를 보낼 것인지 선택하는 방법이 필요 someValue ifItIsAFuture [self| self messageIWantToSend]합니다. 일반 코드와 통합하기가 까다로워서 보다 좋습니다 .
amon

8
@amon "약속과 약속이 모나드이므로 비동기 코드를 작성할 수 있습니다." 여기서 모나드는 실제로 필요하지 않습니다. 썽 크는 본질적으로 단지 약속입니다. Haskell의 거의 모든 값이 박스로 표시되므로 Haskell의 거의 모든 값은 이미 약속되어 있습니다. 그렇기 때문에 par순수한 Haskell 코드의 어느 곳에서나 거의 무료로 패러렐 리즘을 얻을 수 있습니다.
DarthFennec

2
Async / await는 연속 모나드를 상기시킵니다.

3
실제로, 예외와 async / await는 대수 효과의 사례입니다 .
Alex Reinking

21

누락 된 것은 비동기 작업 의 목적 입니다. 대기 시간을 활용할 수 있습니다!

서버에서 일부 리소스 요청과 같은 비동기 작업을 암시 적으로 즉시 응답을 대기하여 동기 작업으로 설정하면 스레드가 대기 시간으로 다른 작업을 수행 할 수 없습니다 . 서버가 응답하는 데 10 밀리 초가 걸리면 약 3 천만 개의 CPU주기가 낭비됩니다. 응답 대기 시간이 요청의 실행 시간이됩니다.

프로그래머가 비동기 작업을 발명 한 유일한 이유 는 다른 유용한 계산 뒤에 본질적으로 오래 실행되는 작업의 대기 시간을 숨기는 것 입니다. 유용한 작업으로 대기 시간을 채울 수 있으면 CPU 시간이 절약됩니다. 당신이 할 수 없다면, 비동기 작업으로 인해 손실되는 것이 없습니다.

따라서 귀하의 언어가 귀하에게 제공하는 비동기 작업을 수용하는 것이 좋습니다. 그들은 당신을 시간을 절약하기 위해 있습니다.


나는 연산이 차단되지 않는 기능적 언어를 생각하고 있었기 때문에 동기식 구문이 있더라도 장시간 실행되는 계산은 스레드를 차단하지 않습니다
Cinn

6
@Cinn 나는 질문에서 그것을 찾지 못했고, 질문의 예는이 기능이없는 Javascript입니다. 그러나 일반적으로 컴파일러가 설명 할 때 병렬화에 대한 의미있는 기회를 찾기는 다소 어렵습니다. 이러한 기능을 의미있게 활용하려면 프로그래머가 긴 대기 시간 호출 직후 에 무엇 을 넣었 는지 명시 적으로 생각해야합니다 . 프로그래머가이 요구 사항을 피할 수있을 정도로 런타임을 스마트하게 만들면 런타임은 함수 호출 전체에서 적극적으로 병렬화해야하기 때문에 성능을 크게 떨어 뜨릴 수 있습니다.
cmaster

2
모든 컴퓨터는 같은 속도로 대기합니다.
Bob Jarvis-

2
@BobJarvis 예. 그러나 그들은 대기 시간 에 얼마나 많은 일을 할 수 있 었는가에 차이 있습니다.
cmaster

13

일부는 그렇습니다.

비동기식은 비교적 새로운 기능이기 때문에 주류가 아닙니다. 아마도 지금은 좋은 기능인지 또는 친숙하고 사용 가능한 방식으로 프로그래머에게 제공하는 방법에 대해서만 좋은 느낌을 얻은 비교적 새로운 기능이기 때문입니다. 표현 등 기존 비동기 기능은 기존 언어에 크게 고정되어 있으므로 약간 다른 디자인 방식이 필요합니다.

즉, 모든 곳에서하는 것이 분명 좋은 생각은 아닙니다 . 일반적인 실패는 루프에서 비동기 호출을 수행하여 효과적으로 실행을 직렬화하는 것입니다. 비동기 호출이 암시 적으로 있으면 이러한 종류의 오류가 모호해질 수 있습니다. 또한 Task<T>(또는 귀하의 언어와 동등한) 에서로 암시 적 강제를 지원 T하는 경우, 프로그래머가 실제로 원하는 두 가지 중 어느 것이 확실하지 않은 경우 유형 검사기 및 오류보고에 약간의 복잡성 / 비용이 추가 될 수 있습니다.

그러나 이것들은 극복 할 수없는 문제가 아닙니다. 당신이 그 행동을 지원하기를 원한다면 당신은 거의 확실하게 할 수 있지만, 절충이있을 것입니다.


1
나는 비동기 함수로 모든 것을 감싸는 것이 아이디어 일 수 있다고 생각한다. 동기 작업은 즉시 해결 될 것이고 우리는 모든 종류의 처리를해야한다 (편집 : @ amon은 왜 나쁜 생각인지 설명했다 ...)
Cinn

8
" 일부 "에 대한 몇 가지 예를 들어 주시겠습니까?
Bergi

2
비동기식 프로그래밍은 전혀 새로운 것이 아니며, 요즘 사람들이 더 자주 처리해야하는 것입니다.
큐빅

1
@Cubic-내가 아는 한 언어 기능입니다. 그것은 단지 (어색한) 유저 랜드 기능이었습니다.
Telastyn

12

이를 수행하는 언어가 있습니다. 그러나 기존 언어 기능으로 쉽게 수행 할 수 있기 때문에 실제로는 그다지 필요하지 않습니다.

만큼 당신이 가지고있는 몇 가지 비동기를 표현하는 방법을, 당신은 구현할 수있는 선물 또는 약속을 순수 라이브러리 기능으로, 당신은 특별한 언어 기능이 필요하지 않습니다. 그리고만큼 당신이 가지고있는 몇 가지 표현의 투명 프록시를 , 당신은 함께 두 가지 기능을 넣을 수 있습니다 그리고 당신은 투명 선물을 .

예를 들어, 스몰 토크와 그 자손들에서, 객체는 그것의 동일성을 바꿀 수 있고, 문자 그대로 다른 객체를 "나올"수 있습니다 (실제로 이것을 수행하는 방법을이라고합니다 Object>>become:).

을 반환하는 장기 실행 계산을 상상해보십시오 Future<Int>. 이것은 Future<Int>모든 동일한 방법 갖는 Int다른 구현 예를 제외. Future<Int>+방법은 다른 숫자를 추가하지 않고 결과를 반환하며 Future<Int>계산을 래핑 하는 새로운 값 을 반환합니다 . 등등. 현명를 반환하여 구현 될 수없는 방법 Future<Int>대신 자동으로됩니다 await결과는 다음 호출 self become: result.하는이 (현재 실행중인 개체를 만들 것 self, 즉이 Future<Int>) 그대로가 resulta가 될하는 데 사용하는 객체 참조 지금부터 즉, 객체 Future<Int>입니다 이제는 Int클라이언트에게 완전히 투명한 곳입니다.

특별한 비동기 성 관련 언어 기능이 필요하지 않습니다.


좋아,하지만 두 경우 문제가 Future<T>T몇 가지 공통 인터페이스를 공유하고 그 인터페이스 기능을 사용합니다. 그것은해야 become결과 다음 기능을 사용하거나하지? 동등 연산자 또는 문자열 디버깅 표현과 같은 것을 생각하고 있습니다.
amon

나는 그것이 어떤 기능도 추가하지 않는다는 것을 이해합니다. 사실은 즉시 해결 계산과 장기 실행 계산을 작성하는 다른 구문이 있으며 그 후에 다른 목적으로 결과를 동일한 방식으로 사용한다는 것입니다. 둘 다 투명하게 처리하는 구문을 사용하여 더 읽기 쉽고 프로그래머가 처리하지 않아도 될지 궁금합니다. a + ba와 b가 즉시 또는 나중에 사용 가능하든 상관없이 두 정수를 수행하는 것과 마찬가지로 우리는 단지 a + b(가능하게 만들기 Int + Future<Int>)
Cinn

@Cinn : 예. 투명한 선물로 그렇게 할 수 있으며 특별한 언어 기능이 필요하지 않습니다. Smalltalk, Self, Newspeak, Us, Korz, Io, Ioke, Seph, ECMAScript와 같은 기존 기능을 사용하여 구현할 수 있으며 분명히 방금 읽은 것처럼 Python입니다.
Jörg W Mittag

3
@amon : Transparent Futures 의 아이디어 는 미래라는 것을 모른다는 것입니다. 당신의 관점에서, 사이에 공통 인터페이스가없는 Future<T>T때문에 당신의 관점에서가, 어떤이 없습니다Future<T> 만은 T. 물론,이를 효율적으로 만드는 방법에 대한 많은 엔지니어링 과제가 있습니다. 어떤 작업을 차단과 비 차단 등에서 수행해야하는지 여부는 언어 또는 라이브러리 기능으로 수행하는지 여부와 관계가 없습니다. 투명성은 문제의 OP에 의해 규정 된 요구 사항이었다. 나는 그것이 어렵고 이해가되지 않을 것이라고 주장하지 않을 것이다.
Jörg W Mittag

3
@ Jörg 코드가 실제로 해당 모델에서 언제 실행되는지 알 방법이 없기 때문에 기능적 언어 이외의 문제가있는 것 같습니다. 그것은 일반적으로 Haskell에서 잘 작동하지만, 이것이 더 절차 적 언어로 작동하는 방식을 볼 수는 없습니다 (심지어 Haskell에서도 성능에 관심이 있다면 때로는 강제로 실행하고 기본 모델을 이해해야합니다). 그럼에도 불구하고 흥미로운 아이디어입니다.
Voo

7

그들은 (잘, 대부분) 있습니다. 찾고있는 기능을 스레드 라고 합니다 .

그러나 스레드에는 자체 문제가 있습니다.

  1. 코드가에 중단 될 수 있기 때문에 어느 시점 , 당신은 할 수없는 지금 그 일이 "스스로"변경되지 않습니다 가정합니다. 스레드를 사용하여 프로그래밍 할 때 프로그램 변경 사항을 처리하는 방법에 대해 많은 시간을 낭비합니다.

    게임 서버가 다른 플레이어에 대한 플레이어의 공격을 처리한다고 가정합니다. 이 같은:

    if (playerInMeleeRange(attacker, victim)) {
        const damage = calculateAttackDamage(attacker, victim);
        if (victim.health <= damage) {
    
            // attacker gets whatever the victim was carrying as loot
            const loot = victim.getInventoryItems();
            attacker.addInventoryItems(loot);
            victim.removeInventoryItems(loot);
    
            victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon} and you die!");
            victim.setDead();
        } else {
            victim.health -= damage;
            victim.sendMessage("${attacker} hits you with a ${attacker.currentWeapon}!");
        }
        attacker.markAsKiller();
    }
    

    3 개월 후, 플레이어 attacker.addInventoryItems는 도망 칠 때 정확하게 죽이고 로그 오프 victim.removeInventoryItems하면 실패 할 것이며, 아이템을 보관할 수 있으며 공격자는 아이템의 사본을 얻는다는 것을 알게됩니다. 그는 이것을 여러 차례해서 얇은 공기에서 백만 톤의 금을 만들어 게임 경제를 무너 뜨 렸습니다.

    또는 게임에서 피해자에게 메시지를 보내는 동안 공격자가 로그 아웃 할 수 있으며, 머리 위에 "살인자"태그가 표시되지 않으므로 다음 피해자가 도망 가지 않습니다.

  2. 코드는 언제 라도 일시 중단 될 수 있으므로 데이터 구조를 조작 할 때는 모든 곳에서 잠금을 사용해야합니다. 나는 게임에서 명백한 결과를 가져 오는 위의 예를 들었지만 더 미묘 할 수 있습니다. 연결된 목록의 시작 부분에 항목을 추가하십시오.

    newItem.nextItem = list.firstItem;
    list.firstItem = newItem;
    

    스레드가 I / O를 수행 할 때만 일시 중단 될 수 있으며 어떤 시점에서도 중단되지 않는다고 말하는 경우에는 문제가되지 않습니다. 그러나 로깅과 같은 I / O 작업이있는 상황을 상상할 수 있습니다.

    for (player = playerList.firstItem; player != null; player = item.nextPlayer) {
        debugLog("${item.name} is online, they get a gold star");
        // Oops! The player might've logged out while the log message was being written to disk, and now this will throw an exception and the remaining players won't get their gold stars.
        // Or the list might've been rearranged and some players might get two and some players might get none.
        player.addInventoryItem(InventoryItems.GoldStar);
    }
    
  3. 코드는 언제든 일시 중단 될 수 있으므로 저장해야 할 상태가 많이있을 수 있습니다. 시스템은 각 스레드에 완전히 별도의 스택을 제공하여이를 처리합니다. 그러나 스택은 상당히 커서 32 비트 프로그램에서 약 2000 개 이상의 스레드를 가질 수 없습니다. 또는 스택 크기를 너무 작게 만들 위험이 있으므로 스택 크기를 줄일 수 있습니다.


3

질문이 문자 그대로 비 블로킹 IO가 아닌 비동기 프로그래밍에 대해 묻는 것이지만이 특별한 경우에는 다른 것에 대해 논의하지 않고 하나를 논의 할 수는 없다고 생각하기 때문에 오해의 소지가있는 많은 답변을 찾습니다.

비동기식 프로그래밍은 본질적으로 비동기식이지만 비동기식 프로그래밍의 단점은 대부분 커널 스레드를 막는 것입니다. Node.js는 콜백 또는 Promises를 통해 비동기 성을 사용 하여 블로킹 작업이 이벤트 루프에서 전달되도록하고 Java의 Netty는 콜백 또는 CompletableFutures를 통해 비동기 성을 사용 하여 유사한 작업을 수행합니다.

그러나 비 블로킹 코드는 비동기 성을 요구하지 않습니다 . 프로그래밍 언어와 런타임이 당신을 위해 얼마나 기꺼이 할 것인지에 달려 있습니다.

Go, Erlang 및 Haskell / GHC가이를 처리 할 수 ​​있습니다. var response = http.get('example.com/test')응답을 기다리는 동안 같은 것을 작성 하고 장면 뒤에서 커널 스레드를 해제하도록 할 수 있습니다 . 이것은 goroutines, Erlang 프로세스 또는 forkIO블로킹 할 때 씬 뒤에 커널 스레드 를 놓아서 응답을 기다리는 동안 다른 작업을 수행함으로써 수행됩니다.

언어가 실제로 비동기 성을 처리 할 수는 없지만 일부 추상화는 무제한 연속 또는 비대칭 코 루틴과 같은 다른 것보다 더 나아갈 수 있습니다. 그러나 시스템 호출을 차단하는 비동기 코드의 주요 원인은 개발자로부터 완전히 추상화 될 있습니다.

Node.js와 Java는 비동기 비 차단 코드를 지원하는 반면 Go와 Erlang은 동기 비 차단 코드를 지원 합니다 . 그것들은 서로 다른 트레이드 오프를 가진 유효한 접근 방식입니다.

오히려 주관적인 주장은 개발자를 대신하여 비 블로킹을 관리하는 런타임에 대해 논쟁하는 사람들은 초기의 가비지 수집에 대해 논쟁하는 사람들과 같다는 것입니다. 예, 비용이 발생하지만 (이 경우 주로 더 많은 메모리) 개발 및 디버깅이 쉬워지고 코드베이스가 더욱 강력 해집니다.

개인적으로 비동기 비 차단 코드는 향후 시스템 프로그래밍을 위해 예약해야하고보다 현대적인 기술 스택은 응용 프로그램 개발을 위해 동기 비 차단 런타임으로 마이그레이션해야한다고 주장합니다 .


1
이것은 정말 흥미로운 답변이었습니다! 그러나“동기식”과“비동기식”비 차단 코드의 차이점을 잘 모르겠습니다. 나에게 동기식 비 블로킹 코드는 차단 waitpid(..., WNOHANG)해야한다면 실패 하는 C 함수와 같은 것을 의미합니다. 또는 "동기"는 "프로그래머가 볼 수있는 콜백 / 상태 머신 / 이벤트 루프가 없음"을 의미합니까? 그러나 Go 예제의 경우 채널에서 읽음으로써 고 루틴의 결과를 명시 적으로 기다려야합니다. JS / C # / Python에서 async / await보다 비동기가 어떻습니까?
아몬

1
"비동기"와 "동기"를 사용하여 개발자에게 노출 된 프로그래밍 모델에 대해 논의하고 "차단"과 "비 차단"에 대해서는 커널 스레드의 블로킹에 대해 논의합니다. 수행해야하는 다른 계산과 사용할 수있는 여분의 논리 프로세서가 있습니다. 고 루틴은 기본 스레드를 차단하지 않고 결과를 기다릴 수 있지만 원하는 경우 다른 고 루틴이 채널을 통해 통신 할 수 있습니다. 고 루틴 은 비 블로킹 소켓 읽기를 기다리기 위해 채널을 직접 사용할 필요가 없습니다 .
Louis Jackman

흠, 나는 지금 당신의 구별을 이해합니다. 코 루틴 간의 데이터 흐름과 제어 흐름을 관리하는 데 더 관심이 있지만 주 커널 스레드를 절대로 차단하지 않는 것이 더 중요합니다. Go 또는 Haskell이 C ++ 또는 Java보다 이점을 가지고 있는지 확실하지 않습니다. 백그라운드 스레드를 시작할 수 있기 때문에 조금 더 많은 코드가 필요합니다.
amon

@LouisJackman은 시스템 프로그래밍을위한 비동기 비 차단에 대한 마지막 설명을 조금 더 자세히 설명 할 수 있습니다. 비동기 비 차단 접근 방식의 장점은 무엇입니까?
sunprophit

@sunprophit 비동기 비 차단은 단지 컴파일러 변환 (보통 비동기 / 대기)이지만 동기 비 차단은 복잡한 스택 조작, 함수 호출에 항복점 삽입 (인라인과 충돌 할 수 있음), 추적 추적과 같은 런타임 지원이 필요합니다. 가비지 수집과 마찬가지로 사용 편의성과 견고성을 위해 런타임 복잡성을 줄입니다. C, C ++ 및 Rust와 같은 시스템 언어는 대상 도메인으로 인해 이와 같은 더 큰 런타임 기능을 피하므로 비동기 비 블로킹이 더 적합합니다.
루이스 잭맨

2

내가 당신을 올바르게 읽으면 동기식 프로그래밍 모델을 요구하지만 고성능 구현을 요구합니다. 그것이 맞다면 Erlang 또는 Haskell과 같은 녹색 스레드 또는 프로세스 형태로 이미 사용 가능합니다. 그렇습니다. 훌륭한 아이디어이지만 기존 언어에 대한 개장이 항상 원하는만큼 매끄럽지는 않을 수 있습니다.


2

질문에 감사 드리며 대부분의 답변은 현 상태를 방어하는 것으로 나타났습니다. 저급에서 고급까지 다양한 언어에서 우리는 한동안 틀에 박혀있었습니다. 다음으로 높은 수준은 구문 (대기 및 비동기와 같은 명시 적 키워드의 필요성)에 초점을 맞추지 않고 의도에 대해 훨씬 더 많은 언어가 될 것입니다. (Charles Simonyi에게 명백한 신용이지만 2019와 미래를 생각하십시오.)

프로그래머에게 말하면 데이터베이스에서 단순히 값을 가져 오는 코드를 작성하면 "BTW, UI를 중단하지 마십시오"와 "버그를 찾기가 어려운 다른 고려 사항을 도입하지 말 것" ". 차세대 언어 및 도구를 갖춘 미래의 프로그래머는 분명히 한 줄의 코드로 값을 가져 와서 거기에서 나오는 코드를 작성할 수 있습니다.

가장 높은 수준의 언어는 영어로 말하고 업무 수행자의 역량에 의존하여 실제로 원하는 것을 알 수 있습니다. (스타 트렉에서 컴퓨터를 생각하거나 알렉사에 대해 물어보십시오.) 우리는 그와는 거리가 멀지 만 더 가까이 다가 가고 싶습니다. 언어 / 컴파일러는 의도하지 않은 강력하고 의도적 인 코드를 더 많이 생성 할 수있을 것으로 기대합니다. AI가 필요합니다.

한편으로는 스크래치와 같은 새로운 시각적 언어가 있으며이를 수행하고 모든 구문 기술에 얽매이지 않습니다. 확실히, 많은 비하인드 작업이 진행되므로 프로그래머는 그것에 대해 걱정할 필요가 없습니다. 즉, 나는 스크래치로 비즈니스 클래스 소프트웨어를 작성하지 않기 때문에 성숙한 프로그래밍 언어가 자동으로 동기 / 비동기 문제를 관리 할 때가 될 것으로 기대합니다.


1

설명하는 문제는 두 가지입니다.

  • 작성하는 프로그램 은 외부에서 볼 때 전체적으로 비동기 적으로 동작해야합니다 .
  • 그것은해야 하지 함수 호출이 잠재적으로 제어를 포기 여부 호출 사이트에서 볼 수 있습니다.

이것을 달성하는 몇 가지 방법이 있지만 기본적으로

  1. 다중 스레드 (일부 추상화 레벨)
  2. 언어 수준에서 여러 종류의 기능을 가지고 있으며, 모두 이와 같이 불립니다 foo(4, 7, bar, quux).

(1)의 경우 여러 프로세스를 포크하고 실행하고 여러 커널 스레드를 생성하며 언어 런타임 수준 스레드를 커널 스레드로 예약하는 녹색 스레드 구현을 함께 묶습니다. 문제의 관점에서 그들은 동일합니다. 이 세계에서는 어떤 함수도 스레드의 관점에서 제어를 포기하거나 잃지 않습니다 . 자체가 스레드 때로는 때로는 제어를하지 않고가 실행되고 있지 않습니다하지만 당신은이 세상에서 자신의 스레드의 제어를 포기하지 않습니다. 이 모델에 적합한 시스템은 새 스레드를 생성하거나 기존 스레드에서 조인하는 기능이 있거나 없을 수 있습니다. 이 모델에 맞는 시스템 은 Unix와 같은 스레드 를 복제 하는 기능이 있거나 없을 수 있습니다 fork.

(2) 흥미 롭습니다. 정의를 수행하기 위해서는 소개 및 제거 양식에 대해 이야기해야합니다.

awaitJavascript와 같은 언어에 암시 적을 역 호환성으로 추가 할 수없는 이유를 보여 드리겠습니다. 기본 아이디어는 사용자에게 약속을 제공하고 동기 컨텍스트와 비동기 컨텍스트를 구분함으로써 Javascript가 동기 및 비동기 함수를 균일하게 처리하지 못하게하는 구현 세부 사항을 유출한다는 것입니다. await비동기 함수 본문 외부에서 약속 을 할 수 없다는 사실도 있습니다 . 이러한 설계 선택은 "발신자에게 비동기 성을 보이지 않게"와 호환되지 않습니다.

람다를 사용하여 동기 함수를 도입하고 함수 호출로 제거 할 수 있습니다.

동기식 기능 소개 :

((x) => {return x + x;})

동기 기능 제거 :

f(4)

((x) => {return x + x;})(4)

이를 비동기 함수 도입 및 제거와 대조 할 수 있습니다.

비동기 함수 소개

(async (x) => {return x + x;})

비동기 함수 제거 (참고 : async함수 내에서만 유효 )

await (async (x) => {return x + x;})(4)

여기서 근본적인 문제 는 비동기 함수도 promise 객체를 생성하는 동기 함수라는 것 입니다.

다음은 node.js repl에서 비동기 함수를 동 기적으로 호출하는 예입니다.

> (async (x) => {return x + x;})(4)
Promise { 8 }

비동기식과 동기식 함수 호출의 차이가 호출 사이트에 표시되지 않고 정의 사이트에 표시되지 않는 동적 언어 유형의 언어를 가정 할 수 있습니다.

이와 같은 언어를 사용하여 Javascript로 낮추는 것이 가능하므로 모든 기능을 효과적으로 비 동기화해야합니다.


1

Go 언어 고 루틴 및 Go 언어 런타임을 사용하면 모든 코드를 동기화 된 것처럼 작성할 수 있습니다. 한 고 루틴에서 작업이 차단되면 다른 고 루틴에서 계속 실행됩니다. 채널을 사용하면 고 루틴과 쉽게 통신 할 수 있습니다. Javascript 또는 다른 언어의 async / await와 같은 콜백보다 쉽습니다. 몇 가지 예와 설명 은 https://tour.golang.org/concurrency/1 을 참조하십시오 .

게다가, 나는 그것에 대한 개인적인 경험이 없지만 Erlang이 비슷한 시설을 가지고 있다고 들었습니다.

따라서 Go 및 Erlang과 같은 프로그래밍 언어가있어 동기 / 비동기 문제를 해결하지만 불행히도 아직 널리 사용되지는 않습니다. 이러한 언어의 인기가 높아짐에 따라 해당 언어로 제공되는 기능은 다른 언어로도 구현 될 것입니다.


나는 Go 언어를 거의 사용하지 않았지만 명시 적으로 선언 go ...하는 것처럼 보이므로 await ...no 와 비슷하게 보 입니까?
Cinn

1
@Cinn 사실, 아닙니다. 를 사용하여 모든 통화를 자신의 파이버 / 그린 스레드에 고 루틴으로 넣을 수 있습니다 go. 그리고 막을 수있는 호출은 런타임에 의해 비동기 적으로 수행되며 그 동안 다른 고 루틴으로 전환됩니다 (협동 멀티 태스킹). 당신은 메시지를 기다리면서 기다리고 있습니다.
중복 제거기

2
Goroutines는 일종의 동시성이지만 비동기 / 대기와 같은 버킷에 넣지는 않습니다. 협동 코 루틴이 아니라 예약 된 녹색 스레드가 자동으로 (선점 적으로!) 있습니다. 그러나 이것은 자동 대기를 기다리지 않습니다. Go await는 채널에서 읽는 것과 같습니다 <- ch.
amon

@amon 내가 아는 한, goroutine은 런타임에 의해 원시 스레드 (일반적으로 실제 하드웨어 병렬 처리를 최대로 충분하게 만들 수 있음)에 대해 협력 적으로 예약되며 OS에 의해 미리 예약됩니다.
중복 제거기

OP는 "동기 방식으로 비동기 코드를 작성할 수 있도록"요청했습니다. 앞서 언급했듯이 고 루틴과 go 런타임으로 정확하게 그렇게 할 수 있습니다. 스레딩의 세부 사항에 대해 걱정할 필요가 없으며 코드가 동기적인 것처럼 쓰기 차단 읽기 및 쓰기 만 가능하며 다른 고 루틴 (있는 경우)은 계속 실행됩니다. 이 혜택을 얻기 위해 채널을 "기다리거나"읽을 필요도 없습니다. 따라서 Go는 OP의 요구를 가장 잘 충족시키는 프로그래밍 언어라고 생각합니다.

1

아직 제기되지 않은 매우 중요한 측면 인 재진입이 있습니다. 비동기 호출 중에 실행되는 다른 코드 (예 : 이벤트 루프)가 있고 (그렇지 않으면 왜 비동기가 필요한가?) 코드가 프로그램 상태에 영향을 줄 수 있습니다. 호출자는 자신의 함수 호출 기간 동안 영향을받지 않기 위해 프로그램 상태의 일부에 의존 할 수 있으므로 호출자에서 비동기 호출을 숨길 수 없습니다. 예:

function foo( obj ) {
    obj.x = 2;
    bar();
    log( "obj.x equals 2: " + obj.x );
}

bar()비동기 함수 인 경우 obj.x실행 중에가 변경 될 수 있습니다 . 막대가 비동기이며 그 효과가 가능하다는 힌트가 없으면 예상치 못한 것입니다. 유일한 대안은 가능한 모든 함수 / 메소드가 비동기인지 의심하고 각 함수 호출 후 로컬이 아닌 상태를 다시 가져오고 다시 확인하는 것입니다. 이것은 미묘한 버그가 발생하기 쉬우 며 일부 로컬이 아닌 상태가 함수를 통해 가져 오는 경우에는 불가능할 수도 있습니다. 이 때문에 프로그래머는 예기치 않은 방식으로 프로그램 상태를 변경할 가능성이있는 기능을 알고 있어야합니다.

async function foo( obj ) {
    obj.x = 2;
    await bar();
    log( "obj.x equals 2: " + obj.x );
}

이제이 bar()함수가 비동기 함수 라는 것을 분명히 알 수 있으며 이를 처리하는 올바른 방법은 obj.x나중에 예상 값을 다시 확인하고 발생한 변경 사항을 처리하는 것입니다.

다른 답변에서 이미 언급했듯이 Haskell과 같은 순수한 기능 언어는 공유 / 전역 상태가 전혀 필요하지 않으므로 그 영향을 완전히 벗어날 수 있습니다. 함수형 언어에 대한 경험이 많지 않아서 아마도 편향적이지만 글로벌 상태가 부족하다는 것이 큰 응용 프로그램을 작성할 때 이점이 아니라고 생각합니다.


0

귀하의 질문에 사용한 Javascript의 경우 알아야 할 중요한 사항이 있습니다. Javascript는 단일 스레드이며 비동기 호출이없는 한 실행 순서가 보장됩니다.

따라서 당신과 같은 시퀀스가 ​​있다면 :

const nbOfUsers = getNbOfUsers();

그 동안 다른 어떤 것도 실행되지 않을 것입니다. 잠금 장치 또는 이와 유사한 장치가 필요하지 않습니다.

그러나 getNbOfUsers비동기 인 경우 :

const nbOfUsers = await getNbOfUsers();

getNbOfUsers실행 하는 동안 실행 수율 및 다른 코드가 중간에 실행될 수 있음을 의미합니다 . 결과적으로 수행중인 작업에 따라 일부 잠금이 필요할 수 있습니다.

따라서 어떤 상황에서는 호출이 동기적일 경우 필요하지 않은 추가 예방 조치를 취해야하므로 호출이 비동기적일 때와 그렇지 않은 경우를 아는 것이 좋습니다.


당신 말이 맞아, 질문의 두 번째 코드 getNbOfUsers()는 약속을 반환하는 것처럼 유효하지 않습니다 . 그러나 그것은 정확히 내 질문의 요점입니다. 왜 명시 적으로 비동기식으로 작성해야합니까? 컴파일러는 그것을 감지하고 자동으로 다른 방식으로 처리 할 수 ​​있습니다.
Cinn

@Cinn 그것은 내 요점이 아닙니다. 내 요점은 비동기 호출을 실행하는 동안 실행 흐름이 코드의 다른 부분에 도달 할 수 있지만 동기 호출은 불가능하다는 것입니다. 여러 스레드가 실행되고 있지만 인식하지 못하는 것과 같습니다. 이로 인해 큰 문제가 생길 수 있습니다 (일반적으로 감지하고 재현하기 어렵습니다).
jcaron

-4

std::asyncC ++ 11부터 C ++ 에서 사용할 수 있습니다 .

템플릿 함수 async는 함수 f를 비동기식으로 (잠재적으로 스레드 풀의 일부일 수있는 별도의 스레드에서) 실행하고 결국 해당 함수 호출의 결과를 보유 할 std :: future를 반환합니다.

그리고 C ++ 20과 함께 코 루틴을 사용할 수 있습니다 :


5
이것은 질문에 대답하지 않는 것 같습니다. 귀하의 링크에 따르면 : "코 루틴 TS는 무엇을 제공합니까? co_await, co_yield 및 co_return"이라는 세 가지 새로운 언어 키워드가 있습니다. 그러나 문제는 왜 await(또는 co_await이 경우) 키워드가 처음에 필요한가?
Arturo Torres Sánchez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.