본문 바로가기
프로그래밍/컴퓨터 과학(CS)

1편] 동기와 비동기 블로킹과 논블로킹에 대해서....

by Hwan2 2020. 9. 7.
반응형

요즘 자료도 많아지고 굉장히 개념 자체가 햇갈립니다. 

저도 조사하면서 "내가 배운 내용이 맞나?" 싶을 정도로 햇갈리는 내용들이 많아서 이렇게 정리합니다.


3편으로 나눌 생각이고,

동기, 비동기, 블로킹, 논 블로킹, 멀티 프로세스, 멀티 쓰레드, 멀티 플렉싱, select, poll, epoll, IOCP, boost::asio::context

순으로 진행하려고 합니다.


요번 글은 동기, 비동기, 블로킹, 논 블로킹에 대한 내용입니다.


블로킹과 논 블로킹에 대해 예기하기 앞서, 동기와 비동기에 대해 예기해 보겠습니다.


1. 동기와 비동기??

동기와 비동기는 굉장히 다양한 곳에서 쓰입니다. 그리고 뜻하는 정의도 조금씩 다르죠. 하지만 근본적으론 같습니다.

프로그래밍 쪽에서의 동기와 비동기의 차이는 "단일 쓰레드냐 멀티 쓰레드냐?"의 차이이고

통신에서의 동기와 비동기의 뜻은 "응답이 올 때까지 기다릴 것이냐 요청만 보내고 내 할일 할 것이냐?" 입니다.


이러한 동기와 비동기에서 블로킹과 논 블로킹 개념이 등장하면서 더욱 머리를 아프게 하죠.


우선 동기부터 살펴보도록 하겠습니다.


1) 동기

코드에서의 동기는 쉽게 설명해서 순차적으로 실행되는것을 의미합니다. 

예를들어 정수를 입력받는 코드가 있다고 가정해봅시다. ex) std::cin >> num;
그리고 해당 코드 뒤엔 1 ~ 10까지 출력하는 for문이 있다고 가정해 봅시다.

main(){
    std::cin >> num;
    for(int i = 1i < 10i++)
        std::cout << i << std::enld;
}


프로그램을 실행시키면 cmd 창에서는 사용자의 입력이 완료되기 전까지 멈춰있게 됩니다.


사용자가 정수를 입력하는 순간 밑에 for문이 돌게되죠. 이것이 동기입니다.


2) 비동기

비동기란 무엇이냐? 동기의 반댓말로 "실행순서가 일정하지 않다." 입니다.

쉽게말해서 2가지 일을 동시에 하는 것입니다.

이 2가지 일을 동시에 할 수 있도록 도와주는 것이 쓰레드입니다.

main(){
    std::cin >> num;
    for(int i = 1i < 10i++)
        std::cout << i << std::enld;
}


이 예제에서 std::cin>>num; 을 쓰레드 1에게 넘겨주고, for문을 쓰레드 2에게 넘겨주게 된다면


실행했을 때 사용자가 입력을 하지도 않았는데 for문의 실행이 이뤄지고 있을 것입니다.



3) 그럼 무엇을 써야 하는가?

상황에 맞게 써야합니다. 쓰레드도 운영체제마다 늘릴 수 있는 한계가 존재합니다. 때문에 Thread poll이라는 개념을 통해 

쓰레드를 관리하죠. 또한 너무 많아도 context switch 때문에 서능저하가 발생하게 됩니다. 따라서 상황에 맞게 써야합니다.



2. 블로킹, 논 블로킹

블로킹이란 무엇일까요? 막혀있다는 뜻입니다. 그럼, 논 블로킹은? 막혀있지 않다는 뜻입니다.

블로킹과 논 블로킹은 쓰레드 관점에서 보셔야 이해하기 편합니다. 

동기와 비동기 코드에 모두 존재하니 말이죠.....


1) 블로킹


이미 우리는 블로킹을 수도없이 봐왔습니다. std::cin >> 에서 데이터를 입력 받을 때,

socket()을 열어 listen()할 때, 데이터를 읽기위해 read()할 때 등....

해당 함수들은 무언가 요청이나 입력이 될 때 까지 계속 기다립니다. 하염없이.....

이런것들을 블로킹 되었다고 합니다.

쓰레드로 나누던, 안나누던 해당 함수를 실행하고 있는 쓰레드는 바보처럼 멈춰지게 돼버립니다.


이것을 블로킹이라고 합니다. 그리고 이런 문제는 클라이언트가 많이 접속될 수 있는(1000명 이상) 서버에선 치명적입니다.

(블로킹 함수로 처리한다면 쓰레드는 1000개 이상이 되니 말이죠.)


그래서 등장한 것이 논 블로킹입니다.



2) 논 블로킹


사실 논 블로킹은 I/O (입 출력) 때문에 생겨졌습니다. 왜냐하면 가장 필요로 하는 프로그래밍 구간이 
서버와 클라이언트 간 입출력 문제이기 때문입니다. 1:1통신이면 상관 없지만 1:1000, 1:10000, 1:100000이 되버리면
블로킹 함수로는 감당이 안되기 때문입니다. 
생각해보자면 간단합니다. 1만명의 클라이언트가 서버에 접속해 메시지를 보낸다고 한다면 서버는 어떻게 받아야 할까요??
쓰레드 1만개를 만들어야 할까요? 하지만 쓰레드는 1000개 언저리까지만 만들 수 있는데 말이죠??
또한 1만개를 만든다 처도 성능이 좋아질까요?? 메모리는?? 

이러한 문제들 때문에 등장한 것이 논 블로킹 입니다.

논 블로킹은 운영체제의 도움이 필요합니다. 동작방식은 다음과 같습니다.

1. 핸들러를 통해 블로킹 되는 함수를 관리한다. 
2. 핸들러는 콜백 함수로 실행되며 특정 조건에 맞으면 해당 함수를 꺼내 실행하게 된다.
3. 특정 조건이란? read, write, error 등, 운영체제, 라이브러리에 따라 달라질 수 있다.
4. 신호가 온다면 해당 신호 정보를 바탕으로 원하는 함수를 꺼내 실행되고 종료된다.



이를통해 저희는 1:10000, 1:100000 을 처리할 수 있게 됩니다. 그것도 쓰레드 1개로 말이죠.


어떻게?? 

1. 1만명에 대한 소켓 정보를 운영체제에게 넘긴다.

2. 1만명중 3번과 100번, 2222번에서 데이터가 넘어왔다.

3. 운영체제는 데이터가 넘어온 것을 확인하고 해당 신호가 들어온 녀석들의 소켓정보를 핸들러에게 넘겨준다.

4. 쓰레드는 순차적으로 데이터를 읽어 처리한다.

5. 처리가 끝나면 함수는 종료된다.

5. 끝.....



위 예시를 대략적으로 이해하기 쉽게 썻습니다. 그리고 해당 작업은 epoll이나 IOCP의 내용입니다.


저렇게 처리하는 것이 멀티 플랙싱 방식이고요.


그리고 저렇게 클라이언트가 많아지게 된다면.... 쓰레드 하나로 모든 데이터를 처리하는 것이 힘들 것입니다.


그래서 Thread poll을 만들어 미리 Thread를 만들어 놓고, 작업량들을 분산해서 쓰레드들에게 처리하도록 맡길 수 있습니다.



3) 그럼 누가더 좋은가??

이것 또한 상황에 맞게 사용해야 합니다. 1:1, 1:10 같은 소규모 통신에서는 블로킹이 논 블로킹보다 훨씬 빠릅니다.
왜냐하면 논 블로킹은 데이터를 운영체제에게 넘겨주는 크기도 클 뿐더러 과정이 다소 복잡합니다.
하지만 그 둘의 속도차이를 묻는다면 1초도 안 날 것입니다.
하지만... 그럼에도 불구하고.... ms 조차 민감한 코드나 쉬운 코딩을 위한다면 블로킹 형식으로 사용해야 겠죠?
상황과 사용 용도에 맞게 사용하시면 될 것 같습니다.




사실 Thread poll을 직접 만들고 한쪽 Thread에 몰린 데이터를 분석한다음 분산시켜주고, 이런 과정들은 상당히 Advanced한 일이고

이 글을 쓰는 저도 만들어보지 못한 것들입니다.


하지만 시대가 흐른만큼 굉장히 좋은 성능을 제공해주는 라이브러리들이 많이 있죠.


Thread poll만 하더라도 C++에선 task나 async내부에서 Thread poll만들어 관리하고, 

Thread를 분산처리함에 있어선 openMP가 있습니다.


그러니 저와 같이 초보자 분들은 내용만 이해하고 넘어 갑시다.!!! ㅠ



2편에선 해당 내용과 관련된 멀티 프로세스, 멀티 쓰레드, 멀티 플랙싱에 대해 설명해보도록 하겠습니다.

반응형

댓글


스킨편집 -> html 편집에서