본문 바로가기

컴퓨터 과학/한 권으로 읽는 컴퓨터 프로그래밍

컴퓨터 과학 - 병렬성과 비동기성

반응형

 

 

[개요]

 

  • 컴퓨터는 과거 싱글코어 프로세서와 달리 , 멀티코어 프로세서로 한번에 둘 이상의 일을 처리 할수 있게 되었다. 
  • 이 때문에 경합 조건이 발생했는데, 예를 들어 잔고가 100 만원인 계좌를 은행 창구에서 50만원을 찾으려 할때 배우자가 75 만원을 찾으려 하면 같은 시점에서 돈을 인출하려 했기 때문에 어느것이 우선순위인지 알수 없기에 경합 조건이 발생한다. 이 경우, 둘 중 한사람에게만 돈을 지급해야 초과 인출을 막을 수있기에 공동 계좌를 lock 잠근다. 이 말은 컴퓨터에서도 동일하게 일부 연산들은 근본적으로 멀티 테스킹을 막아야 한다는 뜻이다.

 

[용어 정리]

 

  • 경합조건 
    • 두 개 이상의 Operation이 같은 System Resource에 Access할 때, 그들사이의 경쟁에 의해 수행 결과를 예측할 수 없게 되는 상태를 의미한다.
  • 공유 자원
    • 데이터를 처리하기 위한 자원이 공유될 경우를 의미
  • 프로세스
    • 컴퓨터에서 연속적으로 실행되고 있는 컴퓨터 프로그램
  • 스레드
    • 프로세스 내에서 실행되는 여러 흐름의 단위를 의미한다.

 

[내용]

 

경합 조건이란 무엇인가

 

  • 경합 조건이란 2개의 프로그램이 같은 자원에 동시에 접근하고, 자원 사용 순서에 따라 결과가 달라지는 경우를 뜻한다.
  • 위의 예시와 같이 계좌 잔고 라는 공유 자원 에서 두 프로그램이 접근하는 타이밍에 따라 결과가 달라진다는 것을 알수 있다. 예를 들어 100만원 잔고가 있는 통장에서 10만원을 인출하고, 나중에 50만원을 인출하면 40만원만 남겠지만, 100만원 이 있는 잔고에서 10만원과 40만원을 동시에 인출하면 잔고가 90만원이 될수도 있고 60만원이 될수도 있다.

 

공유 자원

 

  • I/O 장치를 공유하는 경우 프린터를 생각해 보면된다. 여려 문서가 뒤섞이면 공유 프린터가 제대로 작동하지 않을 것이다.

 

프로세스와 스레드

 

  • 운영체제의 기능중 하나는 여러 작업을 관리하는 것이다. 운영체제는 프로세스를 관리한다. 프로세스는  사용자 공간에서 실행되는 프로그램이다. 멀티 코어 시스템에서는 여러 프로그램이 병렬로 실행될 수 있다. 이때 프로그램이 자원을 공유하면 경합조건이 발생할 수 있다. 
  • 자원을 공유하는 프로세스들은 서로 통신을 해야 자원을 공유할 수 있다. 프린트 서버가 좋은 예다. 문서 출력을 처리하는 동시에 다른 프로그램이 자신에게 통신하는것도 신경써야 한다. 1980년대 버클리 캘리포니아 주립 대학교에서 네트워크 관련 코드가 개발됐다. 프로그램이 여러 곳에서 들어오는 동작을 기다렸다가 동작에 따라 적절한 핸들러를 호출 할수 있게 되었다. 하지만 GUI 방식이 들어오면서 사용자의 입력을 기다리느라 대기하는 시간이 많이 생겼다. 그래서 핸들러를 인터럽트 하는 방식이 생겨났다. 실행하는 중간에 잠시 실행을 중단 하면서 상태를 저장하고, 나중에 그 위치로 돌아와 실행을 재개할 수 있다는 뜻이다. 
  • 저장할 상태는 스택에 있다. 프로세스마다 스택을 하나씩 가지고 있다. 그렇기에 상태를 저장하기에 쓰레드가 필요하다. 스레드는 프로세스 내에서 실행되는 여러 흐름의 단위를 의미한다. 자체적으로 스택을 가진다. 

  • 한 쓰레드에서 다른 쓰레드로 넘어 갈때 스레드 스케줄러가 CPU레지스터를 저장해야 한다. 이는 운영체제가 프로세스에서 다른 프로세스로 문맥을 전환할 때 일어나는 일과 비슷하다. 쓰레드가 유용하다는 사실이 입증된 다음부터는 기계와 독립적인 API가 표준화 되었다.
  • 하지만 쓰레드에도 문제가 발생했는데, 쓰레드는 데이터를 공유하기 때문에, 보안문제가 생긴다. 두번째로 한 탭에서 버그가 나면 다른 탭 전체가 멈춰 버린다. 문제가 생긴 부분과 무관한 탭에서 작업했던 내용을 잃어 버릴 수 있다.  세번째로 쓰레드가 완료하는게 오랜시간이 걸리면 다른 모든 쓰레드가 실행되지 못해서 문제가 생길수 있다. 

  • 우리가 처리해야 할 문제는 공유자원이 아니다. 작은 연산으로 이루어진 작업을 어떻게 쪼개어 작업할 수 있을까이다. 우리는 상호 배제 메커니즘을 통해 처리하게 만들어야 한다. advisory lock을 만들어서 달성한다.
  • advisory lock 흐름
    • 잔고 잠금 -> 100만원 읽음 -> 10만원 더함 -> 110만원 씀 -> 잔고 잠금 해제 -> 잔고 잠금 -> 110만원 읽음 -> 50만원 더함 -> 160 만원 씀 -> 잔고 잠금 해제
  • 하지만 이 경우 프로그램이 느려지거나 악의적인 동작으로 락이 해제되기까지 시간이 걸릴수 있다.

트랜젝션과 작업 크기

  • 우리는 각 연산을 수행하기 전에 연산이 성공했는지 알아야 하기 때문에, 양방향 통신 이어야 한다. 
  • 성능 향상시키는 가장 좋은 방법은 트랜잭션이다. 트랜잭션이라는 말은 각 연산을 독립적으로 수행하는 대신 한꺼번에 묶어서 처리한다.

  • 트랜잭션을 사용하여 락이 걸려 있는 시간을 최소화 할 수 있다. 

락 대기

  • 프로그램이 락을 기다리는 동안 다른 일을 할 수 없으면, 트랜젝션을 사용해 세밀하게 만들어도 아무런 의미가 없다. 결국 다중성 이라는 말을 멀티테스킹 전체 측면에서 살펴봐야 한다.
  • 스핀을 통해 락을 성공적으로 얻을때까지 락 획득을 시도한다. 그리고 락을 요청하는 존재가 락 획득 요청을 락을 관리하는 쪽에 등록하고, 요청이 받아들여졌을때 통지를 받는 방식을 사용하면 요청하는 쪽에서는 기다리는 동안 유용한 일을 할수 있다. 
  • 일부 운영체제는 락기능을 제공하는데, 블로킹비블로킹이 있다. 블로킹은 시스템이 락을 할당할 수 있을때까지 락을 요청한 프로그램을 일시중단시킨다는 뜻이고, 비블로킹은 프로그램이 실행되고 나중에 락을 얻었는지 여부를 통지받는 방식이다.

교착 상태

  • 복잡한 시스템에서는 여러 락을 사용하는 경우도 있다. 이때 프로그램 1은 락 A를 얻고, 프로그램 2는 락 B를 얻는다. 이때 서로 다른 프로그램들은 연결상태가 꼬여 있을때 상대방의 락을 가지게 되면서 상대방의 락을 해제하려는 시도가 일어날수 있다. 이를 교착상태라고 하는데, 이는 프로그래머가 잘못된 코드를 작성했을때 발생한다.
  • 교착 상태는 다음 4가지 원인에서 발생한다
    • 공유 자원을 함께 쓸수 없어서 어느 한 프로세스가 독점적으로 사용해야 하는경우
    • 프로세스들은 어느 자원을 점유한 상태에서 다른 자원을 요청한다.
    • 각 프로세스가 서로 순환적으로 다른 프로세스가 갖고 있는 자원을 요구한다.
    • 프로세스사 할당받은 자원을 강제로 빼앗을 수 없다.

단기 락 구현

  • 여러 프로세서가 락 사용하기 위한 test and set 이라는 명령어를 제공한다. 어떤 메모리 위치에 들어 있는 값을 1로 설정하고, 원래 그 위치에 들어 있던 값을 돌려준다. 처음 메모리에는 0이 들어있어야 한다.
  • 또다른 방법으로 동시에 경합하는 프로세스가 아무 많을때 사용하는 compare and swap 이 있다. 명령어를 호출하는 쪽에서 예전값과 새 값을 모두 제공한다. 예전 값이 메모리 위치에 들어 있는 현잭밧과 일치하면 메모리 값을 새로운 값으로 바꾸고 프로세스는 락을 얻는다.

장기 락 구현

  • 여태까지 짧은 시간 동안 소유할 락에 대해 이야기 했지만, 락을 오래 소유하고 싶은 경우가 있다. 이런 상황은 보톤 여러 프로그램이 자원에 접근하면 안되는 경우에 해당한다. 예를 들어 여러 사용자가 같은 문서를 동시에 변경하는 것을 허용하지 않도록 설계된 워드 프로세서가 있다. 

브라우저 자바스크립트

  • 자바스크립트는 단일 스레드로 돌아가는데, 동시성의 문제가 발생할 수 있다. 
  • 자바 스크립트의 본래 목적은 더 빠르게 사용자 피드백을 제공하고 인터넷 트레픽을 줄일 목적으로 나왔다. 자바 스크립트가 없었을때는 은행계좌를 입력한 데이터를 서버에 전송하고 데이터가 숫자로만 이루어졌는지 확인하고, 오류가 없을때 처리해주는 방식을 사용했다. 서버까지 요청해서 처리해야 하기 때문에 처리과정이 오래걸렸다.
  • 웹 페이지의 복잡도가 증가하게 되면서 자바 스크립트가 주류 프로그래밍언어가 되었고, 자바 스크립트는 비동기 통신에 잘 맞지 않았다.

  • 자바 스크립트는 이벤트 루프를 사용한다. 실행할 작업을 queue 에 담아 한번에 하나씩 꺼내서 실행한다. 이때 서버에 전송된 데이터는 리스너 쓰레드에 의해 응답 받게되는데, 응답 속도가 앞에 발생한 이벤트보다 뒤에 발생한 이벤트가 느려지면 쓰레드 처리 순서가 꼬이게 된다. 

비동기 함수와 프로미스

  • Node.js 세계에서는 나쁜 라이브러리가 계속 생겨나고 있다. 콜백을 제개로 구현하지 않은 라이블리를 사용하는 프로그램은 아주 디버깅하기 힘들다. 단순히 함수와 라이브러리를 서로 조합하는 과정인 것처럼 배우기 때문에, 이런 경우가 더 문제가 된다.
  • 자바 스크립트는 프로미스라는 구성요소를 추가해 이런 문제를 처리하기 시작했다. 1970년대 생겨난 이 개념은 비동기 콜백 메커니즘을 언어 고유 기능으로 넣어서 라이브러리가 잘못 비동기 연산을 구현하지 못하게 한다. 
  • 프로미스를 사용하면 자바스크립트는 단일 스레드이기 때문에, 프로미스 체이닝을 할수 있다. 체이닝을 사용하면 someting().then().then().then()... 등 연쇄적인 호출이 가능하다.

 

[참고 문헌]

 

  1. 한 권으로 읽는 캄퓨터 구조와 프로그래밍
반응형