programming

블록과 논블록, 동기와 비동기

hmk run dev 2025. 5. 6. 17:57

오늘은 프로그래밍에서 매우 중요한 개념인 블록(Block)과 논블록(Non-block), 그리고 동기(Synchronous)와 비동기(Asynchronous) 처리에 대해 자세히 알아보겠습니다. 이 개념들은 종종 혼동되기 쉽지만, 효율적인 프로그래밍을 위해 반드시 이해해야 하는 핵심 요소입니다.


개념 정의와 차이점

블록 vs 논블록

 

블록(Block) 처리란:

  • 특정 작업이 완료될 때까지 다음 작업으로 진행하지 않고 대기하는 방식
  • 프로그램이 해당 작업에 "차단(blocked)"되어 있는 상태
  • 작업이 끝날 때까지 제어권을 돌려받지 못함

논블록(Non-block) 처리란:

  • 작업 완료 여부와 상관없이 다음 작업으로 즉시 진행하는 방식
  • 프로그램이 "차단되지 않고(non-blocked)" 계속 실행됨
  • 작업을 요청한 후 즉시 제어권을 돌려받음

동기 vs 비동기

동기(Synchronous) 처리란:

  • 작업이 순차적으로 실행되며, 한 작업이 완료된 후에 다음 작업이 시작
  • 결과를 즉시 반환받음
  • 코드의 실행 순서와 작업의 완료 순서가 일치

비동기(Asynchronous) 처리란:

  • 작업의 완료를 기다리지 않고 다음 작업을 진행
  • 결과는 나중에 콜백, 프로미스, 이벤트 등의 방식으로 받음
  • 코드의 실행 순서와 작업의 완료 순서가 일치하지 않을 수 있음

블록/논블록과 동기/비동기의 관계

이 두 쌍의 개념은 서로 다른 측면을 다루고 있습니다:

  • 블록/논블록은 제어권(control flow)에 관한 것
  • 동기/비동기는 작업의 완료 시점과 결과 처리 방식에 관한 것

따라서 이론적으로는 다음과 같은 조합이 가능합니다:

  1. 블록 + 동기: 가장 전통적인 방식 (예: 일반적인 함수 호출)
  2. 논블록 + 비동기: 현대적 웹 개발에서 많이 사용 (예: AJAX 요청)
  3. 블록 + 비동기: 드물게 사용 (예: 일부 멀티스레딩 상황)
  4. 논블록 + 동기: 특수한 경우 (예: 일부 폴링 메커니즘)

장단점 비교

블록 처리의 장단점

장점:

  • 코드가 직관적이고 이해하기 쉬움
  • 디버깅이 간단함
  • 작업의 순서가 명확함

단점:

  • 자원 활용 효율이 낮음 (대기 시간 동안 CPU 유휴 상태)
  • 사용자 인터페이스가 멈출 수 있음
  • 확장성이 제한됨

논블록 처리의 장단점

장점:

  • 자원 활용 효율이 높음
  • 사용자 인터페이스 응답성 유지
  • 높은 처리량 가능

단점:

  • 코드 복잡성 증가
  • 디버깅이 어려움
  • 경쟁 상태(race condition)와 같은 동시성 문제 발생 가능

동기 처리의 장단점

장점:

  • 코드 흐름 추적이 용이함
  • 순차적 실행으로 예측 가능성이 높음
  • 에러 처리가 직관적임

단점:

  • I/O 작업 등에서 효율이 낮음
  • 병렬 처리가 어려움
  • 긴 작업은 전체 프로그램을 지연시킬 수 있음

비동기 처리의 장단점

장점:

  • 높은 응답성과 효율적인 자원 활용
  • 병렬 처리 가능
  • I/O 바운드 작업에 이상적

단점:

  • 콜백 지옥(callback hell)과 같은 코드 복잡성
  • 에러 처리가 복잡함
  • 디버깅 어려움

실전 적용 가이드

블록 처리를 사용해야 할 때

  1. 단순한 계산이나 메모리 작업처럼 빠르게 완료되는 작업
  2. 다음 작업이 이전 작업의 결과에 의존적일 때
  3. 트랜잭션 일관성이 중요한 데이터베이스 작업
  4. 초기화 과정과 같이 반드시 순차적으로 실행되어야 하는 경우

논블록 처리를 사용해야 할 때

  1. UI 응답성이 중요한 클라이언트 애플리케이션
  2. 네트워크 통신이나 파일 I/O와 같이 대기 시간이 긴 작업
  3. 동시에 여러 작업을 처리해야 하는 서버
  4. 실시간 데이터 처리가 필요한 경우

동기 처리를 사용해야 할 때

  1. 순차적인 데이터 처리가 필요한 경우
  2. 코드의 명확성과 디버깅 용이성이 중요할 때
  3. 에러 처리가 중요한 중요 업무 로직
  4. 작업의 결과가 즉시 필요한 경우

비동기 처리를 사용해야 할 때

  1. 네트워크 요청, 데이터베이스 쿼리 등 I/O 바운드 작업
  2. 대용량 데이터 처리
  3. 사용자 인터페이스의 응답성 유지가 중요한 경우
  4. 여러 독립적인 작업을 동시에 처리해야 할 때

프로그래밍 언어별 구현 사례

JavaScript

JavaScript는 단일 스레드 언어지만 비동기 처리를 위한 다양한 메커니즘을 제공합니다.

 

// 블록 + 동기
function syncBlock() {
  const result = expensiveCalculation(); // 이 함수가 완료될 때까지 대기
  console.log(result);
}

// 논블록 + 비동기 (콜백 방식)
function asyncNonBlock() {
  fetchData((result) => {
    console.log(result);
  });
  console.log("요청 시작됨"); // fetchData 완료 전에 실행됨
}

// 논블록 + 비동기 (Promise 방식)
function asyncNonBlockPromise() {
  fetchDataPromise()
    .then(result => console.log(result))
    .catch(error => console.error(error));
  console.log("요청 시작됨"); // Promise 완료 전에 실행됨
}

// 논블록 + 비동기 (async/await 방식 - 문법적으로는 동기식처럼 보이지만 내부적으로는 비동기)
async function asyncNonBlockAwait() {
  console.log("요청 시작됨");
  try {
    const result = await fetchDataPromise(); // 비동기지만 코드는 동기식처럼 보임
    console.log(result);
  } catch (error) {
    console.error(error);
  }
}

 


실제 사례

웹 개발에서의 API 호출

웹 애플리케이션에서 외부 API를 호출할 때:

 

블록 + 동기 방식:

// 블록 + 동기 (권장하지 않음)
function getDataSync() {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', 'https://api.example.com/data', false); // 동기 모드
  xhr.send();
  if (xhr.status === 200) {
    return JSON.parse(xhr.responseText);
  }
}
// 주의: 이 방식은 브라우저 메인 스레드를 차단하여 UI 응답성을 저하시킴

 

 

논블록 + 비동기 방식 (권장):

// 논블록 + 비동기 (fetch API)
function getDataAsync() {
  fetch('https://api.example.com/data')
    .then(response => response.json())
    .then(data => {
      updateUI(data); // 데이터가 준비되면 UI 업데이트
    })
    .catch(error => console.error('Error:', error));
  
  showLoadingIndicator(); // 즉시 실행되어 사용자에게 로딩 중임을 표시
}

 

[Q&A] async/await는 블록인가 논블록인가?

Q: API 호출에 await를 사용하면 응답을 기다려야 하므로 블록 방식이 아닌가요?

A: 이 질문은 매우 좋은 포인트를 짚고 있습니다! async/await의 블록/논블록 특성은 두 가지 관점에서 봐야 합니다:

 

 

함수 내부 관점: await는 해당 함수 내에서 Promise가 해결될 때까지 다음 코드 실행을 일시 중지합니다. 이런 면에서는 "블록"처럼 보입니다.

async function getData() {
  console.log("1. 요청 전");
  const data = await fetchAPI(); // 여기서 함수 실행이 일시 중지됨
  console.log("3. 데이터 수신 후"); // 응답이 올 때까지 실행되지 않음
  return data;
}

 

전체 프로그램 관점: await를 만나면 함수 실행은 일시 중지되지만, 중요한 점은 제어권이 이벤트 루프로 넘어간다는 것입니다. 따라서 메인 스레드는 차단되지 않고 다른 작업을 계속할 수 있습니다.

 

getData();
console.log("2. getData 호출 후 즉시 실행"); // 데이터를 기다리지 않고 실행됨

// 실행 순서: 1 → 2 → 3

 

이처럼 async/await는 개발자에게는 동기 코드처럼 보이는 가독성을 제공하면서도, 실제로는 프로그램 전체가 블록되지 않는 논블록 방식으로 동작합니다. 이것이 JavaScript의 비동기 프로그래밍의 강력한 특징입니다.

 

결론: API 호출을 await로 처리하는 코드는 전체 시스템 관점에서 볼 때 "논블록 + 비동기" 패턴이라고 할 수 있습니다!


파일 시스템 작업

Node.js에서 파일 시스템 작업 시:

블록 + 동기 방식:

 

javascript// 블록 + 동기
const fs = require('fs');

function readFileSync() {
  try {
    const data = fs.readFileSync('large-file.txt', 'utf8');
    console.log(data);
  } catch (error) {
    console.error('Error:', error);
  }
  console.log('파일 읽기 완료'); // 파일 읽기가 완료된 후에만 실행됨
}

 

논블록 + 비동기 방식:

// 논블록 + 비동기
const fs = require('fs');

function readFileAsync() {
  fs.readFile('large-file.txt', 'utf8', (error, data) => {
    if (error) {
      console.error('Error:', error);
      return;
    }
    console.log(data);
  });
  console.log('파일 읽기 요청됨'); // 파일 읽기 완료 전에 실행됨
}

 

 

데이터베이스 쿼리

Node.js와 MongoDB를 사용한 데이터베이스 쿼리:

블록 + 동기 방식 (잘 사용하지 않음):

// 블록 + 동기 (가상의 예시, 실제로는 권장하지 않음)
function findUserSync(id) {
  const user = db.collection('users').findOneSync({ _id: id });
  return user;
}

 

 

논블록 + 비동기 방식 (권장):

// 논블록 + 비동기 (Promise)
function findUserAsync(id) {
  return db.collection('users').findOne({ _id: id })
    .then(user => {
      return processUser(user);
    })
    .catch(error => {
      console.error('Error:', error);
      throw error;
    });
}

// 논블록 + 비동기 (async/await)
async function findUserAsyncAwait(id) {
  try {
    const user = await db.collection('users').findOne({ _id: id });
    return processUser(user);
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

 


최신 트렌드와 미래 전망

최근 프로그래밍 패러다임은 비동기 논블록 처리 쪽으로 크게 기울고 있습니다:

  1. 리액티브 프로그래밍: 데이터 스트림과 변화 전파를 중심으로 한 비동기 프로그래밍 패러다임 (예: RxJS, ReactiveX)
  2. 이벤트 기반 아키텍처: 이벤트 생성과 소비를 통한 느슨한 결합의 시스템 구축 (예: Node.js의 이벤트 루프)
  3. 서버리스 컴퓨팅: 비동기 처리에 최적화된 클라우드 서비스 (예: AWS Lambda)
  4. 마이크로서비스 아키텍처: 독립적으로 배포 가능한 서비스들의 비동기 통신

결론

블록/논블록과 동기/비동기는 서로 다른 개념이지만, 상호보완적으로 작용하여 프로그램의 실행 방식과 성능에 영향을 미칩니다. 현대 프로그래밍에서는 특히 사용자 경험과 시스템 효율성을 위해 논블록 비동기 방식이 선호되는 추세이지만, 각 상황에 맞는 적절한 방식을 선택하는 것이 중요합니다.

효율적인 개발을 위해서는:

  1. 작업의 특성 파악: I/O 바운드인지, CPU 바운드인지 분석
  2. 사용자 경험 고려: 응답성이 중요한지, 처리 속도가 중요한지 판단
  3. 개발 복잡성 평가: 팀의 역량과 프로젝트 요구사항에 맞는 방식 선택
  4. 최신 API와 라이브러리 활용: 각 언어와 플랫폼에서 제공하는 최적화된 도구 사용

이러한 개념을 제대로 이해하고 적용한다면, 더 효율적이고 반응성 높은 애플리케이션을 개발할 수 있을 것입니다.