오늘은 공룡책🦕에 언급된 Blocking과 Nonblocking, Synchronous와 Asynchronous에 대해서 정리해보고자 합니다. Synchronous와 Asynchronous의 개념은 쓰레드I/O에서 등장하게 되는데요, 각각이 어떠한 의미로 사용되는지도 구분해서 살펴보도록 하겠습니다.

목차

  1. 1. I/O 관점에서의 동기와 비동기
  2. 2. 논블록킹과 비동기 I/O
  3. 3. 쓰레드 관점에서의 동기와 비동기
  4. 4. 정리해보면

I/O 관점에서의 동기와 비동기

동기(synchronous) 장치는 예측 가능한 응답 시간을 갖는 데이터 전송 작업을 수행합니다. 이와 반대로 비동기(asynchronous) 장치는 데이터 전송 작업에 대한 응답 시간이 불규칙 하거나 예측이 불가능합니다.

A synchronous device performs data transfers with predictable response times, in coordination with other aspects of the system. An asynchronous device exhibits irregular or unpredictable response times not coordinated with other computer events.

동기 장치로는 테이프, 비동기 장치로는 키보드를 예로 들수 있습니다.


아래 그림은 I/O 처리에 있어서 동기와 비동기의 차이(I/O처리가 완료될때까지 기다리느냐 기다리지 않느냐)를 보여주고 있습니다.


논블록킹과 비동기 I/O

시스템 콜 인터페이스의 또 다른 측면은 블록킹(blocking) I/O와 논블록킹(nonblocking) I/O 중 무엇을 선택하는지와 관련되어있습니다. 어플리케이션이 블록킹 시스템 콜을 요청하는 경우에는 해당 시스템 콜을 요청한 쓰레드의 실행이 중단됩니다. 이 쓰레드는 운영 체제의 실행 큐(run queue)에서 대기 큐(wait queue)로 이동하게 됩니다. 시스템 콜이 완료된 이후에는, 해당 쓰레드가 실행이 재개될 수 있는 상태인 실행 큐(run queue)로 다시 이동합니다. 실행이 재개된 어플리케이션에서는 시스템 콜로 부터 반환된 결과를 전달받습니다. I/O 장치에 의해 수행되는 물리적인 행위들은 일반적으로 비동기적(I/O 장치마다 물리적 행위들을 수행하는데 걸리는 시간이 다양하고 예측할 수 없기때문에)입니다. 그럼에도 불구하고 운영 체제는 블록킹 어플리케이션 코드가 논블록킹 어플리케이션 코드보다 상대적으로 작성하기 쉽기 때문에 어플리케이션 인터페이스로 블록킹 시스템 콜을 제공하고 있습니다.

Another aspect of the system-call interface relates to the choice between blocking I/O and nonblocking I/O. When an application issues a blocking system call, the execution of the calling thread is suspended. The thread is moved from the operating system’s run queue to a wait queue. After the system call completes, the thread is moved back to the run queue, where it is eligible to resume execution. When it resumes execution, it will receive the values returned by the system call. The physical actions performed by I/O devices are generally asynchronous— they take a varying or unpredictable amount of time. Nevertheless, operating systems provide blocking system calls for the application interface, because blocking application code is easier to write than nonblocking application code.


몇몇 사용자 레벨의 프로세스들에서 논블록킹 I/O가 필요한 경우가 있습니다. 한가지 예로 모니터에 데이터를 출력함과 동시에 키보드와 마우스의 입력 값을 받는 사용자 인터페이스를 생각해 볼 수 있습니다. 또다른 예로 출력 값의 압축을 풀면서 모니터 화면에 뿌려줌과 동시에 디스크에 저장된 파일에서 프레임들을 읽어들이는 비디오 어플리케이션을 생각해볼 수 있습니다.

Some user-level processes need nonblocking I/O. One example is a user interface that receives keyboard and mouse input while processing and displaying data on the screen. Another example is a video application that reads frames from a file on disk while simultaneously decompressing and displaying the output on the display.


어플리케이션 개발자가 I/O와 동시에 또 다른 실행 흐름을 처리할 수 있는 한가지 방법은 멀티쓰레드를 사용하는 것입니다. 일부 쓰레드는 블록킹 시스템 콜을 처리할 수 있고 나머지 쓰레드들은 작업을 계속해나가면 됩니다. 몇몇 운영 체제는 논블록킹 I/O 시스템 콜을 제공합니다. 논블록킹 호출은 쓰레드의 실행을 중단시키지 않는 대신 몇 바이트가 전달됐는지에 대한 정보를 곧바로 리턴합니다.

One way an application writer can overlap execution with I/O is to write a multithreaded application. Some threads can perform blocking system calls, while others continue executing. Some operating systems provide nonblocking I/O system calls. A nonblocking call does not halt the execution of the thread for an extended time. Instead, it returns quickly, with a return value that indicates how many bytes were transferred.


논블록킹 시스템 콜에 대한 대안으로 비동기 시스템 콜이 있습니다. 비동기 호출은 I/O 처리가 끝나기를 기다리기 않고 곧바로 리턴합니다. 쓰레드는 계속해서 코드를 실행해 나갑니다. I/O 처리가 완료되는 시점이 되면 쓰레드의 주소 공간에 저장된 변수를 사용하거나 시그널 또는 인터럽트를 발생시키거나 쓰레드의 선형 컨트롤 흐름 바깥에서 실행되는 콜백 루틴을 통해 I/O 처리 완료 여부가 쓰레드로 전달됩니다. 논블록킹 read() 함수의 경우에는 사용 가능한 데이터가 무엇이든 즉시 리턴(리턴 값은 요청받은 바이트 전체가 될 수도 있고 혹은 그 보다 적거나 또는 아예 없을 수도 있음)하게 되며, 비동기 read() 함수의 경우에는 함수 전체에 걸쳐 수행될 예정이나 나중에 완료될 전송을 요청한다는 것에 차이가 있습니다.

An alternative to a nonblocking system call is an asynchronous system call. An asynchronous call returns immediately, without waiting for the I/O to complete. The thread continues to execute its code. The completion of the I/O at some future time is communicated to the thread, either through the setting of some variable in the address space of the thread or through the triggering of a signal or software interrupt or a call-back routine that is executed outside the linear control flow of the thread. The difference between nonblocking and asynchronous system calls is that a nonblocking read() returns immediately with whatever data are available— the full number of bytes requested, fewer, or none at all. An asynchronous read() call requests a transfer that will be performed in its entirety but will complete at some future time.

해당 설명만으로는 nonblocking과 asynchronous의 차이가 명확히 이해가 되질 않아 stack-overflow의 관련 답변을 참조하였습니다. 해당 링크에서 Non-Blocking에 대한 설명 중 “expect caller to execute the call again” 라는 문장이 눈에 들어오네요🙂


논블록킹 동작의 좋은 예로 네트워크 소켓에서 사용되는 select() 시스템 콜이 있습니다. 이 시스템 콜은 최대 대기 시간 정보를 인자로 갖고 있습니다. 이 값을 0으로 설정하게 되면 쓰레드는 블록킹없이 네트워크 활동에 대해서 주기적으로 확인합니다. 하지만 select() 함수를 사용하게 되면 추가적인 오버헤드가 발생하게 되는데 이는 select() 함수가 오직 I/O가 가능한지 여부만을 확인(실제 데이터 전송은 일어나지 않음)하기 때문입니다. 실제 데이터 전송을 위해서는 select() 함수 이후에 read()나 write() 함수 호출이 반드시 수반되어야 합니다.

A good example of nonblocking behavior is the select() system call for network sockets. This system call takes an argument that specifies a maximum waiting time. By setting it to 0, a thread can poll for network activity without blocking. But using select() introduces extra overhead, because the select() call only checks whether I/O is possible. For a data transfer, select() must be followed by some kind of read() or write() command.


쓰레드 관점에서의 동기와 비동기

멀티 쓰레드 생성의 2가지 전략인 비동기 쓰레드와 동기 쓰레드에 대해 소개해보려합니다. 비동기 쓰레드에서는 부모 쓰레드가 자식 쓰레드를 생성하고나서 자신의 실행을 이어나가는데 이것은 부모와 자식 쓰레드가 동시에 작업을 수행하며 각각의 실행 흐름이 독립적임을 의미합니다. 쓰레드들은 독립적이므로 대게 쓰레드 간의 데이터 공유가 발생하게 됩니다.

We introduce two general strategies for creating multiple threads: asynchronous threading and synchronous threading. With asynchronous threading, once the parent creates a child thread, the parent resumes its execution, so that the parent and child execute concurrently and independently of one another. Because the threads are independent, there is typically little data sharing between them.

동기 쓰레드는 부모 쓰레드가 한개 또는 그 이상의 자식 쓰레드를 생성하고 모든 자식의 작업이 끝날때까지 대기한 후에 부모 자신의 실행이 재개됩니다. 부모에 의해 생성된 자식 쓰레드들은 동시에 작업을 수행하지만 자식 쓰레드들의 작업이 모두 완료될때까지 부모 쓰레드는 중단됩니다. 각각의 자식 쓰레드가 작업을 마치게 되면 그것들은 종료되고 그들의 부모 쓰레드로 합류(join)하게 됩니다. 일반적으로 동기 쓰레드는 쓰레드들 간의 중요한 데이터 공유를 수반합니다. 예를 들어 부모 쓰레드가 여러 자식 쓰레드에 의해 계산된 결과들을 취합해야할 수 있습니다.

Synchronous threading occurs when the parent thread creates one or more children and then must wait for all of its children to terminate before it resumes. Here, the threads created by the parent perform work concurrently, but the parent cannot continue until this work has been completed. Once each thread has finished its work, it terminates and joins with its parent. Only after all of the children have joined can the parent resume execution. Typically, synchronous threading involves significant data sharing among threads. For example, the parent thread may combine the results calculated by its various children.


정리해보면

블록킹 vs 논블록킹

  • 시스템 콜의 요청 주체가 되는 쓰레드를 중단하느냐(대기 큐로 이동시키느냐) 그렇지 않느냐

논블록킹 vs 비동기

  • 시스템 콜이 곧바로 반환되는 경우, 사용할 수 있는 데이터를 갖고 있느냐 그렇지 않느냐

동기 vs 비동기

  • 시스템 콜의 완료를 기다리느냐 기다리지 않느냐



해당 게시글에서 발생한 오탈자나 잘못된 내용에 대한 정정 댓글 격하게 환영합니다😎

Reference