0️⃣ Single Thread
Dart는 싱글 스레드 언어라는 말, 많이 들어보았을 것이다. Javscript도 싱글 스레드 언어이기 때문에 두 언어가 비슷하게 동작할 것이라고 보통 예상하지만 실상은 전혀 다르다. 우선 싱글 스레드가 무엇인지에 대해서부터 알아보자. (프로세스와 스레드의 개념에 대해서도 이해하고 있어야 하므로 아래 글을 먼저 읽고 와서 다시 보자!)
[얼레벌레 공부하는 CS] 프로세스와 스레드
0️⃣ 프로세스란?프로세스는 현재 실행중인 프로그램을 의미한다. 프로그램 그 자체는 보조기억장치에 보존되어있는 데이터지만 이 프로그램을 RAM에 적재하여 실행하는 순간 프로세스라고 부
kkevido.tistory.com
위 글을 다시 읽고 와서 스레드가 무엇인지 다시 정의해보자면,
- 프로세스가 할당 받은 자원을 이용하는 실행의 단위
- 크롬 브라우저는 멀티 프로세스로서 여러개의 탭을 실행시킬 수 있는 원리
- 하나의 탭 내에 있는 웹 사이트는 싱글 스레드 구조로 돌아감
이 정도로 설명할 수 있겠다. 크롬 브라우저가 멀티 프로세스라니... 왜 탭을 여러개 켜두면 컴퓨터가 죽어가는 지 알 수 있는 대목이다.
아무튼! 그렇다면 싱글 스레드가 굴러가는 원리를 그림으로 보아 더 쉽게 이해해보자.

하나의 프로세스에서 단하나의 스레드로만 실행하는 것이 바로 싱글 스레드다. 때문에 레지스터 값과 스택을 모두 하나씩만 갖고 있게 된다.
싱글스레드는 스레드를 하나만 관리하면 돼서 개발자가 직접 공유 메모리를 건들지 않아도 되기 때문에 비교적 프로그래밍 난이도가 쉽다.
| 장점 | 단점 |
| CPU와 메모리를 적게 사용함 | 연산량이 많은 작업을 수행하면 해당 작업이 끝날 때까지 다른 작업을 수행하지 못 함. (시간이 오래 걸림) |
그렇다면 Dart는 싱글 스레드 언어면서 어떻게 비동기를 실행할 수 있는걸까? Dart는 Thread 대신 Isolate를 사용하면서 멀티 스레드처럼 동작하고, 또 이벤트 루프를 사용하여 우리가 일반적으로 알고 있는 비동기를 처리하기 때문이다. 먼저 Isolate에 대해서 알아보자.
1️⃣ Dart는 싱글 스레드, OS에서 본 Flutter는 멀티 스레드!
Dart가 효율적으로 메모리를 관리하는 방법, DartVM
0️⃣ DartVM을 이해하기 전에 먼저 알아야 할 지식들 DartVM, 즉 Dart Virtual Machine에 대해서 제대로 알기 위해선 약간의 사전지식들이 필요하다. 기본적인 OS의 메모리 관리 개념과, Flutter의 release모드
kkevido.tistory.com
Isolate에 대해서 알아보자고 했지만, 이전에 포스팅한 내역이 있다. 내가 썼지만 제법 꼼꼼하고 이해하기 쉽게 써두었으니 읽어보고 나면 Dart의 싱글 스레드 원리를 더 쉽게 받아들일 수 있을 것이다.
우리는 지금 Dart에 대해서 공부하고 있으니 앞으로 Thread 대신 Isolate라는 용어를 사용하여 설명하도록 하겠다.
Flutter 앱을 실행하게 되면, OS 위로 해당 앱이 프로세스로서 돌아가기 시작하며 그 프로세스 안에는 단 하나의 Isolate가 동작하기 시작한다. 이것이 지금까지 우리가 공부한 내용을 토대로 이해한 Flutter의 동작 원리이다.

그럼 Isolate의 핵심 원칙은 무엇이냐!
Heap 영역을 공유하지 않고 각자 갖고 있는다.
분명 싱글 스레드인데, "각자" 라는 단어가 말이 되는가?
그렇다. 여기서 우리가 오늘 공부해야할 개념 compute라는 함수가 등장한다.
~ compute 함수 ~
우선 compute 함수 자체도 비동기를 도와주는 함수는 맞다. 어차피 Future, Stream 함수를 이용하여 비동기 함수를 만들 수 있는데 왜 하필 compute를 쓰는가?
- 하나의 메인 Isolate에서 사용자의 이벤트와 서버 통신, 각종 연산을 수행한다.
- 순간적으로 많은 작업을 처리할 때면 사용자의 화면이 버벅일 수 있다.
- 화면이 버벅대지 않게 하기 위해서 새로운 Isolate를 만들어 메인 Isolate와 분리된 공간에서 큰 연산을 처리한다.
- 메인 Isolate는 복잡한 연산을 처리하고 있지 않기 때문에 화면이 버벅이지 않는다.
이러한 이유로 compute를 쓰는 것이다. compute 함수는 Isolate를 하나 더 만들어 격리된 공간에서 큰 연산을 처리할 수 있도록 도와주고, 해당 연산이 끝나면 결과를 반환한 뒤에 메모리를 해제한다.
import 'package:flutter/foundation.dart';
Future<File> compressImage(File file) async {
var result = await compute(compressTask, file);
return result;
}
// [중요] 이 함수는 메인 스레드와 완전히 격리된 곳에서 돕니다.
File compressTask(File file) {
var result = heavyCompressionAlgorithm(file);
return result;
}
이미지 압축, 대용량 JSON 파싱 등 시간이 오래 걸리는 복잡한 연산 처리를 메인 Isolate에서 함께 처리하게 되면 사용자 경험이 저하되므로 compute를 이용하여 별도의 Isolate를 더 만든다. Isolate는 독립된 스레드이므로 서로 메모리를 공유하고 있지 않아 일반적인 스레드와 달리 동기화 문제를 전혀 신경쓰지 않아도 된다. 단, 메인 Isolate의 메모리 주소값을 참조하는 것이 아니라 그 안의 데이터를 통째로 복사해오는 깊은 복사를 하는 비용을 유의해야한다.
결과적으로는 '스레드'(Isolate)가 하나 더 만들어져 있기 때문에 Dart 자체만 놓고 본다면 Single Thread 언어처럼 느껴지지만
OS단에서 Flutter 앱을 보면 Multi Thread로 동작 하고 있는 것이다.
2️⃣ 그럼 Future와 Stream의 동작 원리는?
그렇다면 멀티 스레드처럼 동작하게 만들어주는 compute는 이해했다. 반대로 async/await을 사용하는 Future와 Stream 함수는 어떻게 동작할까? 여기서 바로 Event Loop(이벤트 루프) 개념이 등장한다. 이벤트 루프는 Javascript에서도 쓰이고 있는 개념이니 다들 익숙할 것이다. 하지만 난 안 익숙했다. 고로 Dart를 기준으로 이벤트 루프에 대해서 확실히 개념 정립을 해보겠다.
~ Event Loop ~
이벤트 루프는 말 그대로 Event를 처리하기 위해 무한히 도는 반복문이라서 이벤트 루프라는 이름이 붙었다. 영상으로 보면 훨씬 쉽게 이해될 것이다.
- DartVM: 실제 코드가 실행되는 곳
- Stack 메모리가 작동하는 곳
- main(), print('') 등의 함수와 동기 코드는 대기열을 거치지 않고 바로 DartVM 위에서 즉시 실행
- Microtask Queue: 우선순위 대기열
- scheduleMicrotask, Future.then 등으로 등록된 작업
- DartVM이 처리하던 작업이 끝나면 무조건 Microtask Queue를 먼저 봄
- Microtask Queue에 있는 모든 작업이 끝나지 않으면 절대 일반 대기열 (Event Queue)로 넘어가지 않음 (Jank 발생 / Infinite Loop 빠질 수 있음)
- Event Queue: 일반 대기열
- Future, Timer, Gesture, Rendering 등 일반 작업
- Microtask Queue가 텅 비어서 0개의 Event가 있을 때 Event Queue의 작업을 한 번에 하나씩 수행
❗ 출처
참고 사이트2 : https://incodom.kr/Flutter/Compute
참고 사이트3 : https://noguen.com/311
'Dart' 카테고리의 다른 글
| Dart가 효율적으로 메모리를 관리하는 방법, DartVM (4) | 2025.12.29 |
|---|---|
| Dart의 final과 const의 차이, 네트워크 관점에서 바라보기 (0) | 2025.12.15 |