일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- dart
- dart new 키워드
- widget
- flutter bloc
- Architectural overview
- 파이썬 부동소수점
- flutter ios 폴더
- tft api dart
- dart new
- 롤토체스 api dart
- generate parentheses dart
- 롤 api dart
- flutter android 폴더
- leetcode dart
- PlatformException(sign_in_failed
- com.google.GIDSignIn
- valorant api dart
- keychain error
- 발로란트 api dart
- riot api dart
- flutter widget
- AnimationController
- dart.dev
- 파이썬
- flutter
- flutter statefulwidget
- swift concurrency
- swift 동시성
- docker overview
- lol api dart
- Today
- Total
Coaspe
Dart - Concurrency in Dart 본문
Dart는 async-await, isolates나 Future나 Stream 같은 classes로 concurrent programming을 지원합니다. 이 페이지에서는 async-await, Future, Stream에 대해 전반적인 설명을 합니다.(주로 isolates에 대해)
앱 안에서, 모든 Dart 코드는 isolate 안에서 작동됩니다. 각각의 Dart isolate는 하나의 실행 쓰레드를 가지고, 서로 객체들을 공유하지 않습니다. 서로 통신하고 싶다면, isolates는 message passing을 사용합니다. 비록 Dart의 isolate 모델은 운영체제가 제공하는 프로세스, 스레드 같은 기본 요소로 구현되어 있지만, Dart VM의 이러한 기본 요소의 사용은 이 페이지에서 다루지 않는 구현의 세부사항 입니다.
많은 Dart 앱들은 오직 한 개의 isolate(main isolate)를 사용하지만, 다수의 프로세서 코어에서 병렬 코드 실행을 가능하게 하는 추가적인 isolate 생성도 가능합니다.
Platfrom note: 모든 앱들은 async-await, Future, Stream을 사용 할 수 있습니다. Isolates들은 Dart Native platform 에 구현되어 있습니다.
비동기 타입들과 문법
Futures와 Streams 타입
Dart 언어와 라이브러리들은 Future와 Stream 객체를 어떤 값이 미래에 얻어진다는 것을 표현하기 위해 사용합니다. 예를 들면, 결국 int 값을 나중에 얻게 된다는 약속은 Future<int>와 같이 타입이 설정 됩니다. 앞의 예에서 int의 series를 얻게 된다면 Stream<int>가 됩니다.
다른 예로, 파일을 읽기 위해 dart:io를 사용한다고 해봅시다. 동기적인 file 메소드인 readAsStringSync() 는 파일은 동기적으로 읽어오면서, 에러가 발생하거나 파일을 완전히 읽기 전까지 다른 요청들을 막습니다. 그 메소드는 결국 String을 반환하거나 예외를 발생시킵니다. 앞의 메소드와 동일한 역할을 하는 비동기적인 함수인 readAsString() 는 즉시 Future<String> 객체를 반환합니다. 미래의 어떤 시점에 Future<String>은 stirng 값이나 에러를 반환합니다.
왜 비동기 코드가 중요한가?
대부분의 앱들은 동시에 여러가지 일을 하는 것이 필요하므로 어떤 메소드가 비동기적인지 아닌지는 중요합니다.
비동기적인 계산은 종종 현재의 Dart 코드 밖에서 수행된 계산의 결과 입니다. 여기에는 즉시 완료되지 않는 계산과 결과를 기다리는 Dart 코드를 차단할 의사가 없는 계산들이 포합됩니다.예를 들면, 어떤 앱이 HTTP 요청을 시작 할 때, HTTP 요청이 완료되기 전에 디스플레이를 업데이트 하거나 유저의 인풋에 반응해야 한다면 비동기적인 코드는 반응형 웹이 될 수 있게 도와줍니다.
이런 시나리오들은 non-blocking I/O, HTTP 요청, 브라우져와 커뮤니케이션 같은 운영체제 호출을 포함합니다. 다른 시나리오들로는 아래에 나와있는 것 처럼 다른 Dart isolates에서 실행되는 계산을 기다린다던가, 타이머가 끝나길 기다리는 것 들이 있습니다. 이 모든 작업들은 다른 스레드에서 실행되거나, 운영체제나 Dart 코드가 동시에 컴퓨테이션 할 수 있게 해주는 Dart 런타임에 의해 다루어 집니다.
async-await 문법
asnyc 와 await 키워드는 비동기적인 함수와 그것의 결과를 사용할 수 있게 선언적인 방법을 제공합니다.
다음은 파일 I/O를 기다리게 다른 작업들을 blocking하는 동기적인 코드의 예입니다.
다음은 비동기적으로 바꾼 코드입니다.
main() 함수의 _readFileAsync() 앞에 await 키워드를 사용하여 native code(파일 I/O)가 실행되는 동안 다른 Dart 코드(이벤트 핸들러 같은 것 들)가 CPU를 사용 할 수 있게 합니다. await를 사용하는 것은 _readFileAsync()의 반환값인 Future<String>를 String으로 변환해주는 역할을 합니다. 결과적으로, contents 변수는 암묵적으로 String 타입을 가집니다.
Note: await 키워드는 함수 바디 앞에 붙는 async 키워드가 있는 함수에서만 사용이 가능합니다.
다음의 그림에서 볼 수 있듯이, readAsString()은 Dart 코드가 아닌 코드(Dart 가상 머신, 운영체제)들을 실행 시킬 때 Dart 코드를 중지시킵니다.
async와 await, futures에 대해 더 자세히 알고 싶다면, asynchronous programming codelab 를 확인해보세요.
Isolates는 어떻게 작동하는가?
대부분의 요즘 디바이스들은 멀티 코어 CPU를 가지고 있습니다. 이런 코어들을 잘 이용하기 위해서, 개발자들은 가끔 동시에 코드를 돌리기 위해 공유 메모리 스레드를 사용합니다. 그러나, 상태를 공유하는 동시성은 에러를 발생시키기 쉽고, 복잡한 코드를 만듭니다.
스레드 대신, 모든 Dart 코드는 isolates 안에서 실행 됩니다. 각각의 isolate는 다른 isolate가 접근 할 수 없는 메모리 힙을 가지고 있습니다. 공유 메모리가 없기 때문에 mutexes or locks 를 걱정 할 필요가 없습니다.
Isolate를 사용하여, Dart 코드가 추가 프로세서 코어를 사용할 수 있는 경우 동시에 다수의 독립된 작업을 수행 할 수 있습니다. Isolates는 스레드, 프로세스와 비슷하지만, 각각의 isolate는 이벤트 루프를 돌리는 하나의 스레드와 독립된 메모리를 사용합니다.
Main Isolate
종종 isolates에 대해 전혀 알 필요가 없을 수 있다. 다음 그림과 같이 전형적인 Dart 앱은 모든 코드를 앱의 main isolate에서 실행합니다.
하나의 isolate만 사용하는 프로그램들이라도, async-await을 사용하여 비동기 작업이 완료될 때까지 기다린 후 다음 줄의 코드로 계속 진행함으로써 원활하게 실행될 수 있습니다. 제대로 작동하는 앱은 빠르게 시작하고 이벤트 루프에 최대한 빨리 도달합니다. 그 다음에 앱은 필요하다면 비동기적인 명령을 사용하여 큐에 들어와 있는 이벤트에 즉시 반응 할 수 있습니다.
Isolate의 생명 주기
다음 그림에서 볼 수 있듯이, 모든 isolate는 main() 함수같은 Dart 코드를 실행하면서 시작됩니다. 예를 들어, 이 Dart 코드는 아마도 유저의 인풋이나 파일 I/O에 반응하기 위해 이벤트 리스너를 관리하는 것일 겁니다. isolate의 초기 함수가 반환할 때, 이벤트를 처리해야 할 경우 isolate는 계속 유지됩니다. 이벤트를 핸들링 한 후, isolate는 종료됩니다.
이벤트 핸들링
클라이언트에 앱에서, main isolate의 이벤트 큐는 아마 UI 이벤트나 탭의 요청, 알림을 다시 그리는 작업을 포함 할 것입니다. 이벤트 루프는 큐로 부터 first in, first out 순서로 이벤트를 처리합니다.
이벤트 핸들링은 main() 이 종료된 뒤 main isoalte에서 실행됩니다. 다음 그림에서, main()이 종료된 뒤, main isolate가 첫 번째 repaint 이벤트를 핸들합니다. 그 다음, main isolate는 repaint 이벤트가 따라오는 tap 이벤트를 핸들합니다.
동기적인 명령어가 너무 긴 처리 시간이 걸리면, 앱이 다른 요청에 반응하지 못 할 수 있습니다. 다음 그림에서, tap 핸들링 코드의 처리 시간이 너무 오래 걸려서, 뒤 따라오는 이벤트의 처리가 뒤늦게 동작합니다. 앱은 가만히 있는 것 처럼 보이고, 애니메이션은 버벅 거리며 실행 될 것 입니다.
클라이언트 앱에서, 처리 시간이 너무 긴 동기적인 명령어는 janky (non-smooth) UI animation을 발생시킵니다. 더 운이 안 좋다면, UI가 멈춰버릴 것 입니다.
Background workers
각 isolate 메세지는 하나의 객체를 보낼 수 있고, 여기에는 해당 객체에서 일시적으로 도달할 수 있는 모든 객체가 포함됩니다. 모든 객체를 보낼 수 있는 건 아닙니다. 그리고 일시적으로 도달할 수 있는 객체가 보낼 수 없는 상태라면 전송은 실패합니다. 예를 들어, 만약 List<Object> 안의 어떤 Object도 보낼 수 없는 상태가 아니라면, 전송이 가능합니다. 만약 객체들 중 하나가 Socket이라면, sockets은 전송 할 수 없기 때문에 전송은 실패합니다.
보낼 수 있는 객체들에 대해 알고 싶다면 send() method 를 참고하세요.
Worker isolate는 I/O(예를 들면, 파일을 읽고 쓰기), 타이머 설정 같은 작업을 수행합니다. 그리고 main isolate와 공유하지 않는 각자의 메모리를 가집니다. Worker isolate는 다른 isolates에 영향을 주지 않고 블락하는 게 가능합니다.
Code examples
이번 섹션에서는 Isolate API를 사용해 isolates를 구현합니다.
Flutter note: 만약 Flutter를 web이 아닌 platform에서 사용하고 있다면, Isolate API를 직접적으로 사용하는 대신, Flutter compute() function 을 사용하는 것을 고려해보세요. compute() 함수는 하나의 함수 호출을 worker isolate로 실행하는 심플한 방법 입니다.
간단한 worker isolate 구현하기
이번 섹션에서 main isolate와 main isolate가 만드는 간단한 worker isolate의 구현을 해봅니다. Worker isolate는 함수를 실행 시키고 main isolate에 종료한다는 메시지를 전송하며 종료합니다. (Flutter compute() function works가 비슷한 방법으로 실행됩니다.)
이번 예제에서 다음의 isolate API를 사용합니다.
다음은 main isolate 코드 입니다.
_parseInBackground() 함수는 백그라운드 worker를 위한 isolate를 만드는 코드를 가지고 있고 다음의 결과를 반환합니다.
Isolate를 만들기 전에, 코드는 work isolate가 main isolate로 메세지를 보낼 수 있게 해주는 ReceivePort를 생성합니다.
다음은 백그라운드 worker를 위한 isolate를 생성, 실행시키는 Isolate.spawn()을 호출합니다. Isolate.spawn()의 첫번째 인자는 worker isolate가 실행시킬 함수인 _readAndParseJson 입니다. 두번째 인자는 work isolate가 main isolate로 메세지를 보내기 위해 사용하는 SendPort 입니다. 이 코드에서 SendPort를 생성하지 앟고, ReceivePort의 sendPort 속성을 사용합니다.
Isolate가 생성되면, main isolate는 결과를 기다립니다. ReceiveProt클래스는 Stream을 구하기 때문에, first 속성은 worker isolate가 보낸 첫 번째 메세지를 받기 쉬운 방법 입니다.
생성된 isolate는 다음의 코드를 실행합니다.
isolate api와 관련된 라인은 isolate를 종료시키는 마지막 라인으로, SendPort로 jsonData를 전송합니다. SendPort.send는 보통 데이터를 복사하기 때문에 속도가 느려질 수 있습니다. 그러나, Isolate.exit()을 사용하여 데이터를 전송 할 때, 종료하는 isolate의 메세지를 hold하고 있는 메모리는 복제되지 않지만 전송받는 isolate에게 전달됩니다. 그럼에도 불구하고 전송자는 객체 전송이 허용되는지 확인하기 위해 verification pass를 수행합니다.
Version note: Isolate.exit() 은 2.15 버젼에 추가되었습니다. 이전의 릴리즈는 다음 섹션의 예제에서 나오는 Isolate.send()를 사용한 명시적인 메세지 전송만 지원합니다.
다음 그림은 main isolate와 worker isolate 사이의 커뮤니케이션 삽화 입니다.
Isolates 사이에 여러개의 메세지 보내기
만약 isolates 사이에 더 많은 커뮤니케이션을 원한다면, SendPort의 send() method를 사용하면 됩니다. 다음 그림과 비슷하게 흔한 패턴으로는, main isolate가 worker isolate로 요청 메세지를 보내고 한개 혹은 여러개의 답장을 보내는 것 입니다.
여러개의 메세지를 전송하는 예제는 다음을 참고하세요, isolate samples:
- send_and_receive.dart, which shows how to send a message from the main isolate to the spawned isolate. It’s otherwise similar to the preceding example.
- long_running_isolate.dart, which shows how to spawn a long-running isolate that receives and sends multiple times.
성능과 Isolate group
Isolate가 Isolate.spawn()를 호출 할 때, 두 isolates는 같은 실행가능한 코드를 가지고 있고 같은 isolate group에 있게 됩니다.
Isolate groups는 코드 공유 같은 성능 최적화를 가능하게 합니다. 새로운 isolate는 isolate group이 가지고 있는 코드를 즉시 실행합니다. 또한 Isolate.exit()은 오직 같은 isolate group에 있을 때 실행됩니다.
특별한 경우에, 특정 URI에 있는 코드의 복사로 새로운 isolate를 설정하는 Isolate.spawnUri()를 사용 할 수도 있습니다. 그러나, spawnUri()는 spawn() 보다 훨씬 느립니다. 그리고 새로운 isolate는 그것을 생성한 isolate와 같은 isolate group에 있지 않습니다. 또 다른 성능의 최적화에 중요한 요소는 메세지 전송은 isolates가 서로 다른 groups에 있을 때 더 느리다는 것 입니다.
'Dart > API' 카테고리의 다른 글
Dart - StreamController (0) | 2023.02.17 |
---|---|
Dart/API - new Keyword를 사용하는 이유 (0) | 2022.11.26 |