OS , 네트워크

동기, 비동기, 블로킹, 논블로킹

KimGeon-U 2025. 3. 11. 22:07

개요

프로그래밍시 쓰레드, 프로세스같은 병렬 처리 구조에 대하여 학습하였을 때 동기,비동기 또는 블로킹, 논 블로킹에 대한

용어들을 자주 들어보았습니다. 저는 쓰레드에 대한 학습 시 동기,블로킹은 멀티쓰레드 상황에서의 상태를 제어하기위해 사용한다고 배웠었는데, 저는 동기,블로킹과 비동기,논블로킹이 서로 같다고 생각하였으나, 학습해보니 서로 각각 다른 의미들을 가지고있었습니다.

이러한 이유로 이번 포스팅에서는 동기, 비동기 그리고 블로킹과 논 블로킹에 대하여 알아보겠습니다.

 

동기(Synchronous) / 비동기(Asynchronous) 

https://jieun0113.tistory.com/73

동기 방식 (Synchronous)

동기 방식은 요청을 보낸 후 응답을 받아야지 다음 동작이 진행되는 것을 말합니다.

동기 방식으로 이루어진 프로그램은 요청받은 하나의 작업이 수행될때까지 다른 작업들은 실행하지 않습니다. 자바 프로그래밍 예시를 들면, 쓰레드 하나가 호출한 메서드에 대한 작업이 완료될 때까지 다른 쓰레드들은 작업을 멈추어져 있는 상태를 예시로 들 수 있습니다. 이러한 방법은 동기 방식으로 설계된 쓰레드가 호출한 함수의 처리 결과를 호출한 함수가 체크하며, 호출한 함수에서 종료가 되었을 때 다른 쓰레드가 접근을 할 수 있습니다.

장점으로는 순차적으로 처리가 필요한 시스템이 있을 경우에는 동기방식을 통하여 작업의 순서를 정하여 사용할수 있는 안전성을 보장해주지만, 하나의 요청이 완료될때까지 다른 요청은 대기해야하므로 비동기 방식보다 처리시간이 더 느리다는 단점또한 가지고 있습니다.

 

비동기 방식 (Asynchronous)

비동기 방식은 동기방식과는 상반되는 경우로, 요청에 대한 결과에 상관없이, 다음 작업이 동작하는 방식입니다.

이러한 시스템은 순차적으로 진행하지 않습니다. 즉, 요청한 작업에 대해 완료 여부를 따지지 않고 자신의 다음 작업을 그대로 수행 할 수 있습니다. 또한 비동기 방식의 경우 동기 방식의 경우 요청이 끝났는지를 확인하기 위해 호출한 함수가 확인을 하는 방면, 비동기 방식은 Callback함수에서 작업완료에 대한 값을 받아 처리합니다.

비동기 방식의 장점으로는 요청한 작업에 대한 완료를 신경쓰지 않기 떄문에 쓰레드를 기준으로 여러개의 쓰레드들이 동시 처리를 함으로써 처리 시간이 동기 방식에 비하여 더 빠르다는 장점을 가지고 있습니다.

하지만, 자바 언어에대해 학습시 Thread Safe하지않다. 라는 말을 자주 들어보았을텐데, 비동기 방식의 경우에서는 하나의 데이터에 대해 여러개의 쓰레드가 동시 접근할시에 순서를 가지지않고 동시에 처리하기때문에 데이터의 값이 예상하지 못한 결과를 가질 수 있습니다.

 

// 비동기 클래스
class notSynchronized extends Thread
{
    static int count = 0;

    public void run()
    {
        for(int i = 0; i < 10000; i++)
        {
            // 정적 지역변수 count의 값을 스레드당 10000번씩 증감한다.
            count++;
        }
    }
}
// Case : C++

#include <iostream>
#include <thread>
#include <vector>

int count = 0;  // 모든 스레드가 공유하는 전역 변수

void notSynchronized() {
    for (int i = 0; i < 10000; i++) {
        count++;  // 동기화 없이 증가 (Race Condition 발생 가능)
    }
}

int main() {
    std::vector<std::thread> threads;

    // 10개의 스레드 생성
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(notSynchronized);
    }

    // 모든 스레드가 종료될 때까지 기다림
    for (auto& t : threads) {
        t.join();
    }

    // Race Condition으로 인해 예상값(100000)과 다른 결과가 나올 수 있음
    std::cout << "Final count (not synchronized): " << count << std::endl;

    return 0;
}

Thread 클래스를 확장하여 정적 지역변수 count의 값을 증감시키는 클래스를 생성하였으며, 

Main 메서드에서는 2개의 해당 클래스의 2개의 쓰레드 객체를 생성하여 실행하였습니다.

비동기 클래스의 실행 결과

 

2개의 스레드를 사용하여 기대한 count의 값은 20000이지만, 실행결과 기대값과 다르며, 모두 다른 값을 리턴합니다.

다음은 동기화를 시키는 방법중 예약어 'synchronized'를 사용하여 동기화를 시킨 코드블럭을 보여드리겠습니다.

// Thread들이 공유할 클래스
class SynchronizedCount
{
    private int count = 0;

    //자바의 예약어 'synchronized'를 사용하여 모니터락 메서드 구현
    public synchronized void increaseCount()
    {
        count++;
    }

    public int getCount(){return count;}
}

//Thread를 확장한 클래스
class synchronizedCountThread extends Thread {
    SynchronizedCount synCount;

    synchronizedCountThread(SynchronizedCount synCount)
    {
        this.synCount = synCount;
    }

    public void run() {
        for (int i = 0; i < 10000; i++) {
            synCount.increaseCount();
        }
    }
}
Case : C++

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

class SynchronizedCount {
private:
    int count;
    std::mutex mtx;  // 동기화를 위한 뮤텍스

public:
    SynchronizedCount() : count(0) {}

    // 동기화된 증가 함수 (자바의 synchronized 메서드 역할)
    void increaseCount() {
        std::lock_guard<std::mutex> lock(mtx);
        count++;
    }

    int getCount() {
        std::lock_guard<std::mutex> lock(mtx);
        return count;
    }
};

// 스레드 함수
void synchronizedCountThread(SynchronizedCount& syncCount) {
    for (int i = 0; i < 10000; i++) {
        syncCount.increaseCount();
    }
}

int main() {
    SynchronizedCount syncCount;
    std::vector<std::thread> threads;

    // 10개의 스레드 생성
    for (int i = 0; i < 10; i++) {
        threads.emplace_back(synchronizedCountThread, std::ref(syncCount));
    }

    // 모든 스레드가 종료될 때까지 기다림
    for (auto& t : threads) {
        t.join();
    }

    // 동기화로 인해 정확한 결과 보장 (100000)
    std::cout << "Final count (synchronized): " << syncCount.getCount() << std::endl;

    return 0;
}

Thread에서 확장한 클래스에서 증감연산을 할 클래스의 객체를 받아 2개의 Thread를 실행하였습니다

동기화 처리 결과

 

증감연산을 담당하는 클래스에서 동기화를하여 Thread들이 순차적으로 처리되어 기대한 값과 일치하게 나오는것을 볼 수 있습니다. 이 외에도 명시적으로 Lock을 사용하는 ReentLock, concurrent 등의 동기화 방법이 있습니다.

 

 

즉, 동기와 비동기 방식의 차이는 작업 순서처리의 차이를 가지고 있습니다.

동기 방식의 경우 순서를 가지고 있으며, 비동기 방식은 순서를 지키지 않을수 있으며, 이에 대한 결과는 처리 속도에 차이가 있습니다.

또한 작업 완료의 여부를 호출한 함수가 신경을 쓸 것인지, 호출된 함수(Callback 함수)가 신경을 쓸 것인지에 대한 차이가 있습니다.

 

 

블로킹(Blocking) / 논 블로킹(Non-Blocking)

 

https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC#%EB%8F%99%EA%B8%B0synchronous_/_%EB%B9%84%EB%8F%99%EA%B8%B0asynchronous

Blocking과 Non-Blocking은 다른 요청의 작업을 처리하기 위해 현재 작업을 Block을 하냐, 안하냐의 차이가 있는 프로세스의 실행 방식입니다. 동기 / 비동방식은 전체적인 작업에 대한 순차적인 흐름을 나타내지만, 블로킹 / 논 블로킹의 경우 하나의 처리되어야 하는 작업의 흐름을 막느냐 / 막지 않느냐의 차이가 있습니다. 또한 블로킹은 호출된 함수가 작업을 처리하고 결과를 반환하기 전까지는 호출한 쪽에서 기다려야하며, 논 블로킹의 경우 호출된 함수의 결과를 반환하기 전까지 기다리지 않으며 자신의 작업을 한다는 것을 논 블로킹이라 할 수 있습니다.

 

즉 블로킹 / 논 블로킹의 경우는 하나의 처리해야 하는 작업에게 제어권이 있는지에 대한것을 말합니다.

동기 방식의 경우에는 하나의 작업(함수)가 종료된 후 순차적으로 진행되지만, 블로킹의 경우에는 작업 전체의 흐름을 제어한다는 차이가 있습니다.

 

 

 

 

동기/비동기 와 블로킹/논블로킹의 조합

https://www.inflearn.com/news/72620

동기와 비동기, 그리고 블로킹과 논블로킹은 서로 다른 개념이기때문에 각각 조합하여 사용 할 수 있습니다.

 

1.Sync-Blocking

Sync-Blocking조합의 작업은 다른 작업이 진행되는 동안 자신의 작업을 처리하지 않으며(Blocking) 다른 작업의 완료 여부를 받아 바로 순차적으로 처리하는(Sync)의 방식을 동시에 가지고 있습니다.

이러한 조합은 적은 데이터를 처리하거나 파일 하나를 읽고 쓰는 경우에는 간단하고 직관적인 장점을 가지고 있습니다.

하지만, 데이터가 크거나 시간이 오래걸리는 작업을 할 시에는 하나의 작업이 끝나기 전까지는 다른 작업을 처리하지 못하므로 전체 처리시간이 오래걸리게 되어 비효율적이므로 다른 방식을 사용해야 합니다.

 

2.Sync-NonBlocking

Sync-NonBlocking의 조합은 다른 작업이 진행되는 동안에도 자신의 작업을 처리하면(NonBlocking) 다른 작업의 결과를 바로 처리하여 작업을 순차대로 수행하는 방식(Syncronous)입니다.

하나의 요청이 완료되었는지를 지속적으로 체크하며, 처리가 완료되면 다음 작업을 수행하는 특징을 가지고 있습니다.

예시로는 프로그램의 다운로드완료 후 자동시작하는 경우가 있습니다.

프로그램이 다운로드가 되었는지 지속적으로 확인하며, 동시에 사용자는 다른 프로그램을 사용 할 수 있습니다.

이러한 경우를 Sync-NonBlocking이라 볼 수 있습니다.

 

3.ASync-Blocking

Async-Blocking 조합은 다른 작업이 진행되는동안 자신을 멈추고 기다리며, 다른 작업의 결과를 바로 처리하지않아 순서대로 작업을 수행하는 방식입니다. Sync-Blocking과 Async-Blocking의 차이는 개념적으로만 있으며, 큰 차이가 없습니다.

동기방식의 경우 하나의 함수가 끝날때까지 순차적으로 처리한다는 개념인 방면, Blocking은 작업 전체의 흐름을 제어하기 때문에 비슷한 작업 효율이 나온다고 할 수 있습니다.

4.ASync-NonBlocking

Async-NonBlocking 조합은 다른 작업이 진행되는 것에 관계 없이 자신의 작업 또한 수행하며, 다른 작업의 순서를 기다리지 않는방식입니다. 해당 방식은 데이터가 크거나 시간이 오래걸리는 작업에 적절한 조합이라 볼 수 있습니다.

동기 방식을 사용하지 않아 순서를 기다리지않으므로 순차적인 처리가 필요하지 않은 경우에 사용하는것이 좋습니다.

대표적인 방식으로는 대규모 사용자에게 메시지를 전송하거나, 여러개의 프로그램을 동시 다운로드 하는 경우가 있습니다.

 

 

정리

이번 포스팅에서는 동기/비동기 와 블로킹/논블로킹에 대한 차이를 알아보았습니다.

 

동기, 비동기 / 블로킹, 논 블로킹

  1. Synchronous : 동시에 작업을 하는 프로그램들간의 시간을 Callback함수를 사용하여 시간을 맞춘다. 
  2. ASynchronous 동시에 작업을 하는 프로그램들의 시간을 맞추지 않는다.
  3. Blocking : 한 작업이 수행되는 동안 다른 작업들은 할 수 없게 제어권을 가지고 있다.
  4. NonBlocking : 한 작업이 수행되는 동안 다른 작업을 수행할 수 있으며, 제어권을 바로 돌려준다.

동기/비동기와 블로킹/논블로킹은 서로 조합하여 사용할 수있다.

  1. Sync-Blocking : 어떠한 작업이 처리되는동안, 다른 작업들은 대기한다.
  2. Sync-NonBlocking : 어떠한 작업이 처리되는동안, 다른직업들은 대기한다.
    하지만, 작업이 다 처리되었는지 지속적으로 확인한다.
  3. ASync-Blocking : 어떠한 작업이 처리되는 동안, 다른 작업들은 대기한다.
    즉, 동기-블로킹과 비슷한 개념으로, 1번과 비슷한 작업 효율이 나온다.
  4. ASync-NonBlocking : 다른 작업이 진행되는동안에도 자신의 작업을 처리한다.
    순서를 기다리지 않으므로, 처리의 순서가 필요없는 경우에 사용한다.

 

이번 학습을 통하여 동기/비동기와 블로킹/논블로킹에 대한 차이점을 알 수 있었습니다.

또한 4개의 조합을 알 수 있엇는데 Async-Blocking의 경우에는 개발자의 실수로 이루어지는 경우에 이러한 조합이

나온다고 하여 프로그래밍시에 주의해야할 경우를 인지할 수 있엇습니다.

이번 학습을 통하여 프로그래밍 할시 어떠한 조합을 사용해야 할지 확인할수 있었습니다.

 

출처 및 참고내역

 

https://deveric.tistory.com/99

https://inpa.tistory.com/entry/%F0%9F%91%A9%E2%80%8D%F0%9F%92%BB-%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B8%94%EB%A1%9C%ED%82%B9%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC#%EB%8F%99%EA%B8%B0synchronous_/_%EB%B9%84%EB%8F%99%EA%B8%B0asynchronous