하마
5k
2018-02-13 11:39:48 작성 2018-02-27 17:05:17 수정됨
14
4410

블럭,논블럭,동기,비동기 이야기



블럭,논블럭,동기,비동기 이야기  


블록,논블럭,동기,비동기를 구분하는 것에 대한 글들이 있는데, 별거 아닌거 가지고 어렵게 풀어내는 거 같아서 나름 간단하고 분명하게 구분해 보는 글을 작성 해 본다. 근데 함정이 있는데 분명하게 정답을 말해 준다는게  아니다. 분명하게 정답이 없으며, 불분명하다고 말해주려는 것이다. ㅎㅎ  면접시나 시험지에 적을 정확한 정답을 몰라서 혹시 불안해 하시는 분이 있다면 이 글을 읽고 안심하셔도 될 것이다. 구체적으로 블럭/논블럭에 대한 구분은 비교적 명확하다. 동기/비동기로 넘어가면 말하는 상황에 따라서 조금 달라지기 시작한다. 이제 조합하기 시작하면 문제가 발생하기 시작한다. 이 글에서는 이것의 구분에 대한 설명을 해드릴 것이지만, 구분을 굳이 왜 해야하는가? 라는 의문이 생길 수도 있을 것이다. 그렇다면 내 글의 의도가 먹힌 것이다. 사실 이것은 구분을 명확히 해야하는 문제라기 보단, 이런식으로 시스템이 작동 하기도 하는 구나 라는 "감" 을 잡으면 되는 문제이다. 

여기서 내가 내리는 정의 또한 "정답" 이 아니다. 애초에 정답이 없는 문제라고 생각하고 유연하게 바라보자. 즉 1+1=2 같은 종류의 문제라거나, 자바에서 class 의 정의는? 같은 문제와는 다르다. 디자인패턴 같은 느낌이다. "의도"는 있지만 "구현"은 제각각인.. 

처음에는 각각의 정의를 내려보고, 그 담엔 우체국을 예로 들어서 이야기 식으로 구분을 해 보며, 마지막으로는 코드를 통한 예를 통해 그 "감"을 잡아내는 수확을 얻도록 하자.


@ 다 읽기 귀찮고 감 만잡으려면 3번 우체국이야기는 꼭 읽자
@ 애초에는 I/O 과 연관되어서 정의내리는 것이었는데, I/O 상관없는 비동기가 자주 사용되면서 더 혼탁해졌다.


1. 개별 정의


블럭/논블럭 

- 블럭/논블럭는 함수호출에서의 이야기이다.(기술적으로 명확히 구분된다.) 
- A 라는 함수를 호출했을때, A라는 함수를 호출 했을 때 기대하는 행위를 모두 끝마칠때까지 기다렸다가 리턴되면, 이것은 블로킹 되었다고 한다.
- A 라는 함수를 호출 했는데, A라는 함수를 호출 했을 때 기대하는 어떤 행위를  요청 하고 바로 리턴되면 이것은 논블럭킹 되었다고 한다.

동기/비동기

- 동기/비동기는 행위에 대한 이야기이다.(기술적으로 구분 안된다. 추상적으로 구분한다.) 

- A 라는 행위와 B 라는 별개의 행위가 있다고 하자. A 라는 행위와 B 라는 행위가 동시(or 순차적이지 않다면)에 실행되고 있으면 비동기라고 한다.여기서 제약이 하나 있는데 A,B 행위 사이에는 인과관계가 있어야 한다. 즉 웹서버를 예로 들어서 멀티쓰레드로 각각 A와B가 다른 클라이언트와 작업 할 때 둘은 동시에 작업하고 있지만, 둘의 인과관계는 없지 않나? 이땐 비동기라고 볼 수 없다. 

- A라는 행위와  B라는 행위가 순차적으로 작동한다면 동기라고 한다. 
- 동기적 행동에는 하나가 더 있다. A라는 행위가 별개의 것이 아니라, B라는 행위를 관찰하는 행위라면 이것이 동시에 일어나더라도 동기이다. 기술적으로 말해서 A라는 쓰레드와 B라는 쓰레드가 따로 돌아 간다고 해도, 어떤 하나의 행위가 다른 행위에 밀착되어 있다면 두 행위가 다른 쓰레드에서 벌어지더라도 동기란 말이다. 관찰하는 행위라는 말자체가 정확한 기술적 구분이 되는게 아니기 때문에 추상적이라는 표현을 사용한 것이며, 이 글의 가장 불분명 한 곳이니 잘 기억해 두도록하자. 이해가 안가면 다음의 우체국 예제와 실제 코드예제를 통해 이해할 수 있을 것이다.


2.조합 정의  


이제 위의 블럭/논블럭과 동기/비동기를 조합해보자. 

블럭/동기 

A가 실행되다가 B라는 일을 수행하는 함수를 호출해서 B를 시작한다. B라는 일이 끝나면 함수를 리턴한다. A와 B는 순차적으로 진행되기 때문에 동기이며,  B라는 일을 하는 함수를 호출하고 그 일이 끝나고 나서야 리턴되므로 블럭된 것이다. 따라서 블럭/동기 


블럭/비동기 

어떻게 블럭되었는데 A,B라는 일이 동시에 일어나는가? 설명을 들어보고 이런 경우를 말하는구나라는 "감"을 잡아보자.

일단 A는 B라는 일을 시킨다. 그리고 바로 리턴하고 (여기서는 논블럭)  B는 일을 시작하고, A도 자신의 일을 한다. A는 중간에 B라는 일이 하는 중간 결과를 보고 받아서 처리해야한다. A는 B에게 요청을 해서 중간결과를 기다린다(블록), 요청의 결과를 받고 나서 그 결과를 이용해서 A는 자신의 일을 처리한다. 동시에 B 는 또 자신의 일을 동시에 한다. (비동기) A는 다시 B에게 중간결과를 요청해서 기다린다 (블록) , 요청의 결과를 받고 A는 자신의 일을 , B는 자신의 일을 한다. 반복된다.

이 글을 읽고, 사실 갸우뚱 해야한다. 중간에 블록되는 동안에는 "동기" 라고 말 할 수 있기 때문이다. 즉 어느 한 순간에 대해 해석하자면 틀릴 수도 있는것이다. 즉 처음부터 말해왔듯이 "정답"이 존재하지 않는다. 다만 이런 패턴들이 분명히 사용되고 있구나라고 감을 잡는게 목적이다.


논블럭/동기 

이것이 예도 위의 블럭/비동기와 비슷한데 조금 다른 늬앙스에 대해서 "감"을 잡아보자.

이것도 역시 A는 B라는 일을 시킨다. 바로 리턴한다. (논블럭)  B는 일을 시작하는데, A는 자신의 일을 하지 않는다. A의 하는 일이란 그저 B가 하는일을 확인하는 것이다. B가 결과 보고(중간 보고가 아니다) 를 했는지를 확인하는 함수를 호출하고 ,바로 리턴한다 (논블럭) 즉 결과 보고를 받을 때 까지 기다리는게 아니라, 결과 보고가 나왔는지 확인하고 바로 리턴하는 것이다.  이 짓을 계속한다. 즉 함수를 계속 논블럭으로 호출되긴 하나, A는 그저 B를 염탐할 뿐이다. 이 상태를 말한다. 그냥 염탐하지 말고 B가 일을 모두 끝마치고 리턴되길 기다리지 ;;; (그냥 블럭/동기로 하는게 나은 상황이 연출된다) 

이후에 B가 결과보고를 하면,B는 자신의 일이 끝난 것이고 A는 이제서야 자신의 일을 처리하게 된다. 즉 순차적이라는 말이다. 따라서 동기~

논블럭/비동기 

간단하다. A는 B의 일을 시작시키고 바로 리턴한다 (논블럭) 그리고 A와B는 각자 자신의 일을 한다 (비동기) 


2. 실행활에서 일어나는 우체국 이야기로 풀어보자.


블럭/동기 

우체국에 배달 트럭들이 줄을 서 있다. 우체국에 들어오는 물품들을 싣기 위해서인데,
 
- 1번 트럭이 우체국에 내 것들을 가져와주세요 요청하고 기다린다. (블럭)

- 우체국은 1번 트럭에게 주기 위한 물건들을 찾아서 싣기 시작한다. 

- 2번트럭은 1번트럭에 물건이 다 싣기를 기다린다. (블럭)

- 3번 트럭도 기다린다. (블럭)

- 1번트럭이 물건을 싣고 떠나면, 우체국은 이제 2번 트럭의 물건을 찾아서 싣는다. (동기) 

모든 일들이 순차적으로 일어 난다 (동기) 


블럭/비동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 집으로 돌아온다.

- 우체국은 물품을 준비하고, 나는 집에서 집안 청소를 한다. (비동기)

- 우체국에 전화 해서 접수원과 통화한다. 물품이 준비되었냐고 물어본다. 접수원은 준비될 때 까지 기다리라고 한다. 나는 하염없이 기다린다 (블럭)

- 접수원이 준비됬다고 말한다. 나는 트럭을 가지고 우체국으로 가서 물건을 싣고 온다.

- 우체국은 자신의 일을 하고, 나는 싣고 온 물건을 배달한다 (비동기) 

중간에 블럭되는 지점이 있지만, 그 이전과 이후에는 각자 자신의 일을 한다. 


논블럭/동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 집으로 돌아온다.

- 우체국은 물품을 준비하고, 나는 전화기를 붙잡는다.

- 우체국에 전화 해서 접수원과 통화한다. 물품이 준비되었냐고 물어본다. 접수원은 안됬다고 말한다. 나는 전화를 바로 끊는다. (논블럭)  

- 전화를 끊고, 집안 청소를 하는게 아니라, 다시 우체국에 전화한다. 안됬다고 하면 바로 끊는다 (논블럭)

- 계속 반복적으로 전화한다 (논블럭이며, 나는 내 일을 하는게 아니라 우체국의 일에 매달리고 있으므로 동기) 

- 이번 전화에는 접수원이 준비됬다고 말한다. 나는 트럭을 가지고 우체국으로 가서 물건을 싣고 온다.

- 나는 싣고 온 물건을 배달한다.

중간 중간 논블럭으로 전화를 바로 끊지만, 끊고 나서 바로 또 전화를 하므로 동기


* 이 경우에 내가 배달하는 동안에는 현실과 좀 다르지만 우체국은 쉰다고 생각 해야한다.  (동기


논블럭/비동기 

우체국에 가서 내가 필요한 물품은 무엇이라고 접수원에게 말을 하고 트럭을 놓고 집에 온다. (논블럭)
트럭(버퍼) 크기가 크다면 우체국에서 많이 채워 줄 것이다. (하지만 좀 더 시간이 걸리겠지) 

- 우체국은 물품을 준비하고, 나는 집에 와서 내일 을 한다 (비동기)

- 전화 따위는 하지 않는다. 우체국에서 알아서 트럭에 짐을 채워서 나에게 트럭이 준비됬으면 연락 할 것이기 때문이다. 

- 트럭이 가득 찼다고 연락이 왔다. 나는 트럭을 가지고서 배달을 시작하고 우체국은 자신의 일을 한다.

이것이 논블럭/비동기이다. 완전 효율적이지 않는가? 

하지만 이것도 병목지점이 있다. 어디일까? 
그렇다. 이 배달기사는 트럭이 한대 뿐이다. 
트럭이 한대 뿐이기 때문에, 배달하는 동안에는 우체국에서 또 다른 짐을 싣지 못한다.

어떻게 해결 할까? 간단하다. 트럭을 2개 만드는 것이다 (기술적으로 버퍼를 2개)
그러면 한대는 배달하는 동안에 우체국에 다른 한대를 맡겨 놓는 것이다.
이렇게 되면 배달일 끝날 쯤에는 우체국에 가있는 트럭은 가득 차 있을 것이고, 나는 연속적으로 배달을 할 수 있어서 돈을 많이 벌 수 있을 것이다.

여기서 끝이 아니다 병목이 또 하나 있다. 이번엔 무엇인가?
그렇다 배달기사가 하나라는 것이다. 

우체국에서 또 다른 트럭이 벌써 가득 차 있다고 연락이 왔지만, 배달중이라 그것을 처리 할 수가 없다. 
이때 어떻게 해야하나? 
그렇다 배달알바를 구하면 된다. 한대의 트럭이 준비되면 그 트럭이 짐을 3등분해서 배달알바 3명에게 나눠준다. 또 다른 트럭이 준비되면 , 배달알바가 끝난 알바생에게 나눠주거나 또 다른 알바생에게 나눠주면 된다.

일의 크기에 따라서 알바생을 늘리면 되는 것이다. 이 알바생이 소프트웨어에서 무엇일까?
그렇다~~ 멀티쓰레드이다.

비동기 / 싱글쓰레드로 짧게 짧게 일하는 곳 (Node 비동기 서버에서 간단한 리턴만 서비스 하는 곳)에서는 멀티 쓰레드를 굳이 도입하지 않아도 효율적이지만, 백단에서 먼가 해야 할 것이 많다면 (CPU intensive) 이렇게 멀티쓰레드를 추가 해주면 성능이 대폭 올라 갈 것이다.


3. 실제 코드 예제로 풀어보자.


블럭되어 동기식으로 일처리 - javascript

const fs = require('fs');
const data = fs.readFileSync('/file.md'); 

파일 다 읽을 때 까지 함수가 멈춰져 있으며, (블럭) 다른일도 못한다 (동기)

논블럭되어서 비동기식으로 일처리  - javascript

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {  // readFile 호출해 놓고 바로 리턴한다.
  if (err) throw err;  // 하지만 이 일에 대한 인과관계 장치를 마련해둔다.
});

... 다른 일을 한다 ..
파일 읽으라고 명령해 두고 바로 리턴(논블럭) 바로 다른 일을 한다. 다른 일을 하는 와중에도 시킨 일에 대한 대비는 하고 있다. (비동기)  우체국 예제로 치면, 전화가 오면 받아서 배달트럭을 가지고 올 준비를 마쳐놓은 것이다.


논블럭인데 비동기는 아니다  - golang
func start_server() {

    l, err := net.Listen(CONN_TYPE, CONN_HOST+":"+CONN_PORT)
    defer l.Close()

    for {
        conn, err := l.Accept()
        if err != nil {
            log.Print(err)
            continue
        }
    go handle_client(conn)
    }
}
go 루틴으로 (go handle_client) 개별 처리하게 만들고 바로 리턴해서, 클라이언트로 부터의 연결을 체결 하는 일을 하는 계속 진행. 하지만 멀티쓰레드로 동시에 일 들은 하지만 인과관계 부족으로 동기/비동기와 무관

논블럭이면서 비동기  - golang

아래의 예처럼 go 루틴으로 분기시킨 후에 go 채널로써 상호작용(인과관계)를 발생시키는 경우에는 비동기식이라 할 수 있을 것이다.
  sigs := make(chan os.Signal, 1)
  signal.Notify(sigs)
  go func() {
    s := <-sigs
    log.Printf("RECEIVED SIGNAL: %s",s)
    AppCleanup()
    os.Exit(1)
  }()


논블럭/동기  - Scala 

val myFuture: Future[String] = Future {
      val f = Source.fromFile("build.sbt")
        try f.getLines.mkString("\n") finally f.close()
    }
    
    if myFuture.isCompleted {
             ....
    }
  
    Thread.sleep(100)

    if myFuture.isCompleted {
             ...
    }

    Thread.sleep(100)
    
    if myFuture.isCompleted {
             ...
    }
파일을 읽어 주세요 라고 (논블럭) 으로 일을 시키고나서, 자신의 일은 안하고 계속 눈치보면서 확인함 (동기) 물론 실제 저런식으로 코딩을 하진 않는다. 

// 실제로는 아래처럼 onComplete 에 콜백을 등록해 준다. 
// 더 콜백들을 편하게 조작하기 위해서, 많은 언어에서 지원하는 async/await 또한 지원한다.

val file = Future { Source.fromFile(".gitignore-SAMPLE").getLines.mkString("\n") }

file onComplete {
    case Success(text) => log(text)
    case Failure(t) => log(s"Failed due to $t")
 }

이렇게 사용하면 논블럭/비동기라 할만하다.


블럭/비동기  - JAVA

Selector selector = Selector.open(); 
 
ServerSocketChannel mySocket = ServerSocketChannel.open();
InetSocketAddress myAddr = new InetSocketAddress("localhost", 1111);

mySocket.bind(myAddr);

mySocket.configureBlocking(false);

int ops = mySocket.validOps();
SelectionKey selectKy = mySocket.register(selector, ops, null);
while (true) { selector.select(); Set<SelectionKey> myKeys = selector.selectedKeys(); Iterator<SelectionKey> myIterator = myKeys.iterator(); while (myIterator.hasNext()) { SelectionKey myKey = myIterator.next(); if (myKey.isReadable()) { SocketChannel myClient = (SocketChannel) myKey.channel(); ByteBuffer myBuffer = ByteBuffer.allocate(256); myClient.read(myBuffer); String result = new String(myBuffer.array()).trim(); } crunchifyIterator.remove(); } }

파일(or 소켓입력)을 읽어주세요 라고 부탁하고 나서, 자신의 일을 하다가 파일이 읽혀졌는지 selector.select() 를 통해서 무한 대기 즉 우체국에 전화 걸어서 접수원이 기다리라고 해서 대기중 (블럭).

selector.select() 가 리턴을 함 (접수원이 짐이 가득 실어 졌다고 말함) 
그러면 이제 우체국에가서 짐을 실음.(사실 짐을 싣는 과정도 블러킹이지만 멀티쓰레드를 이용해서 병목을 줄일 순 있음)  짐을 싣는 도중에 우체국은 다른 사람 짐을 처리하기 위해 자기 할 일 함.(비동기) 


논블럭/비동기 - C++ (IOCP)

while (! isStop())  
{
    // 위의 이야기에서는 한명의 트럭기사가 처리했지만, 4명의 오너배달부들이 트럭을 감시 할 수도 있다. 
    // 그럼 한명이 병목되더라도 나머지 기사들이 들어온 짐들을 처리 할 수 있을 것이다.
    if (GetQueuedCompletionStatus(m_hIOCP, &dwTrans, &pKey, (LPOVERLAPPED*)&pOV, 64))
    {
        if (pOV)
        {
            MySession* sess = pOV->m_sess;
                ....

            if (pOV == &sess->m_recv1) //트럭1(버퍼1)을 다 실었다고 우체국으로 부터 연락옴 
            {
                bool error = false;
                sess->m_recv1.m_size = dwTrans; // 받은 데이터 사이즈 
                sess->m_recv2.Reset(); //recv1 버퍼를 처리할것이고, recv2 버퍼는 OS한테 넘긴다.
             //트럭을 가져온다(기술적으로는 os버퍼에서 응용버퍼로 데이터를 이동한다) 

                if (! ReadFile((HANDLE)sess->m_sock, (LPVOID)sess->m_recv2.m_data.c_str(), (DWORD)sess->m_recv2.m_data.size(), &dwTrans, &sess->m_recv2))
                {
                    if (GetLastError() != ERROR_IO_PENDING)
                        error = true;
                }

                OnData(*sess, sess->m_recv1);// 트럭에 실린 짐을 처리하기 시작한다.
                if (error)
                    OnClose(*sess);
            }

            else if (pOV == &sess->m_recv2)
            {
                //우체국에서 트럭2에 짐을 다 실었을 경우에 처리한다.
                // 트럭만 바뀌었을 뿐이지 위와 동일

            }
        }
    }
}

 
bool MyServer::onData(MySession& sess, MyPacket& data)
{
    std::string cmd, dat;
    while (recvPacket(sess, cmd, dat))
    {
       //우체국으로 부터 받은 트럭으로 부터 배달을 시작함 
       //여기서 멀티쓰레드 사용하면 (알바배달부를 더 고용하면) 더 효율적이게 됨 
       //물론 너무 많이 만들어도 곤란하다. 적절하게
   
    }
    return false;
}

// 이 코드는 트럭에 실린 짐을 확인하는 코드이다.
// 즉 트럭에 실린 짐이 김연아에게 갈 짐이 맞는지 확인하는 것이다.
// 트럭에 실린 짐이 김연아에게 갈 짐 중에서 50% 밖에 실리지 않았다면
// 김연아의 짐이 100% 될 때까지 트럭을 다시 우체국에 보내고 처리하지 않는다. 

// 기술적으로는 "syn:: ~~~ ::end" 까지의 패킷이 완성되야 일 처리를 시작한다는 것이다.
static bool recvPacket(MySession& sess, std::string& cmd, std::string& data)
    {
        size_t st = sess.m_recvs.find("syn::");
        if (st == std::string::npos)
            return false;

        ...... [패킷 작업] 패킷의 끝인 ::end 를 찾음 ......     

        if (dt)
            data = sess.m_recvs.substr(sz+1, dt);

        return true;
    }

* 참고로 자바로도 가능하다 (NIO2) 



하마블로그


21
26
  • 댓글 14

  • 꾸아앙
    1k
    2018-02-13 12:59:06

    전 이런식으로 이해했었는데요.. ㅋㅋ;

    친구부를때(listen)

    카톡 한번만 보냄(block)
    올때까지 카톡 계속보냄(non-block)

    친구(data)가 도착하니까 부른애가 맞이해줌(sync)
    친구(data)가 도착하니까 다른애가 맞이해줌(async)

    1
  • 24TO26
    380
    2018-02-13 21:43:14

    잘봤습니다.


    나머지 3개는 다 감이 잡히는데 논블럭/동기는... '저짓거리를 왜하지?' 이생각만 드네요;; 왜 저렇게 비효율적인 구조일까. 저럴수밖에 없는 이유라도 있는걸까 이생각밖에 안듬. 

    0
  • zepinos
    15k
    2018-02-14 10:29:22

    저도 예전에 여기서 관련 내용으로 지적 받은 적이 있었습니다. 저 역시 구분을 안하고 말하고 있더라구요. 그냥 논블록 비동기 라이브러리 쓰니까요. 사용 방법의 차이도 거의 없고...(아예 없나?)

    0
  • 히히히히힣
    217
    2018-02-14 12:01:02

    잘 설명 해주신거 같습니다.


    동기 = 요청 시 바로 응답 받을 수 있음(요청에 대한 응답을 실 시간으로 받을 수 있다는 의미)

    비동기 = 요청 시 바로 받을 수도 있고 나중에 받을 수도 있음(요청에 대한 응답이 언제 올지 모른다는 의미)


    제가 이해하고 있던 내용은 위와 같은데 맞는건가요?

    그리고 추가적으로 질문이 있습니다만 요청 시에 원했던 요청에 대한 응답이 오지 않았을 때를 논블럭이라고 하는 건가요?

    예를 들면 1+1 요청 보내면 2로 응답이 오면 블럭이고 문제가 생기거나 2로 응답이 오지 않으면 논블럭 이런 식으로요


    0
  • 하마
    5k
    2018-02-14 13:29:29 작성 2018-02-14 13:48:43 수정됨

    MEAN Stack

    - 일을 시킨 쪽에서 일일이 확인 하느냐
    - 일을 받은 쪽에서 결과를 처리하느냐 

    의 기술적 선택 문제로 볼 수 있는데, 일일이 확인 하느냐의 "정도" 차이로 나누어 질 거 같습니다.
    즉 일일이 확인 하는 "정도"를 심하게 가져가면 "동기" 라고 본것이죠. 결국 논블록/동기의 케이스는 억지로 4칸중 하나를 채우기 위해 만들어진 정의라고 보시면 될 거 같습니다. 일일이 확인 하는 정도를 느슨하게 가져가는 경우에는 저런 방식이 종종 사용되죠. 이 경우는 비동기라고 봐야 할 것이구요.

    while(!Future.isDone){ // 완성되었는지 확인하고..

           ...  완성되지 않았으면 내 할일 하고..

    }

    .. 완성 되었으면 그 일 처리하고..

    이런식으로~


    꾸아앙,히히히히힣

    제 생각에 그건 좀 아닌 거 같네요.  글의 내용 중에서 질문을 주면, 포커스를 줄일 수 있을 거 같습니다. 답변도 잘 해드릴 거 같고요.

    0
  • 스타
    3k
    2018-02-14 14:17:28

    하마님 글보고 전 이렇게 이해했어요.

    블럭/넌벌럭 : 시키는 입장

    동기/비동기 : 일하는 입장


    블럭 : 일 시키고 끝날때까지 기다리겠다.

    넌블럭 : 일 시키고 딴짓 하고 있겠다.

    동기 : 시킨일은 한번에 한가지만 하겠다.

    비동기 : 시킨일은 다 받아서 해 주겠다.

    0
  • 앙앙이
    2k
    2018-02-14 16:38:01

      마지막 4번째 IOCP 에서는 만약 onData 처리시

    동접 천명이라고 한다면 onData 호출은 천건이 나올 수 있다는건가요?


    저 같은 경우 비동기 서버를 만드는 이유들중 하나가 쓰레드 스위치를 줄이기 위함입니다.

    그런데 AsynchronousSocketChannel 어 좋네 하다가 

    쓰레드 갯수 통제가 안될것 같다는 의문이 생겨서 함 질문해 봅니다.

    0
  • 히히히히힣
    217
    2018-02-14 17:16:55

    음......글의 내용으로 질문을 다시 드리자면


    동기 / 비동기 중 A라는 행위와 B라는 행위가 순차적으로 동작한다면 동기라고 한다라는 의미라는 게

    A = 요청이라는 행위, B = 응답을 준다는 행위로 볼 수도 있지 않을까 싶어서 말씀 드렸는데 다른 내용일까요?


    그리고 블럭 / 논블럭에서 이해하기로는 A라는 함수 호출 시 기대하는 행위라는 게 함수 호출 시 원했던 값이 리턴되는 케이스에 대해 블럭이나 논블럭이냐로 나뉘는 것 같아서 말씀 드렸습니다.


    0
  • 하마
    5k
    2018-02-14 17:24:45 작성 2018-02-14 19:20:52 수정됨

    앙앙이 

    onData 함수를 보면 세션이 파라미터로 넘어갑니다. 세션(내부적으로 소켓포인터를 가지고 있음)당 하나의 클라이언트이란 건데, 결국 클라이언트당 쓰레드가 나누어 지지 않고 있다는 겁니다.

    * 1000명의 클라이언트가 데이터를 보내 온다면 당연히 1000번의 onData 함수가 불려지게 됩니다.

    소스 시작부분의 주석과 소스를 다시 보면 

    if (GetQueuedCompletionStatus(m_hIOCP, &dwTrans, &pKey, (LPOVERLAPPED*)&pOV, 64))

    이렇게 WINDOWS OS I/O가 무엇인가를 처리했는지 확인하는 함수를 호출하는 쓰레드가 있는데요. 이게 하나라고 생각하시면 됩니다. (정확히는 CPU * N . 개발자마음) 

    이 쓰레드 하나로 I/O 및 onData() 같은 후속 처리하는거죠. 클라이언트마다 쓰레드를 따로 가지고 있으면서 처리되는게 아니라,(다시 말하지만 1000개의 쓰레드가 만들어지는게 아닙니다) 어떤 클라이언트의 I/O가 발생될 경우에만, onData() 에서 처리됩니다.

    따라서  onData() 처리에서 시간을 많이 소비하는 로직이 들어 있다면 병목입니다. 하나의 쓰레드이기 때문에 그 놈이 시간을 다 잡아먹으면 나머지 999명의 클라이언트에 대해서는 홀드됩니다.
    따라서 이 시간을 적게 소비하는 경우에 한해서 싱글쓰레드 비동기 서버가 위력을 발휘하게 되는 것입니다.


    정리) 

    1. OS I/O가 무엇인가를 처리했는지 확인하는 쓰레드의 경우 CPU * 1~8 로 상황에 맞게 만듭니다. (Node 서버의 경우 내부적으로 이 쓰레드가 하나로 알고 있습니다. 전형적인 싱글쓰레드 비동기 서버. 자바스크립트 자체가 싱글쓰레드 기반이니 어쩔수 없을듯)

    2. onData() 내부에서도 쓰레드 분기를 해줄 수 도 있습니다. 동접이 별로 없고, 하나의 연결에 대한 일처리가 빡셀때~


    1
  • 앙앙이
    2k
    2018-02-14 18:26:45

    // 하마

    답변 감사합니다.


    개인적으로 리눅스 좋아하기에 리눅스 부심이 있는데요.

    이것에 상처를 준 놈이 바로 윈도우 IOCP 입니다.

    IOCP 를 활용하는 c++ 소스 예제를 보니

    자바가 많이 부러워서  AsynchronousSocketChannel 만들었구나 합니다.

    selector 는 전통적인 유니스 프로그래밍 방식(?) 이라고 생각합니다.

    하마님 글은 은근 IOCP 찬가로 읽혀지기에 속상합니다.

    아래 글은 2005년 글이지만 댓글 쓰레드 따라가다 보면 2013년 IOCP vs EPOLL 비교한 사이트 주소(https://www.slideshare.net/sm9kr/iocp-vs-epoll-perfor?qid=3c154372-42f3-4505-aa95-2cb21ab78984&v=qf1&b=&from_search=2) 가 있습니다.  

    리눅스 부심있다고 하지만 객관적인 데이터를 부정할 수 는 없는 노릇이지요.


    2005년 "Win32 IOCP를 따라잡을 Unix, Linux, *BSD계열의 방법은?" 참고 주소 : https://kldp.org/node/60222

    0
  • Itshighnoon
    6
    2018-02-16 19:13:43

    좋은 설명글 감사합니다 !

    0
  • 로보넥스
    2018-02-19 05:05:25

    동기 : 직접 어떤 함수를 호출함

    비동기 : 콜백함수를 전달하여 호출당함

    블럭 : 호출한 함수관련 처리가 동일 스레드에 국한

    논블럭 : 호출한 함수가 타 스레드와 관계됨


    0
  • 꾸아앙
    1k
    2018-02-20 10:24:55

     로보넥스

    블럭/넌블럭은 아닌것 같습니다만...

    지금 회사에서 돌아가고있는 서버의 코드가 sync/non-block인데 싱글스레드로 돌리고 있습니다;;

    블럭/넌블럭의 차이는 소켓버퍼에 변동이 있을때까지 기다리냐/안기다리냐의 차이가 아닌가요?

    넌블럭은 호출시점에 소켓버퍼에 데이터가 있으면 데이터를 가져오고, 데이터가 없으면 우드블럭으로 알려주는걸로 알고있습니다...


    앙앙이

    윈도우의 IOCP와 리눅스의 epoll의 성능차이는 비교의 의미가 없다고 생각하는데요;; ㄷㄷ

    퍼포먼스를 비교하려면 동일한 급의 숙련도를 가진 개발자가 서로 각각 만들어서 비교해봐야 하는데

    그게 가능한 일인가요...?

    그저 자기가 사용하는 방식이 더 좋다고 생각하고 싶은 사람들의 억지라고밖에 안보이는데요;;

    거기에 요즘은 서버 성능도 좋아져서 select/poll 방식만으로도 왠만한 사용자수를 버티는 마당에...

    전혀 불필요한 얘기같습니다...

    1
  • 로보넥스
    2018-02-20 12:17:54

    좋은 지적이십니다만.

    소켓의 경우.. 머 대체로 소켓용어죠. 논블럭.

    3초간(연결실패) 또는 15초간(ip얻기 실패)

    내 프로그램이 뻗지 않는 이유는

    다른 쓰레드의 처리 또는 아예 다른 프로세스의

    처리가 있기 때문입니다.


    간단히 표현한다고 그랬지만 굳이 제 정의가

    틀린것은 오히려 블럭의 경우 동일 스레드에

    국한된다고 한 점이죠. 용어의 정의가 법령도 아니기에

    간단히 의도를 살려본 겁니다^^

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.