비동기 프로그래밍과 멀티 스레딩의 차이점은 무엇입니까?


234

나는 그것들이 기본적으로 같은 것이라고 생각했습니다. 프로세서 (2+ 프로세서를 가진 머신에서) 사이에 작업을 나누는 프로그램을 작성하는 것입니다. 그리고 내가 읽고 있어요 , 어떤 말한다 :

비동기 메서드는 비 차단 작업입니다. 비동기 메서드의 대기 식은 대기중인 작업이 실행되는 동안 현재 스레드를 차단하지 않습니다. 대신, 표현식은 나머지 메소드를 연속으로 등록하고 비동기 메소드의 호출자에게 제어를 리턴합니다.

async 및 await 키워드는 추가 스레드를 생성하지 않습니다. 비동기 메서드는 자체 스레드에서 실행되지 않으므로 비동기 메서드에는 멀티 스레딩이 필요하지 않습니다. 메소드는 현재 동기화 컨텍스트에서 실행되며 메소드가 활성화 된 경우에만 스레드에서 시간을 사용합니다. Task.Run을 사용하여 CPU 바운드 작업을 백그라운드 스레드로 이동할 수 있지만 백그라운드 스레드는 결과를 사용할 수있을 때까지 기다리는 프로세스에는 도움이되지 않습니다.

누군가 나를 위해 영어로 번역 할 수 있는지 궁금합니다. 비동기 성 (단어입니까?)과 스레딩을 구별하는 것으로 보이며 비동기 작업은 있지만 멀티 스레딩은없는 프로그램을 가질 수 있음을 의미합니다.

이제 나는 pg의 예제와 같은 비동기 작업의 아이디어를 이해합니다. Jon Skeet의 C # In Depth, 3 판 467

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

async키워드 수단 " 이 기능은,이 호출 될 때마다, 그 호출이 호출 할 후에 완성이 모든 것을 요구되는 컨텍스트에서 호출되지 않습니다."

다른 말로, 일부 작업 중간에 작성

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

이후 DisplayWebsiteLength()로는 아무 상관이없는 x이상을 y일으킬 것 DisplayWebsiteLength()같은, "백그라운드에서"실행되는

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

분명히 그것은 어리석은 예이지만, 나는 정확하거나 완전히 혼란 스럽거나 무엇입니까?

(또한, 나는 이유에 대해 혼란스러워하고있어 sender그리고 e이제까지 위의 함수의 본문에 사용되지 않습니다.)


13
이것은 좋은 설명입니다 : blog.stephencleary.com/2013/11/there-is-no-thread.html
Jakub

sender그리고 e이것이 실제로 이벤트 처리기라고 제안 하고 있습니다. 거의 유일한 곳 async void이 바람직한 곳 입니다. 대부분 버튼 클릭 또는 이와 유사한 방식으로 호출됩니다. 결과적으로이 작업은 나머지 응용 프로그램과 관련하여 완전히 비동기 적으로 발생합니다. 그러나 여전히 UI 스레드 (UI 스레드에 콜백을 게시하는 IOCP 스레드에서 약간의 시간이 소요됨) 인 하나의 스레드에 있습니다.
Luaan


3
DisplayWebsiteLength코드 샘플 에 대한 매우 중요한 참고 사항 : 명령문 HttpClient에서 사용하지 않아야 using합니다.-로드가 과중하면 코드에서 사용 가능한 소켓 수를 소진하여 SocketException 오류가 발생할 수 있습니다. 부적절한 인스턴스화 에 대한 추가 정보 .
Gan

1
@JakubLortz 기사가 실제로 누구인지 모르겠습니다. 초보자에게는 그렇지 않습니다. 스레드, 인터럽트, CPU 관련 사항 등에 대한 지식이 필요하기 때문입니다. 고급 사용자에게는 해당되지 않으므로 이미 명확합니다. 나는 그것이 너무 높은 추상화 수준에 관한 모든 것을 이해하는 데 도움이되지 않을 것이라고 확신합니다.
Loreno

답변:


589

당신의 오해는 매우 일반적입니다. 많은 사람들이 멀티 스레딩과 비동기가 같은 것임을 알고 있지만 그렇지 않습니다.

유추는 일반적으로 도움이됩니다. 식당에서 요리하고 있습니다. 계란과 토스트 주문이 들어옵니다.

  • 동기식 : 계란을 요리 한 다음 토스트를 요리합니다.
  • 비동기식 단일 스레드 : 계란 요리를 시작하고 타이머를 설정합니다. 토스트 요리를 시작하고 타이머를 설정합니다. 둘 다 요리하는 동안 부엌을 청소하십시오. 타이머가 꺼지면 계란을 불에서 꺼내고 토스트를 토스터에서 꺼냅니다.
  • 비동기식, 멀티 스레드 : 계란 요리와 토스트 요리를 위해 두 명의 요리사를 더 고용합니다. 이제 자원을 공유 할 때 요리사가 부엌에서 서로 충돌하지 않도록 요리사를 조정하는 데 문제가 있습니다. 그리고 당신은 그들을 지불해야합니다.

이제 멀티 스레딩은 한 가지 종류의 비동기 성이라는 것이 합리적입니까? 스레딩은 작업자에 관한 것입니다. 비동기는 작업에 관한 것 입니다. 다중 스레드 워크 플로에서는 작업을 작업자에게 할당합니다. 비동기식 단일 스레드 워크 플로에는 일부 작업이 다른 작업의 결과에 의존하는 작업 그래프가 있습니다. 각 작업이 완료되면 방금 완료된 작업의 결과를 고려하여 실행할 수있는 다음 작업을 예약하는 코드를 호출합니다. 그러나 모든 작업을 수행하는 데는 한 명의 작업자 만 필요하고 작업 당 한 명의 작업자 만 필요하지는 않습니다.

많은 작업이 프로세서 바운드가 아님을 인식하는 데 도움이됩니다. 프로세서 바운드 작업의 경우 프로세서 수만큼 작업자 (스레드)를 고용하고, 각 작업자에게 하나의 작업을 할당하고, 각 작업자에게 하나의 프로세서를 할당하고, 각 프로세서가 결과를 계산하는 것 이외의 다른 작업을 수행하도록하는 것이 합리적입니다. 가능한 빨리. 그러나 프로세서에서 대기하지 않는 작업의 경우 작업자를 전혀 할당 할 필요가 없습니다. 결과가 제공된다는 메시지가 도착할 때까지 기다렸다가 기다리는 동안 다른 작업을 수행하면 됩니다. 해당 메시지가 도착하면 다음에 할 일 목록에서 완료된 작업의 지속을 체크 아웃하도록 예약 할 수 있습니다.

Jon의 예제를보다 자세히 살펴 보겠습니다. 무슨 일이야?

  • 누군가가 DisplayWebSiteLength를 호출합니다. WHO? 우리는 상관하지 않습니다.
  • 레이블을 설정하고 클라이언트를 만들고 클라이언트에게 무언가를 가져 오도록 요청합니다. 클라이언트는 무언가를 가져 오는 작업을 나타내는 객체를 반환합니다. 작업이 진행 중입니다.
  • 다른 스레드에서 진행 중입니까? 아마 아닙니다. 스레드가없는 이유에 대한 Stephen의 기사 를 읽으십시오 .
  • 이제 우리는 작업을 기다립니다. 무슨 일이야? 우리는 작업이 생성 된 시간과 대기 한 시간 사이에 작업이 완료되었는지 확인합니다. 그렇다면 결과를 가져 와서 계속 실행합니다. 완료되지 않았다고 가정 해 봅시다. 이 방법의 나머지 부분을 해당 작업의 계속으로 등록하고 반환 합니다.
  • 이제 제어가 호출자에게 돌아 왔습니다. 무엇을합니까? 원하는 것이 무엇이든.
  • 이제 작업이 완료되었다고 가정하십시오. 어떻게 했어요? 다른 스레드에서 실행 중이거나 방금 반환 한 호출자가 현재 스레드에서 완료되도록 실행할 수 있습니다. 어쨌든, 이제 우리는 완료된 작업이 있습니다.
  • 완료된 작업은 올바른 스레드 (다시, 유일한 스레드 일 수도 있음)에게 작업의 계속을 실행 하도록 요청합니다 .
  • 제어는 대기 지점에서 방금 떠난 방법으로 즉시 다시 전달됩니다. 이제이 있다 우리가 지정할 수 있도록 결과 사용할 수 text및 방법의 나머지 부분을 실행합니다.

그것은 내 비유에서와 같습니다. 누군가 문서를 요구합니다. 문서를 우편으로 보내고 다른 작업을 계속하십시오. 우편물에 도착하면 신호를 받고 기분이 좋으면 나머지 작업 과정을 수행합니다. 봉투를 열고 배송료를 지불하십시오. 당신을 위해 모든 일을하기 위해 다른 일꾼을 고용 할 필요는 없습니다.


8
@ user5648283 : 하드웨어는 작업을 생각하기에 잘못된 수준입니다. 작업은 단순히 (1) 미래에 값을 사용할 수 있고 (2) 해당 값을 사용할 수있을 때 올바른 스레드에서 코드를 실행할 수 있음을 나타내는 개체입니다 . 개별 작업이 미래에 결과를 얻는 방법은 전적으로 달려 있습니다. 일부는이를 위해 "디스크"및 "네트워크 카드"와 같은 특수 하드웨어를 사용합니다. 일부는 CPU와 같은 하드웨어를 사용합니다.
Eric Lippert

13
@ user5648283 : 다시, 내 비유에 대해 생각하십시오. 누군가가 계란과 토스트를 요리하도록 요청하면 스토브와 토스터와 같은 특수 하드웨어를 사용하며 하드웨어가 작동하는 동안 부엌을 청소할 수 있습니다. 누군가가 계란, 토스트 및 마지막 호빗 영화에 대한 독창적 인 비판을 요구하는 경우 계란과 토스트가 요리되는 동안 리뷰를 작성할 수 있지만 하드웨어를 사용할 필요는 없습니다.
Eric Lippert

9
@ user5648283 : 이제 "코드 재 배열"에 대한 질문에 이것을 고려하십시오. 수율 리턴이있는 메소드 P와 P의 결과에 대해 foreach를 수행하는 메소드 Q가 있다고 가정하십시오. 코드를 단계별로 실행하십시오. 우리는 약간의 Q, 약간의 P, 약간의 Q를 실행한다는 것을 알 수 있습니다. 그 점을 이해하십니까? 기다림은 본질적으로 멋진 드레스의 수익률 반환입니다 . 이제 더 명확합니까?
Eric Lippert

10
토스터는 하드웨어입니다. 하드웨어에는 서비스를 제공하기 위해 스레드가 필요하지 않습니다. 디스크 및 네트워크 카드 및 OS 스레드보다 훨씬 낮은 수준에서 실행되지 않는 항목
Eric Lippert

5
@ ShivprasadKoirala : 그것은 전혀 사실아닙니다 . 당신이 그것을 믿는다면, 당신은 비동기성에 대해 매우 잘못된 믿음 을 가지고 있습니다 . C #에서 비동기의 핵심은 스레드를 만들지 않는다는 것입니다.
Eric Lippert

27

브라우저 내 Javascript는 스레드가없는 비동기 프로그램의 좋은 예입니다.

동일한 객체를 동시에 터치하는 여러 코드 조각에 대해 걱정할 필요가 없습니다. 페이지에서 다른 자바 스크립트를 실행하기 전에 각 함수가 실행을 완료합니다.

그러나 AJAX 요청과 같은 작업을 수행 할 때 코드가 전혀 실행되지 않으므로 다른 자바 스크립트는 요청이 다시 발생하여 관련 콜백을 호출 할 때까지 클릭 이벤트와 같은 것에 응답 할 수 있습니다. AJAX 요청이 다시 수신 될 때 이러한 다른 이벤트 핸들러 중 하나가 계속 실행중인 경우 완료 될 때까지 핸들러가 호출되지 않습니다. 필요한 정보를 얻을 때까지 수행 한 작업을 효과적으로 일시 중지 할 수는 있지만 JavaScript "스레드"는 하나만 실행됩니다.

C # 응용 프로그램에서는 UI 요소를 처리 할 때마다 같은 일이 발생합니다. UI 스레드에있을 때만 UI 요소와 상호 작용할 수 있습니다. 사용자가 단추를 클릭하고 디스크에서 큰 파일을 읽음으로써 응답하려는 경우, 숙련되지 않은 프로그래머가 click 이벤트 핸들러 자체 내에서 파일을 읽는 실수를 저지를 수 있습니다. 해당 스레드가 해제 될 때까지 더 이상 클릭, 호버링 또는 기타 UI 관련 이벤트에 응답 할 수 없으므로 파일로드가 완료되었습니다.

프로그래머 가이 문제를 피하기 위해 사용할 수있는 한 가지 옵션은 파일을로드 할 새 스레드를 만든 다음 파일을로드 할 때 UI 스레드에서 나머지 코드를 다시 실행하여 UI 요소를 업데이트해야한다는 스레드 코드를 알려주는 것입니다 파일에서 찾은 내용을 기반으로합니다. 최근까지이 접근 방식은 C # 라이브러리와 언어가 쉬워 졌기 때문에 매우 인기가 있었지만, 그보다 근본적으로 더 복잡했습니다.

하드웨어 및 운영 체제 수준에서 파일을 읽을 때 CPU가 수행하는 작업에 대해 생각하면 기본적으로 디스크에서 메모리로 데이터 조각을 읽고 "인터럽트를 사용하여 운영 체제에 충돌하는 명령을 내립니다. 읽기가 완료되면 " 다시 말해 디스크에서 읽기 (또는 실제로 I / O)는 본질적으로 비동기 작업입니다. 해당 I / O가 완료되기를 기다리는 스레드의 개념은 라이브러리 개발자가 프로그래밍하기 쉽도록 만든 추상화입니다. 불필요하다.

이제 .NET의 대부분의 I / O 작업에는 ...Async()호출 할 수 있는 해당 메서드가 있으며 Task거의 즉시 반환 됩니다. 이것 Task에 콜백을 추가 하여 비동기 작업이 완료 될 때 실행할 코드를 지정할 수 있습니다 . 또한 해당 코드를 실행할 스레드를 지정하고 비동기 작업이 수시로 확인하여 비동기 작업을 취소하기로 결정했는지 확인할 수있는 토큰을 제공하여 작업을 신속하게 중지 할 수있는 기회를 제공합니다 그리고 우아하게.

async/await키워드가 추가 될 때까지 C #은 콜백 코드가 호출되는 방식에 대해 훨씬 분명했습니다. 콜백은 작업과 관련된 대리자 형식 이었기 때문입니다. ...Async()코드의 복잡성을 피하면서 작업 사용의 이점을 계속 제공하기 위해 async/await이러한 델리게이트 생성을 추상화합니다. 그러나 여전히 컴파일 된 코드에 있습니다.

따라서 UI 이벤트 처리기 await에 I / O 작업을 수행하여 다른 작업을 수행 할 수 있도록 UI 스레드를 비우거나 파일을 읽은 후 UI 스레드로 자동으로 돌아갈 수 있습니다. 새 스레드를 만듭니다.


하나의 JavaScript "스레드"만 실행됩니다 . 더 이상 Web Workers 에는 적용되지 않습니다 .
oleksii 2016 년

6
@oleksii : 기술적으로 사실이지만 웹 워커 API 자체가 비동기적이고 웹 워커가 호출 한 웹 페이지의 자바 스크립트 값이나 DOM에 직접 영향을 줄 수 없기 때문에 그에 대해서는 언급하지 않았습니다. from,이 답변의 중요한 두 번째 단락이 여전히 유효 함을 의미합니다. 프로그래머의 관점에서 보면 웹 워커 호출과 AJAX 요청 호출 사이에는 차이가 거의 없습니다.
StriplingWarrior
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.