다트는 async-await, isolate 그리고 Future와 Stream과 같은 클래스를 이용하여 동시성 프로그래밍를 지원합니다.
앱 내에서 모든 다트 코드는 격리되어 실행됩니다. 각 Dart isolate는 단일 실행 스레드를 가지며 다른 isolate와 객체를 변경할 수 있도록 공유하지 않는습니다. 대신 서로 통신하기 위해 메시지 전달을 사용합니다. Dart의 isolate(격리) 모델은 운영 체제가 제공하는 프로세스 및 스레드와 같은 기본 기본 요소로 구축됩니다.
Asynchrony types and syntax
1. The async-await syntax
void main() {
// Read some data.
final fileData = _readFileSync();
final jsonData = jsonDecode(fileData);
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
String _readFileSync() {
final file = File(filename);
final contents = file.readAsStringSync();
return contents.trim();
}
위 코드를 비동기식으로 만들면 다음과 같습니다.
void main() async {
// Read some data.
final fileData = await _readFileAsync();
final jsonData = jsonDecode(fileData);
// Use that data.
print('Number of JSON keys: ${jsonData.length}');
}
Future<String> _readFileAsync() async {
final file = File(filename);
final contents = await file.readAsString();
return contents.trim();
}
위 내용은 대부분 알고 계신 내용입니다. 이제 실제로 어떻게 isolate(격리) 하는지 보겠습니다.
대부분의 최신 장치들은 멀티 코어 CPU를 갖추고 있습니다. 이러한 모든 코어를 활용하기 위해 개발자들은 동시에 실행되는 공유 메모리 스레드를 사용하기도 합니다. 그러나 공유 상태에서의 동시성은 오류가 발생하기 쉬우며 복잡한 코드를 초래할 수 있습니다.
스레드 대신 모든 다트 코드는 격리된 내부에서 실행됩니다. 각 격리된 공간은 자체 메모리 힙을 가지고 있어, 격리된 다른 공간에서 접근할 수 없도록 합니다. 공유 메모리가 없기 때문에 뮤텍스나 잠금에 대해 걱정할 필요가 없습니다.
isolate를 사용하면 Dart 코드가 필요한 경우 추가 프로세서 코어를 사용하여 한 번에 여러 개의 독립적인 작업을 수행할 수 있습니다. 격리된 것은 스레드나 프로세스와 비슷하지만 각각 자체 메모리와 이벤트 루프를 실행하는 단일 스레드를 가지고 있습니다.
Main isolate
일반적으로 다트 앱은 아래 그림과 같이 Main isolate에서 모든 코드를 실행합니다.
단일 Isolate 프로그램도 비동기 await를 사용하여 다음 줄의 코드로 계속하기 전에 비동기 작업이 완료될 때까지 기다림으로써 원활하게 실행될 수 있습니다. 잘 작동하는 앱은 빠르게 시작하여 이벤트 루프에 최대한 빨리 도달합니다. 그런 다음 앱은 필요에 따라 비동기 작업을 사용하여 대기 중인 각 이벤트에 즉시 응답합니다.
isolate 라이프 사이클
다음 그림에서 알 수 있듯이, 모든 isolate는 Main 함수와 같은 일부 다트 코드를 실행하는 것으로 시작합니다. 그림의 Dart 코드는 사용자 입력이나 파일 I/O에 응답하기 위해 일부 이벤트 수신을 등록할 수 있습니다. isolate의 초기 기능이 반환될 때, isolate는 이벤트를 처리해야 할 경우 주변에 머무릅니다. 이벤트를 처리한 후 격리된 상태가 종료됩니다.
이벤트 핸들링
클라이언트 앱에서 isolate의 이벤트 대기열에는 Tap과 같은 UI 이벤트로 인해 다시 그리기 요청과 알림이 포함될 수 있습니다. 예를 들어, 다음 그림은 Repaint 이벤트와 Tap 이벤트, 두 가지 Repaint 이벤트를 보여 줍니다. 이벤트 루프는 대기열에서 이벤트를 선입선출(FIFO) 순으로 가져옵니다.
동기식 작업에 처리 시간이 너무 많이 걸리면 앱이 응답하지 않을 수 있습니다. 다음 그림에서는 Tap 처리 코드가 너무 오래 걸려서 후속 이벤트가 너무 늦게 처리됩니다. 앱이 정지된 것처럼 보일 수 있으며 앱에서 수행하는 애니메이션은 janky(부드럽지 않은) 일 수 있습니다.
클라이언트 앱에서 너무 긴 동기화 작업의 결과는 종종 janky(부드럽지 않은) UI 애니메이션입니다. 더 나쁜 것은 UI가 완전히 응답하지 않을 수 있다는 것입니다.
예를 들어 큰 JSON 파일을 구문 분석하는 등 시간이 많이 걸리는 계산으로 인해 앱의 UI가 응답하지 않는 경우, 해당 계산을 백그라운드 작업자 격리(background worker)로 오프로드하는 것을 고려해 보십시오. 다음 그림에 표시된 일반적인 경우는 계산을 수행한 다음 종료하는 단순한 worker isolate를 생성하는 것입니다. worker isolate는 worker가 종료될 때 메시지로 결과를 반환합니다.
각각의 격리된 메시지는 하나의 객체를 전달할 수 있으며, 여기에는 해당 객체에서 도달할 수 있는 모든 것이 포함됩니다. 일부 개체 유형은 보낼 수 없으며, 전송이 불가능한 개체가 있으면 전송이 실패합니다.
예를 들어 목록에 보낼 수 없는 개체가 없는 경우에만 List<Object> 유형의 개체를 보낼 수 있습니다. 객체 중 하나가 소켓이라면 소켓을 보낼 수 없기 때문에 전송이 실패합니다.
메시지를 보낼 수 있는 개체의 종류에 대한 자세한 내용은 send() 메서드에 대한 API 참조 문서를 참조하세요.
worker isolate는 I/O(예: 파일 읽기 및 쓰기), 타이머 설정 등을 수행할 수 있습니다. 그것은 자체 메모리를 가지고 있고 Main isolate와 어떠한 상태도 공유하지 않습니다. worker isolate는 다른 isolate에 영향을 미치지 않고 차단할 수 있습니다.
간단한 worker isolate 만들어 보기
void main() async {
// Read some data.
final jsonData = await _parseInBackground();
// Use that data
print('number of JSON keys = ${jsonData.length}');
}
// Spawns an isolate and waits for the first message
Future<Map<String, dynamic>> _parseInBackground() async {
final p = ReceivePort();
await Isolate.spawn(_readAndParseJson, p.sendPort);
return await p.first;
}
_parseInBackground() 함수는 백그라운드 작업자에 대한 분리 파일을 생성하고 시작한 다음 결과를 반환하는 코드를 포함합니다.
- isolate를 생성하기 전에 코드는 worker isolate가 main isolate로 메시지를 보낼 수 있도록 하는 ReceivePort를 생성한다.
- 다음은 Isolate.spawn()에 대한 호출로, 백그라운드 작업자에 대한 분리를 생성하고 시작합니다. Isolate.spawn()의 첫 번째 인수는 worker isolate가 실행하는 함수입니다.(_readAndParseJson).두 번째 인수는 작업자 격리가 메시지를 메인 격리로 보내기 위해 사용할 수 있는 SendPort이다. 코드는 SendPort를 만들지 않으며, ReceivePort의 SendPort 속성을 사용합니다.
- isolate가 생성되면 기존의 isolate는 결과를 기다립니다. ReceivePort 클래스는 Stream을 구현하기 때문에 첫 번째 속성은 작업자가 분리하여 보내는 단일 메시지를 가져오는 쉬운 방법입니다.
isolate의 실행코드는 다음과 같습니다.
Future _readAndParseJson(SendPort p) async {
final fileData = await File(filename).readAsString();
final jsonData = jsonDecode(fileData);
Isolate.exit(p, jsonData);
}
마지막으로 iolate를 종료하여 전달된 SendPort로 jsonData를 전송합니다. 격리된 공간 사이의 메시지 전달은 일반적으로 데이터 복사를 포함하므로 메시지의 크기가 커질수록 속도가 느려질 수 있습니다. 그러나 Isolate.exit()를 사용하여 데이터를 전송하면 기존 isolate에 메시지를 보관하는 메모리가 복사되지 않고 수신 isolate로 바로 전송됩니다. 이 전송은 빠르고 일정한 시간 안에 완료됩니다. O(1).
isolate 간에 멀티 메세지 보내기
https://dart.dev/guides/language/concurrency
'Flutter(플러터) > Dart' 카테고리의 다른 글
Dart 2.15 (0) | 2021.12.29 |
---|
댓글