현장실습 중 프로젝트를 진행하다가 동기, 비동기 개념과 맞닥뜨렸다.
원래 알고 있는 개념이었는데, 오랜만에 봐서 그런가? 갑자기 헷갈려서 급하게 공부를 했다.
지금은 어느 정도 이해했지만, 나중에 또 까먹을 것 같아서 개념 정리를 해보려고 한다.
동기
직렬적으로 일을 처리한다.
요청이 들어오면 순차적으로 작업을 수행하고, 해당 작업이 수행중이면 다음 작업은 대기한다.
만약 서버에 1억개의 요청이 들어왔다고 해보자.
1억개의 요청을 처리해야 하는 서버가 동기식으로 되어 있다면 1억번째 사람은 앞의 9999만9999명의 요청이 끝날 때까지 기다려야 한다.
비동기
병렬적으로 일을 처리한다.
요청이 들어오면, 해당 요청에 의한 작업이 끝나지 않았더라도 계속 다른 요청을 받는다.
그리고 들어온 요청에 대한 작업이 끝났다는 이벤트가 오면 해당 요청을 처리한다.
만약 A 요청이 들어오면, 해당 요청에 대한 작업이 끝나지 않아도 다음 요청을 받는다.
그러다가 A 요청이 끝났다는 이벤트가 오면, 해당 요청을 추후에 처리한다.
이러한 비동기 방식은 네트워크 관리에 최적화되어 있다.
nodejs도 이벤트 기반, non-blocking I/O 모델을 사용하고 있다고 한다.
왜 비동기 처리가 필요할까?
그래서, 비동기 처리를 하면 뭔가 성능이 좋아질 것 같은 느낌이 든다.
그럼 어떨 때 사용하는 게 좋을까?
프로젝트를 진행하면서, 로컬 서버 혹은 리모트 서버로부터 API를 통해 데이터를 가져오는 일이 많다.
예를 들어 지금 진행하는 프로젝트에서는 프론트엔드에서 그래프를 그리기 위해 노드리스트, 엣지리스트를 가져오는 일을 하고는 한다.
만약, "그림 그리기" 버튼을 클릭해서 서버에서 데이터를 가져오는 코드를 동기적으로 실행한다면, 어떻게 될까?
데이터를 받아올 때까지, 즉 요청이 완료될 때까지 다음 작업은 실행될 수가 없으므로, 사용자에게 앱이 멈춘 것처럼 보인다.
만약 엄청난 양의 데이터를 서버에서 가져오는 코드를 동기적으로 작성한다면, 데이터를 가져올 때까지 앱이 멈추고, 사용자는 불편을 겪게 된다.
그래서 보통, 이런 과정(네트워크 요청, 데이터베이스 조회)은 비동기 처리를 해주고는 한다.
플러터에서는 async, await, Future를 통해 비동기 처리를 해주고는 한다.
Future?
Future는 말그대로, 나중에 어떤 값이나 오류를 반환할 것이라는 약속을 나타내는 객체다.
nodejs에서 Promise와 비슷한 개념이라는 생각이 든다.
함수의 반환형을 Future로 지정해놓고, 비동기 연산에서 사용한다.
Future<String> fetchUserData() {
// 비동기적으로 사용자 데이터를 가져오는 예제 함수
return Future.delayed(Duration(seconds: 2), () {
return '사용자 데이터';
});
}
void main() {
print('데이터 요청 시작');
fetchUserData().then((data) {
// Future가 완료되면 then의 콜백이 호출됩니다.
print(data); // 사용자 데이터
}).catchError((error) {
// 오류가 발생하면 catchError의 콜백이 호출됩니다.
print(error);
});
print('데이터 요청이 끝날 때까지 기다리지 않음');
}
main함수에서 fetchUserData를 호출하면 데이터 요청이 시작되고, Future가 완료될때까지 기다리지 않고, 즉시
"데이터 요청이 끝날 때까지 기다리지 않음"을 출력한다.
그리고 2초 후에 Future가 완료되고, then에 제공된 콜백이 호출되어 "사용자 데이터"를 호출한다.
async, await
nodejs에도 있는 개념이다.
async는 비동기 함수를 선언할 때, 함수 정의에 추가한다.
await는 async 함수 내에서 사용할 수 있고, 해당 함수 내에서 비동기적으로 수행될 표현식 앞에서 사용된다.
await를 사용하면 해당 표현식이 완료될 때까지 실행을 일시 중단하고, 완료되면 결과를 반환한다.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _data = 'Loading...';
@override
void initState() {
super.initState();
fetchData();
}
// async 키워드를 사용하여 비동기 함수를 선언합니다.
Future<void> fetchData() async {
try {
// await 키워드를 사용하여 HTTP GET 요청의 완료를 기다립니다.
http.Response response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));
// 응답이 오면, 응답 데이터를 처리합니다.
if (response.statusCode == 200) {
String data = json.decode(response.body)['title']; // JSON 파싱
setState(() {
_data = data;
});
} else {
setState(() {
_data = 'Failed to load data.';
});
}
} catch (e) {
setState(() {
_data = 'Failed to load data: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Async Await Example'),
),
body: Center(
child: Text(_data),
),
);
}
}
위 코드에서 fetchData함수는 async 키워드를 사용해 비동기 함수로 선언되었고, 함수 내부에서 await 키워드를 활용해 HTTP GET 요청의 결과가 준비될 때까지 기다린다.
요청이 성공적으로 완료되면 응답에서 title을 추출해 _data 상태를 업데이트하고, 실패하거나 예외가 발생하면 오류 메시지를 설정한다.
이렇게 async, await를 사용하면, 비동기 코드를 마치 동기 코드처럼 읽고 쓰기 쉽게 만들 수 있고, UI 쓰레드를 차단하지 않고, 네트워크 요청이나 파일 입출력과 같은 시간이 소요되는 작업을 잘 처리할 수 있다.
정리
동기
- 실행 순서: 함수 호출이 발생하면, 프로그램의 실행 흐름이 함수 내부로 이동하고, 함수 내부의 코드가 완료될 때까지 다음 코드로 넘어가지 않는다.
- 블로킹 동작: 동기적 함수가 처리를 완료할 때까지 현재 스레드는 그 함수에서 벗어날 수 없으므로, 이를 블로킹 호출이라고 한다.
- 반환 값: 함수의 결과(반환값 또는 오류)는 함수 호출이 반환될 때 바로 사용할 수 있다.
- UI 또는 다른 처리 차단: 동기적 호출은 UI 스레드와 같은 중요한 스레드에서 무거운 작업(네트워크 통신, 데이터베이스 조회)을 수행할 경우 UI가 멈추는 것처럼 보일 수 있다.
비동기
- 비블로킹 실행: 플러터에서 비동기 함수는 Future를 반환하고 즉시 종료되므로 호출한 코드는 Future가 완료되기를 기다리지 않고 바로 다음 코드로 넘어갈 수 있다. Future의 결과는 나중에 처리된다.
- .then 체이닝: Future 객체에 .then을 체이닝하여, Future가 오나료된 후에 실행할 코드를 정의할 수 있다. 혹은 .catchError를 통해 오류를 처리할 수도 있다.
- async, await: async 함수 내에서 await 키워드를 사용하면 Future의 완료를 기다리고 결과를 바로 변수에 할당할 수 있다.
.then보다 더 이해하기 쉬운 코드를 작성 가능하다. async(이 함수는 비동기다) / await(이 함수의 실행은 기다린다)
- UI 반응성 유지: 비동기 호출은 UI의 반응성을 유지하고, 백그라운드에서 무거운 작업을 수행하기 위해 사용된다. UI 스레드가 블로킹되지 않으므로, 사용자는 작업이 처리되는 동안에도 앱을 계속 사용할 수 있다.
예를 들어 DB 쿼리나 네트워크 요청이 비동기 처리를 사용하면, 해당 작업이 완료될 때까지 프로그램이 멈추는 것(동기 처리 했을 시)을 방지하고, 대신 다른 작업을 수행할 수 있다. -> 앱의 반응성 향상
비동기 함수는 애플리케이션의 반응성과 효율성을 향상시킨다.
- 반응성 유지
- 자원을 효율적으로 사용
- 병렬 작업: 여러 비동기 작업을 거의 동시에 시작해 병렬로 실행 가능 -> 동시성(concurrency)을 활용해 작업을 빠르게 완료
- 스케일링 용이성: 서버 측 애플리케이션은 동시의 많은 수의 요청을 처리해야 함 -> 비동기 프로그래밍을 통해 수 만개의 동시 연결을 효과적으로 처리
살짝 주의할 점!
- 비동기 함수에서의 성능 향상은 비동기 함수가 동기 함수에 비해 항상 "더 빠른" 처리를 의미한다는 것은 아니다.
- 비동기 함수의 주된 목적은 대기 시간(latency)이 있는 작업들을 관리하고, 애플리케이션의 전체적인 처리 능력을 향상시키는 데 있다.
참고 및 출처
👩💻 완벽히 이해하는 동기/비동기 & 블로킹/논블로킹
동기/비동기 & 블로킹/논블록킹 프로그래밍에서 웹 서버 혹은 입출력(I/O)을 다루다 보면 동기/비동기 & 블로킹/논블로킹 이러한 용어들을 접해본 경험이 한번 쯤은 있을 것이다. 대부분 사람들은
inpa.tistory.com
동기와 비동기? 무슨 차이일까?
Node.js를 공부하다가 동기와 비동기에대한 개념을 한번 정리해보았다. Node.js는 비동기방식의 대표적인 플랫폼으로 비동기에 대한 특성과 예제를 적어보고 싶었다.
80000coding.oopy.io
동기와 비동기방식의 차이점(콜백함수와 프로미스)
오늘은 자바보다는 자바스크립트, 그중에서도 노드를 사용한 서버 프로그래밍을 다룰 때 처리하는 동기와 비동기 처리방식에 대해서 알아보려고 한다. 물론 동기와 비동기 자체의 개념은 모든
blog.metafor.kr
[Javascript] 비동기, Promise, async, await 확실하게 이해하기
초보자 입장에서 헷갈리기 쉬운 자바스크립트의 Promise 에 대해 낱낱이 파헤칩니다. 더 나아가 async와 await을 올바르게 사용하는 법까지 소개합니다.
springfall.cc
'CS > 그외' 카테고리의 다른 글
라이브러리 vs 프레임워크 (1) | 2024.07.16 |
---|---|
오버로딩과 오버라이딩 (0) | 2024.07.12 |
static 키워드 (0) | 2024.07.12 |
클래스, 객체, 인스턴스의 차이가 뭔가요? (0) | 2024.07.10 |