동기 (Synchronous)
동기정의
- 동기는 두 가지 이상의 대상(함수, 애플리케이션 등)이 서로 시간을 맞춰 행동하는 것이다.
- 작업을 요청한후에 결과가 나올때까지 기다리다가 나온후 처리하는것
- 호출된 함수의 리턴하는 시간과 결과를 반환하는 시간이 일치하는 경우
비동기 (Asynchronous)
비동기정의
- 비동기는 동기와 반대로 대상이 서로 시간을 맞추지 않는 것을 말한다. 예를 들어 호출하는 함수가 호출되는 함수에게 작업을 맡겨놓고 신경을 쓰지 않는 것을 말한다.
- 호출된 함수의 리턴하는 시간과 결과를 반환하는 시간이 일치하지 않는 경우
블럭킹 (Blocking)
블럭킹 정의
- I/O 작업은 Application Level 에서 수행할수없다. I/O 작업은 커널에 의해서 수행된다. 따라서 프로세스는 System call 시스템 함수로 커널에게 I/O 작업을 요청한다 이때 context-switching 이 발생한다. 커널에서 수행하던 작업을 마치고나서 데이터를 프로세스에게 리턴할때 스레드에 걸려있던 블락이 해제 된다.
- 애플리케이션 실행 시 운영체제 대기 큐에 들어가면서 요청에 대한 system call이 완료된 후에 응답을 보낸다. 커널작업이 완료하기 전까지 유저 프로세스는 작업을 중단하고 대기해야한다. I/O작업은 CPU를 거의 사용하지 않기 떄문에 CPU낭비가 심하다.
- Blocking은 직접 제어할 수 없는 대상의 작업이 끝날 때까지 제어권을 넘겨주지 않는 것이다. 예를 들어 호출하는 함수가 IO를 요청했을 때 IO처리가 완료될 때까지 아무 일도 하지 못한 채 기다리는 것을 말한다.
블럭킹 (Blocking)
블럭킹 정의
- 애플리케이션 실행 시 운영체제 대기 큐에 들어가지 않고, 실행 여부와 관계없이 바로 응답을 보낸다. 바로 응답하기 힘든 경우, 에러를 반환하는데 정상데이터를 받을 때까지 계속해서 요청을 다시 보낸다.
- Blocking은 직접 제어할 수 없는 대상의 작업이 끝날 때까지 제어권을 넘겨주지 않는 것이다. 예를 들어 호출하는 함수가 IO를 요청했을 때 IO처리가 완료될 때까지 아무 일도 하지 못한 채 기다리는 것을 말한다.
IO모델
- 블로킹 : 애플리케이션 실행 시 운영체제 대기 큐에 들어가면서 요청에 대한 system call이 완료된 후에 응답을 보낸다.
- 논블로킹 : 애플리케이션 실행 시 운영체제 대기 큐에 들어가지 않고, 실행 여부와 관계없이 바로 응답을 보낸다. 바로 응답하기 힘든 경우, 에러를 반환하는데 정상데이터를 받을 때까지 계속해서 요청을 다시 보낸다.
IO이벤트 통지모델은 논블로킹에서 제기된 문제를 해결하기 위해 고안되었다. IO 이벤트를 통지하는 방법은 크게 동기형 통지모델과 비동기형 통지모델로 나눌 수 있다.
- 동기 : 시스템콜을 기다린다. (notify를 사용자 프로세스가 담당) 시스템의 반환을 기다리는 동안 대기 큐에 머무는 것이 필수는 아니다. (블로킹은 필수로 머물러야함)
- 비동기 : 시스템콜을 기다리지 않는다. (notify를 커널이 담당) 요청에 대해 처리완료 여부에 관련없이 응답하고 다음코드를 돌린다. 이후에 운영체제에서 처리완료여부를 알려주고 응답한다.
I/O 통지모델 select
- select는 싱글쓰레드로 다중 I/O를 처리하는 멀티플렉싱 통지모델의 가장 대표적인 방법이다. 해당 파일 디스크립터가 I/O를 할 준비가 되었는지 알 수 있다면, 그 파일 디스크립터가 할당받은 커널Buffer에 데이터를 복사해주기만 하면된다. 이런 목적하에 통지모델은 파일디스크립터의 상황을 파악할 수 있게 하는 기능을 할 수 있어야한다. select는 많은 파일 디스크립터들을 한꺼번에 관찰하는 FD_SET 구조체를 사용하여 빠르고 간편하게 유저에게 파일 디스크립터의 상황을 알려준다.
- 먼저 파일 디스크립터의 번호를 FD_SET에 등록하면 해당 비트의 값이 1로 저장된다. 그리고 I/O처리 준비가 되면 SELECT를 통해 해당 비트의 값을 갱신하고 프로세스는 변경된 값을 보고 커널 버퍼에 데이터를 복사하면 되는 것이다.
- 일반적으로 검사할 수 있는 fd개수가 최대 1024개로 제한된다. 그리고 관찰 영역에 포함되는 모든 파일 디스크립터에 대해서 순회하면서 한번씩 FD_ISSET으로 체크하는 것도 불필요한 체크인것 처럼 보인다.
- 그리고 관찰 대상에 대한 정보인 FD_SET을 계속해서 select문을 통해서 운영체제에게 전달하는 것도 큰 부하를 일으킨다
I/O 통지모델 epoll
- Epoll은 리눅스에서 select의 단점을 보완하여 사용할 수 있도록 만든 I/O통지 모델이다. 파일 디스크립터를 사용자가 아닌 커널이 관리를 하며, 그만큼 CPU는 계속해서 파일 디스크립터의 상태 변화를 감시할 필요가 없다. 즉, select처럼 어느 파일 디스크립터에 이벤트가 발생하였는지 찾기 위해 전체 파일디스크립터에 대해서 순차검색을 위한 FD_ISSET 루프를 돌려야 하지만, Epoll의 경우 이벤트가 발생한 파일 디스크립터들만 구조체 배열을 통해 넘겨주므로 메모리 카피에 대한 비용이 줄어든다.
- epoll은 select의 단점을 많이 개선한 형태의 통지방식이다. FD_SET을 운영체제가 직접 관리하는 것으로 많은 부분이 개선되었다. 하지만 그 본질적인 동작 구조는 select와 크게 다르지 않다. 프로세스가 커널에게 지속적으로 I/O 상황을 체크하여 동기화 하는 개념은 여전히 유효하다. 따라서 epoll의 통지모델 역시 동기형 통지모델이다.
- epoll을 이용한 기법에서는 관찰해야 할 파일 디스크립터와, 그 디스크립터로부터 관찰해야 할 event의 종류를 'epoll 인스턴스'에 저장할 수 있다. 이 'epoll 인스턴스'에 등록된 파일 디스크립터들은 운영체제에 넘겨져 저장되며, 이후에 프로그램이 epoll_wait함수를 호출해 대기하고 있으면 등록된 event가 발생할 때 운영체제가 해당 파일 디스크립터를 프로세스로 넘겨준다. Select 기법에서 직접 파일 디스크립터 배열을 검사하며 event를 찾아내야 했던 것과는 다르게, epoll 기법에서는 모든 계획을 운영체제에 미리 전달한 뒤 epoll_wait 함수를 통해 기다리기만 하면 되는 것이다.
- 관리 fd의 수는 무제한.
- select, poll과 달리, fd의 상태가 kernel 에서 관리하므로 상태가 바뀐것만을 직접 통지 , fd_set 복사가 필요없음.
- 일일이 fd 세트를 kernel 에 보낼 필요가 없음.
- kernel이 fd를 관리하고 있기 때문에 커널과 유저스페이스 간의 통신 오버헤드가 대폭 줄어듬.
동기 블로킹 (Synchronous Blocking)
- 프로그램이 블로킹을 일으키는 시스템 함수를 호출
- 한 작업당 한 번의 사용자-커널사이의 문맥교환 발생
- 정지된 프로그램은 CPU를 사용하지 않고 커널의 응답을 대기
- 프로그램 관점에서 보면 마치 처리로직이 오래걸리는 것 같지만, 사실은 커널의 일을 기다리느라 블록되어 있는 것이다. 이게 개선 포인트
Device = IO.open()
#이 thread는 데이터를 읽을 때까지 아무 일도 할 수 없음
data = device.read()
print(data)
- Synchronous: read() 메서드(애플리케이션)가 리턴하는 시간과 커널에서 결과를 가져오는 시간이 일치한다.
- Blocking: 커널의 작업이 완료될 때까지 대기한다.
3. 동기 논블로킹 (Synchronous Non-blocking)
- 동기블로킹의 개선안이지만 비효율적이다. 왜냐하면 위에서 정리했듯이 논블로킹방식은 정상데이터가 올 때 까지 계속 시스템콜을 하며 문맥교환을 한다.
- IO 지연(latency) 초래한다.
device = IO.open()
ready = False
while not ready:
print("There is no data to read!")
# 다른 작업을 처리할 수 있음
# while 문 내부의 다른 작업을 다 처리하면 데이터가 도착했는지 확인한다.
ready = IO.poll(device, IO.INPUT, 5)
data = device.read()
print(data)
- Synchronous: read() 메서드(애플리케이션)가 리턴하는 시간과 커널에서 결과를 가져오는 시간이 일치한다.
- Non-Blocking: 애플리케이션으로부터 요청을 받은 커널은 작업 완료 여부와 상관없이 바로 반환하여 제어권을 애플리케이션에게 넘겨준다. 커널의 작업이 완료되면 작업 결과를 애플리케이션에게 반환한다.
- 대표적인 예로는 멀티플랙싱을 수행하는 select(), epoll() 함수가 있다.
4. 비동기 블로킹
- IO는 논블로킹이고 알림(notify)가 블로킹인 방식이다.
- select() 시스템함수 호출이 사용자프로세스를 블로킹한다.
- 비효율적이다.
5. 비동기 논블로킹
- 시스템콜이 즉시 IO개시 여부를 반환한다. 사용자프로세스는 다른일을 할 수 있고(CPU는 다른 업무를 볼 수 있다), IO는 백그라운드에서 처리된다.
- IO 응답이 도착하면 신호나 콜백으로 IO전달을 완료한다.
ios = IO.IOService()
device = IO.open(ios)
def inputHandler(data, err):
"Input data handler"
if not err: print(data)
device.readSome(inputHandler)
# 이 thread는 데이터가 도착했는지 신경쓰지 않고 다른 작업을 처리할 수 있다.
ios.loop()
- Asynchronous: readSome() 메서드(애플리케이션)가 리턴하는 시간과 커널에서 결과를 가져오는 시간이 일치하지 않는다.
- Non-Blocking: 애플리케이션으로부터 요청을 받은 커널은 작업 완료 여부와 상관없이 바로 반환하여 제어권을 애플리케이션에게 넘겨준다. 작업이 끝나면 애플리케이션에게 시그널 또는 콜백을 보낸다.
- 대표적인 예로는 윈도우에서 멀티플랙싱을 수행하는 IOCP가 있다.(epoll()보다 성능이 좋다.)
파일디스크립터
파일디스크립터 정의
- 흔히 유닉스 시스템에서 모든 것은 파일이라고 한다. 일반적인 정규파일(Regular File)에서부터 디렉토리(Directory), 소켓(Socket), 파이프(PIPE), 블록 디바이스, 캐릭터 디바이스 등등 모든 객체들은 파일로써 관리된다. 유닉스 시스템에서 프로세스가 이 파일들을 접근할 때에 파일 디스크립터(File Descriptor)라는 개념을 이용한다.
- 파일 디스크립터는 '0이 아닌 정수', 'Non-negative Integer' 값이다. 즉, 음수가 아닌 0과 양수인 정수 값을 갖는다. (unsigned int 값이라고 보면 된다.) 프로세스가 실행 중에 파일을 Open 하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중에 사용하지 않는 가장 작은 값을 할당해 준다. 그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근 할 때, FD 값을 이용해 파일을 지칭 할 수 있다.
- 프로그램이 프로세스로 메모리에서 실행을 시작 할 때, 기본적으로 할당되는 파일 디스크립터들이 있다. 바로 표준 입력(Standard Input), 표준 출력(Standard Output), 표준 에러(Standard Error)이다. 이 들에게 각각 0, 1, 2 라는 정수가 할당
출처: [공부 모음]
출처: [Developer Ahn]
참조글
egloos.zum.com/YSocks/v/482118
https://rammuking.tistory.com/entry/Epoll의-기초-개념-및-사용-방법
https://dev-ahn.tistory.com/96
https://velog.io/@codemcd/Sync-VS-Async-Blocking-VS-Non-Blocking-sak6d01fhx
https://asfirstalways.tistory.com/348
https://sjh836.tistory.com/109