1227 - 0102

1227

칸반 보드 사전과제 회고

폴더 구성

이벤트 리스너를 등록하여 컨트롤러 역할을 하는 app.js,
이슈 리스트와 이슈 번호를 발급하기 위한 idNumber를 상태로 저장하고 상태가 변경될 때 이슈 리스트 렌더 함수를 호출하는 store.js,
화면 뷰와 관련된 함수를 모아둔 view.js,
그 외 유틸 함수와 상수를 관리하는 utils 폴더로 구성하였다.

이 중 idNumber의 경우 일반적으로 데이터베이스가 기본키를 지정해주기에 필요없지만, 지금 상황에서는 중복되지 않는 키를 발급해줘야 하고 일관성을 유지하며 중복되지 않는 키를 발급해주기 위해 상태로 관리하였다.

Drag & Drop

구현 방식

  • 깃허브 칸반 보드의 드래그 앤 드롭이 어떤 방식으로 동작하는지 직접 테스트해보고 결정했다.
  • 일반적으로 드롭 존과 함께 구현하는 경우가 대부분이었는데, 마우스 커서 위치에 따라 이슈 카드가 실제로 들어갔을 때의 뷰를 미리 보여주는 것이 사용자 경험 상 더 좋을 것 같다고 판단했다.
  1. 드래그를 시작할 때 해당 이슈에 dragging 클래스를 추가하고, drag 구현 로직에 필요한 정보(노드 객체, 보드 타입, 인덱스)를 객체에 저장하였다.
  2. 드롭 가능한 지역을 이슈 리스트가 들어갈 수 있는 전역으로 선택한 뒤, 드래그 중인 이슈를 다른 이슈 위에 올리게 되면 해당 요소의 정보를 가져온다.
    • drag 이슈 정보와 drop 이슈 정보를 비교하여 drop요소가 falsy한 값인 경우 리스트의 가장 뒤에, 그렇지 않다면 dragover 중인 보드에서 drop 이슈 인덱스와 drag 이슈 인덱스를 비교하여 drag 인덱스가 작다면 drop 이슈 앞에, 크다면 drop 이슈 뒤에 요소를 삽입하였다.
    • dragover 이벤트는 연속적으로 발생하기에 같은 이슈를 dragover하는 경우에는 return하여 리플로우를 최소화하였다.
    • 이슈를 드롭할 보드의 이슈 리스트가 드래그한 이슈를 포함하고 있으면서, dropIndex가 -1인 경우 즉, 이슈를 dragover 하지 않는 경우에도 return을 하면 정확히 한 번만 이슈가 삽입될 수 있었다.
  3. 드롭을 실행하게 되면 렌더링 한 정보와 동일하게 상태를 업데이트하고, 스토리지에 반영하였다.
  4. drag 이벤트가 종료되면 dragging 클래스를 제거하였다.

dragInfo 선언 이유

  • 위의 로직을 구현하기 위해서는 drag한 요소의 정보가 필요했다.
  • dataTransfet.setData는 dragover 이벤트에서 정보를 받아올 수 없었기에 드래그 정보를 저장할 변수가 필요하다고 생각했다.

draggable 속성

이미지나 링크 그리고 선택된 텍스트를 제외한 나머지 요소는 기본적으로 드래그되지 않는다. 드래그가 가능하도록 만들고자 하는 요소에 대한 draggable 속성이 true로 설정되어야 한다.

preventDefault를 사용한 이유

drop 이벤트를 사용하기 위해서는 dragover에서 preventDefault를 호출해줘야만 했다.
-> 기본 작업이 드롭하지 않는 것이기 때문!
드래그 또는 드롭 이벤트로 인해 브라우저 디폴트 이벤트가 발생할 경우 이에 대한 이벤트를 막을 수 있도록 하기 위해 사용했다.

기타

insertAdjacentHTML 메서드를 사용한 이유

  • insertAdjacentHTML 메서드는 기존 요소에 영향을 주지 않고 새로 삽입될 요소만을 파싱하여 자식 요소를 추가하므로 모든 자식 노드를 제거하고 다시 다시 새롭게 자식 노드를 생성하여 자식 요소로 추가하는 innerHTML보다 효율적이고 빠르다고 생각해서 사용했다.
  • toDo 이슈를 추가하는 경우 무조건 toDo 보드의 최상단에 삽입되기 때문에 사용가능하다고 판단했다.

상수를 사용한 이유

  • 고정되어 있어야 할 값을 변수에 저장해서 사용하다 보면 실수로 값을 바꿀 가능성이 있는데, 이를 방지하고 코드의 의도를 명확하게 하기 위해 사용하였다.
  • 에러메시지나 확인메시지의 경우, 추후 에러 메시지 확장을 고려하여 Object.freeze() 메서드를 사용해 불변 객체로 상수화하였다.

상태 관리

  • 상태 관리는 최소한으로 이슈 리스트와 이슈 리스트를 발급하기 위한 숫자값만 저장했다.
  • 즉, 스토리지에 저장하기 위한 정보들만 상태로 관리했다.

웹팩 설정

웹팩을 사용한 이유

가장 큰 이유는 웹팩이 기본 제공하는 모듈 기능과 dev server를 사용하기 위해 사용했다.

웹팩 dev-server

웹 애플리케이션을 개발하는 과정에서 유용하게 쓰이는 도구로, 웹팩의 빌드 대상 파일이 변경되었을 때 명령어를 실행하지 않고도 코드만 변경하고 저장하면 빌드한 후 브라우저를 새로고침 해준다.

  • 데브 서버로 빌드한 결과물은 메모리에 저장되고, 파일로 생성하지는 않기 때문에 더 빠르고 자원 소모가 줄어든다.

--node-env

npm에서 환경 변수를 CLI로 제공할 수 있는 명령어

webpack.config.js가 아닌 파일을 나눠 구성한 이유

  • 실행모드에 따라 조건문으로 설정을 구분할 수 있으나 파일을 아예 나눠 놓는 것이 더 좋다고 판단했다.
  • 웹팩 설정 파일을 하나로 병합해주는 Webpack Merge를 사용해 코드 중복을 제거했다.

path.resolve(__root, 'dist')

  • 폴더와 파일의 경로를 지정해주는 코드
  • 결과를 dist 폴더에 출력하겠다는 뜻

dev tool

  • 소스맵 생성 여부와 방법을 제어
  • 소스맵은 빌드 파일이 빌드 이전 파일의 소스 코드 몇 번째 줄과 연결되어 있는지 매핑 정보를 담은 파일

css-loader

css-loader에 의해 css가 해석되어 JS 파일에서 import 하여 css 파일을 읽어들일 수 있다.

style loader

style-loader에 의해 html head에 스타일을 주입받는다.

MiniCssExtractPlugin

css 파일을 추출해주는 플러그인이다.

부족한 점

  • 우선 기본 설정을 하고 시간이 남는다면 빌드 모드와 개발 모드를 철저히 분리하여 설정하고자 했다.
  • 하지만 생각보다 시간이 부족하여 웹팩 설정을 완벽하게 하지 못한 것 같다.
  • 하지만 이대로도 큰 문제는 없다고 판단했다.

아쉬운 점

index.html을 제출하지 못한 이유

온전히 나의 부주의로 인해 발생한 일이며, 이에 대해 핑계를 대기보다는 이번 실수를 통해 예상치 못한 문제가 있을 수 있음을 언제나 예상하고 대비해야겠다는 생각을 했다.

이전까지 프로젝트를 진행할 때는 HtmlWebpackPlugin을 사용했기에 빌드 파일은 명령어를 입력하면 다시 생성될 것이라고 생각했다.
모든 파일 확인을 마친 뒤, 마지막에 dist 파일(빌드 파일)만 지우고 제출하여 이러한 상황이 발생하게 되었다.

Storage

이슈 리스트 상태를 업데이트할 때 idNumber의 경우 굳이 스토리지에 다시 저장하지 않아도 됨에도 불구하고 다시 저장하는 로직인 상태였다. setIdNumber를 호출할 때만 스토리지에 idNumber를 저장하는 방식으로 수정하였다.

XSS 처리

프로젝트 시작 전부터 XSS 처리를 꼭 해야겠다고 계획했었다. innerHTML을 사용해서 렌더링을 수행할 경우 검증되지 않은 값이 자바스크립트를 통해 삽입되어 문제가 발생할 수 있다고 판단하여 꼭 이 처리를 해줘야겠다고 생각했다.

하지만 기본 기능을 구현하고 리팩토링하는데에 집중하다보니 시간이 빠르게 흘러 과제 제출 시간이 얼마 남지 않았고, 급하게 진행하다보니 테스트를 완전히 하지 못했다. 악성 스크립트를 막는다는 목적은 이루었지만, 추후에 기능이 완전하지 않다는 사실을 알게되었다.
filterXSS 메소드를 사용했을 때 의도와 다르게 렌더링되어 의문점이 있었는데, 그 이유는 filterXSS 메소드 로직 내부에 & 기호를 이스케이프 문자로 바꾸어 주는 것을 가장 먼저 했어야 했는데 그 점을 빠트렸다는 것이었다. 비록 과제 제출 시간이 끝나고 알게 되었지만, 부족한 부분을 리팩토링하여 filterXSS 메소드를 정상적으로 구현했고, 화면에 렌더링 될 때만 해당 메소드를 적용해 악성 스크립트로부터의 삽입을 방어하도록 했다.




1228

모의 코딩테스트 11회 회고

알고리즘 공부를 한동안 하지 않다가 다시 하려니 쉽지 않게 느껴졌다.
알고리즘 문제를 풀면서 시간복잡도만 생각해왔는데, 공간복잡도 측면에서도 고민해볼 수 있는 하루였다.

k개의 원소로 이루어진 연속된 부분수열 분리

function solution(nums, k) {
  if (nums.length % k !== 0) return "NO";
  const map = new Map();

  nums.forEach((num) => map.set(num, (map.get(num) || 0) + 1));
  nums.sort((a, b) => a - b);

  for (let i = 0; i < nums.length; i++) {
    if (map.get(nums[i])) {
      for (let j = 0; j < k; j++) {
        if (map.get(nums[i] + j) > 0)
          map.set(nums[i] + j, map.get(nums[i] + j) - 1);
        else return "NO";
      }
    }
  }

  return "YES";
}
  • 우선 k개로 나누어 떨어지지 않는다면 ‘NO’를 반환한다.
  • 맵에 각 숫자의 개수를 해싱하고 정렬한다.
  • 맵에 해당 숫자가 존재한다면 해당 숫자부터 k번까지 증가시켜 해싱된 숫자를 찾고, 있다면 해당 숫자의 개수를 1감소시킨뒤 계속 반복한다.
  • 모두 돌았는데 정답이 도출되지 않는다면 YES를 반환한다.

합이 홀수인 부분수열 개수

function solution(nums) {
  let answer = 0;
  const oddArr = Array(nums.length).fill(0);
  const evenArr = Array(nums.length).fill(0);

  nums[0] % 2 === 0 ? (evenArr[0] = 1) : (oddArr[0] = 1);
  answer += oddArr[0];

  for (let i = 1; i < nums.length; i++) {
    if (nums[i] % 2 === 0) {
      oddArr[i] = oddArr[i - 1];
      evenArr[i] = evenArr[i - 1] + 1;
    } else {
      evenArr[i] = oddArr[i - 1];
      oddArr[i] = evenArr[i - 1] + 1;
    }
    answer += oddArr[i];
  }

  return answer % 1000000007;
}
  • i번째 까지의 부분수열 중 홀수와 짝수의 개수를 계산하며 구해나간다.
  • 홀수 배열에 저장된 모든 값을 더하면 값을 도출할 수 있다.

사과를 모두 먹을 수 있는 최소 일수

function solution(n) {
  const ch = new Map();
  const queue = [n];
  ch.set(n, 1);
  let L = 0;

  function BFS() {
    while (queue.length) {
      const len = queue.length;

      for (let i = 0; i < len; i++) {
        const x = queue.shift();

        if (x === 0) return L;
        if (x % 2 === 0 && !ch.has(x / 2)) {
          queue.push(x / 2);
          ch.set(x / 2, 1);
        }
        if (x % 3 === 0 && !ch.has(x / 3)) {
          queue.push(x / 3);
          ch.set(x / 3, 1);
        }
        if (!ch.has(x - 1)) {
          queue.push(x - 1);
          ch.set(x - 1, 1);
        }
      }
      L += 1;
    }
  }

  return BFS();
}
  • 가장 큰 깨달음을 얻었던 문제
  • 알고리즘 문제를 해결하다보면 시간복잡도만 주로 생각하게 됐었는데, 공간복잡도도 고려해야함을 깨닫게 되었다.
  • 쓸데없이 큰 배열을 잡는 것이 아니라 필요한 만큼만 해싱하여 문제를 해결할 수 있었다.




1229 TIL

MVC 패턴

MCV는 Model, View, Controller의 약자로 하나의 애플리케이션을 구성할 때 구성 요소를 세가지의 역할로 구분한 패턴이다.
로직이 분리가 안되있고, 한꺼번에 정의가 되어있다면 나중에 유지보수하기 힘들 것이라고 생각했다.

모델, Model

애플리케이션의 정보, 데이터를 나타낸다. 데이터베이스와 정보들의 가공을 책임지는 컴포넌트를 말한다.

  1. 사용자가 편집하길 원하는 모든 데이터를 가지고 있어야 한다.
  2. 뷰나 컨트롤러에 대해서 어떤 정보도 알지 말아야 한다.
  3. 변경이 일어나면, 변경 통지에 대한 처리방법을 구현해야만 한다.

뷰, View

  1. 모델이 가지고 있는 정보를 따로 저장해서는 안된다.
  2. 모델이나 컨트롤러와 같이 다른 구성요소들을 몰라야 된다.
  3. 변경이 일어나면 변경통지에 대한 처리방법을 구현해야만 한다.

컨트롤러, Controller

  • 데이터와 사용자인터페이스 요소들을 잇는 다리 역할을 한다.
  • 즉, 이벤트들을 처리하는 부분
  1. 모델이나 뷰에 대해 알고 있어야 한다.
  2. 모델이나 뷰의 변경을 모니터링 해야 한다.




1230 TIL

MVVM 패턴

  • Model, View, View Model로 이루어진 디자인 패턴
  • View Model은 뷰를 표현하기 위해 만든 뷰를 위한 모델이다. 뷰를 나타내 주기 위한 모델이자 뷰를 나타내기 위한 데이터 처리를 하는 부분
  • 뷰는 뷰 모델을 알지만, 뷰 모델은 뷰를 알지 못한다.
  • 뷰 모델은 모델을 알지만, 모델은 뷰 모델을 알지 못한다.

동작

  1. 사용자의 액션은 뷰를 통해 들어오게 된다.
  2. 액션이 들어오면, 커맨트 패턴으로 뷰 모델에 액션을 전달한다.
  3. 뷰 모델은 모델에게 데이터를 요청한다.
  4. 모델은 뷰 모델에게 요청받은 데이터를 응답한다.
  5. 뷰 모델은 응답 받은 데이터를 가공하여 저장한다.
  6. 뷰는 뷰 모델과 데이터 바인딩하여 화면을 나타낸다.

장점

  • 커맨트 패턴과 데이터 바인딩을 사용하여 뷰와 모델, 뷰와 뷰 모델 사이의 의존성을 없앴다.
  • 비즈니스 로직과 프레젠테이션 로직을 UI로부터 분리하여 테스트, 유지 보수, 모듈화가 쉽다.
  • 뷰와 모델을 연결하기 위해 사용해야 하는 연결 코드를 줄일 수 있다.

단점

  • 뷰 모델의 설계가 쉽지 않다.
  • 뷰가 변수와 표현식 모두에 바인딩 될 수 있어서 시간이 지남에 따라 관계없는 프레젠테이션 로직이 늘어나 유지 관리하기 번거롭다.

싱글톤 패턴

  • 인스턴스가 하나만 만들어지도록 하고, 그 인스턴스에 대한 전역 접근을 제공한다.
  • private 생성자와, 정적 메소드, 정적 변수만을 사용한다.
  • 싱글톤 패턴은 단 하나의 인스턴스만 생성해 사용하는 디자인 패턴

싱글톤 패턴 장점

  1. 두 번째 이용시부터는 객체 로딩 시간이 현저하게 줄어 성능이 좋아진다.
  2. 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스들과 데이터를 공유하기 쉽다.
  3. 인스턴스가 절대적으로 한 개라는 것을 보증한다.

싱글톤 패턴 단점

  1. 너무 많은 데이터를 공유할 경우에 인스턴스들 간의 결합도가 높아져 수정이 어려워지고 테스트하기 어려워진다.
  2. 멀티스레드 환경에서 동기화 처리를 하지 않으면 인스턴스가 두 개가 생성되는 경우가 발생한다.




1231 TIL

프로세스 vs 스레드

프로그램이란 어떤 작업을 위해 실행할 수 있는 파일

프로세스

  • 실행 중인 프로그램으로 디스크로부터 메모리에 적재되어 CPU의 할당을 받은 작업의 단위
  • 운영체제로부터 시스템 자원을 할당받는다.
    • CPU시간, 운영되기 위한 주소 공간, Code, Data, Stack, Heap의 구조로 되어있는 독립된 메모리 영역
  • 프로세스마다 최소 1개의 스레드를 갖는다.(메인 스레드)
  • 프로세스는 각각의 별도의 메모리 영역을 할당받는다.

프로세스 제어 블록 (PCB)

  • 특정 프로세스에 대한 중요한 정보를 저장하고 있는 커널 내의 자료구조
  • OS는 프로세스 생성과 동시에 고유한 PCB를 생성한다.
  • 프로세스는 CPU를 할당받아 작업을 처리하다가 프로세스 전환이 발생하면 진행하던 작업을 저장하고 CPU를 반환해야 한다. 이때 작업의 진행 상황을 모두 PCB에 저장하고, 다시 CPU를 할당받게 되면 PCB에 저장되었던 내용을 불러와 종료되었던 시점부터 다시 작업을 수행한다.
  • PCB에 저장되는 정보
    • 프로세스 식별자(Process ID, PID) : 프로세스 식별 번호
    • 프로세스 상태 : new, ready, running, waiting, terminated 등의 상태를 저장
    • 프로그램 카운터(Program Counter, PC) : 프로세스가 다음에 실행할 명령어의 주소를 가리킨다.
    • CPU 레지스터
    • CPU 스케줄링 정보 : 프로세스의 우선순위, 스케줄 큐에 대한 포인터 등
    • 메모리 관리 정보 : 페이지 테이블 또는 세그먼트 테이블 등과 같은 정보를 포함한다.
    • 입출력 상태 정보 : 프로세스에 할당된 입출력 장치들과 열린 파일 목록
    • 어카운팅 정보 : 사용된 CPU 시간, 시간 제한, 계정 번호 등

스레드

프로세스의 실행 단위라고 할 수 있으며, 한 프로세스 내에서 동작되는 여러 실행 흐름으로 프로세스 내의 주소 공간이나 자원을 공유할 수 있다.

  • 스레드는 프로세스 내의 Code, Data, Heap 영역은 다른 스레드와 공유하고 Stack 영역을 따로 할당받는다.
  • 여러 스레드는 한 프로세스 내의 Code, Data, Heap 영역을 공유하지만 프로세스 간에는 메모리에 접근할 수 없다.
  • 스레드는 별도의 레지스터와 스택을 갖고 있으며, 다른 영역을 공유한다. 따라서 한 스레드가 프로세스의 자원을 변경하면, 다른 스레드도 그 변경 결과를 즉시 확인할 수 있다.

[요약]
프로세스 : 자신만의 고유 공간과 자원을 할당받아 사용하는 작업의 단위.
스레드 : 프로세스 내에서 실행되는 흐름의 단위로, 다른 스레드와 프로세스의 자원과 공간을 공유하면서 사용.

멀티 프로세스 vs 멀티 스레드

멀티 프로세스

하나의 응용프로그램을 여러 개의 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하도록 하는 것

장점

자식 프로세스 중 하나에 문제가 발생하면 그 자식 프로세스만 죽는 것 이상으로 영향이 확산되지 않아 안전하다.

단점

  • Context Switching에서의 오버헤드
    • 프로세스는 각 독립된 메모리 영역을 할당받았기 때문에 공유하는 메모리가 없다. 따라서 캐시 메모리 초기화 등의 무거운 작업이 진행되고 많은 시간이 소모되는 등의 오버헤드가 발생할 문제가 있다.
  • 프로세스간 통신 기법 IPC
    • 프로세스는 각 독립된 메모리 영역을 할당받았기 때문에 프로세스들 사이에서 변수나 자료구조를 공유할 수 없다.
    • IPC라는 방법을 사용해야 하는데, 이는 어렵고 복잡한 통신 방법이다.

멀티 스레드

하나의 응용 프로그램을 여러 개의 스레드로 구성하고 각 스레드가 하나의 작업을 처리하도록 하는 것

장점

  • 메모리 공간과 시스템 자원 소모가 줄어들게 된다.
  • 스레드 간 통신 시, 전역 변수의 공간 또는 동적으로 할당된 공간인 Heap 영역을 이용해 데이터를 주고 받으므로 통신 방법이 간단하다.
  • Context Switching 시, 캐시 메모리를 비울 필요가 없기 때문에 비용이 적고 더 빠르다.
  • 시스템 처리량이 향상되고 자원 소모가 줄어들며, 자연스럽게 프로그램의 응답 시간이 단축된다.

단점

  • 서로 다른 스레드가 Data, Heap 영역 등을 공유하기 때문에 어떤 스레드가 다른 스레드에서 사용중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있다. 즉, 자원 공유의 문제가 발생한다.
  • 하나의 스레드에 문제가 생기면 전체 프로세스가 영향을 받기 때문에 주의 깊은 설계가 필요하다.

결론

  • 멀티 스레드는 멀티 프로세스보다 적은 메모리 공간을 차지하고 Context Switching이 빠르다는 장점이 있지만, 오류로 인해 하나의 스레드가 종료되면 전체 스레드가 종료될 수 있다는 점과 동기화 문제를 가지고 있다.
  • 반면, 멀티 프로세싱은 하나의 프로세스가 죽더라도 다른 프로세스에 영향을 주지 않는다는 장점이 있지만, 멀티 스레드보다 많은 메모리 공간과 CPU 시간을 차지한다는 단점이 있다.

자주 나오는 질문

스택을 스레드마다 독립적으로 할당하는 이유

스택은 함수 호출 시 전달되는 인자, 복귀 주소값 및 함수 내에서 선언하는 변수 등을 저장하기 위해 사용되는 메모리 공간
스택 메모리 공간이 독립적이라는 것은 독립적인 함수 호출이 가능함을 의미하고 이는 독립적인 실행 흐름이 추가된다는 것이다.
따라서 스레드의 정의에 따라 독립적인 실행 흐름을 추가하기 위한 최소 조건으로 독립된 스택을 할당하는 것

PC 레지스터를 스레드마다 독립적으로 할당하는 이유

PC 값은 스레드가 명령어의 어디까지 수행했는지를 나타내게 된다. 스레드는 CPU를 할당받았다가 스케줄러에 의해 다시 선점당한다. 그렇기 때문에 명령어가 연속적으로 수행되지 못하고 어느 부분까지 수행했는지 기억할 필요가 있다. 따라서 PC 레지스터를 독립적으로 할당한다.

멀티 프로세스 대신 멀티 스레드를 사용하는 이유

  • 프로그램을 여러 개 키는 것보다 하나의 프로그램 안에서 여러 작업을 해결하는 것이 더욱 효율적이다.
  • 프로세스를 생성하여 자원을 할당하는 시스템 콜이 줄어들어 자원을 효율적으로 관리할 수 있다.
  • Context Switching 시, 캐시 메모리를 비울 필요가 없기 때문에 비용이 적고 더 빠르다.
    • 스레드는 Stack 영역만 초기화하면 되기 때문
  • 스레드는 프로세스 내의 메모리를 공유하기 때문에 데이터 전달이 간단하므로 IPC에 비해 비용이 적고 더 빠르다.




0101 TIL

Context Switching

  • CPU는 한 번에 하나의 프로세스만 처리할 수 있다.
  • 여러 프로세스를 처리해야 하는 상황에서 현재 진행중인 작업의 상태를 PCB에 저장하고 다음에 진행할 작업의 상태값을 읽어 적용하는 과정
  • 태스크의 대부분 정보는 레지스터에 저장되고 PCB로 관리된다. 현재 실행하고 있는 태스크의 PCB 정보를 저장하고, 다음 실행할 태스크의 PCB 정보를 읽어 레지스터에 적재하고 CPU가 이전에 진행했던 과정을 연속적으로 수행할 수 있다.
  • 이는 많은 비용이 소모되는데, 캐시와 메모리 매핑을 초기화해야 한다.
  • Context Switching 비용은 프로세스가 스레드보다 더 많이 든다. 이유는 스레드는 스택 영역을 제외한 모든 메모리를 공유하기 때문에 스택 영역만 변경을 진행하면 되기 때문이다.

Thread-safe

멀티 스레드 환경에서 여러 스레드가 동시에 하나의 공유 자원에 접근할 때, 의도한 대로 동작하는 것을 Thread-safe하다 라고 표현한다.

구현 방법

  • 공유 자원에 접근하는 임계영역(critical section)을 동기화 기법으로 제어해줘야 한다. 이를 ‘상호배제’라고 한다.
  • 동기화 기법으로는 뮤텍스나 세마포어가 존재한다.
  • 어떤 함수가 Reentrant 하다는 것은 여러 스레드가 동시에 접근해도 언제나 같은 실행 결과를 보장한다는 의미이다.
    • 이를 만족하기 위해서 해당 서브루틴에서는 공유자원을 사용하지 않으면 된다.
    • 즉, 전역 변수를 사용하거나 반환하면 안되고 호출 시 제공된 매개변수만으로 동작해야 한다.
  • Reentrant하면 Thread-safe하지만 그 역은 성립하지 않는다.

동기와 비동기

동기 (동시에 일어나는)

동시에 라는 말은 실행되었을 때 값이 반환되기 전까지는 블로킹되어 있다는 것을 의미한다.

  • 동시에 일어난다는 뜻으로 요청과 그 결과가 동시에 일어난다는 약속
  • 바로 요청을 하면 시간이 얼마가 걸리던지 요청한 자리에서 결과가 주어져야 한다.
  • 요청과 결과가 한 자리에서 동시에 일어난다.

비동기 (동시에 일어나지 않는)

  • 요청과 결과가 동시에 일어나지 않은 것이기에 그 자리에서 결과가 주어지지 않는다.

비교

  • 동기 방식은 설계가 매우 간단하고 직관적이지만 결과가 주어질 때까지 아무것도 못하고 대기해야한다는 단점이 있다.
  • 비동기 방식은 동기보다 복잡하지만, 결과가 주어지는데 시간이 걸리더라도 그 시간동안 다른 작업을 할 수 있으므로 자원을 효율적으로 사용할 수 있다는 장점이 있다.

동기화 문제

동기화는 한정적인 자원에 여러 스레드가 동시에 접근할 경우 문제가 발생할 수 있는데, 이 문제를 방지하기 위해 여러 스레드에게 하나의 자원에 대한 처리 권한을 주거나 순서를 조정하는 기법이다.

스레드 동기화

  1. 실행 순서의 동기화 - 스레드의 실행 순서를 정의하고, 이 순서를 따르도록 함
  2. 메모리 접근에 대한 동기화
    • 메모리 접근에 있어서 동시 접근을 막는 것
    • 실행의 순서가 중요한 것이 아니라 한 순간에 하나의 스레드만 해당 자원에 접근하도록 하는 것

유저 모드의 동기화

  1. 임계 구역 기반의 동기화
    • 열쇠를 얻은 프로세스만 임계 구역에 들어갈 수 있다.
    • 다른 스레드가 열쇠를 가지고 있을 시에는 반환할 때까지 블로킹된다.
  2. 인터락 함수 기반의 동기화
    • 함수 내부적으로 한 순간에 하나의 스레드에 의해서만 실행되도록 동기화된다.
    • 임계 구역 기반의 동기화도 내부적으로 인터락 함수를 기반으로 구현된다.
    • 유저 모드 기반으로 동작해서 속도가 빠르다.

커널 모드의 동기화

  1. 세마포어
    • 공유된 자원의 데이터를 여러 프로세스, 스레드가 접근하는 것을 막는 것이다.
    • 동시에 접근할 수 있는 개수를 가지고 있는 Counter
    • 예를 들어 세마포어는 1개 이상의 열쇠라고 할 수 있다.
    • 세마포어가 한 개인 경우는 뮤텍스와 같다.
    • 세마포어는 소유할 수 없기에 세마포어를 소유하지 않은 스레드가 세마포어를 해제할 수 있는 문제가 발생한다.
  2. 뮤텍스
    • 임계 구역을 가진 스레드들의 Running time이 서로 겹치지 않게 각각 단독으로 실행되게 하는 기술
    • 뮤텍스 객체를 두 스레드가 동시에 사용할 수 없다.
    • Lock에 대한 소유권이 있으며 Lock을 가지고 있을 경우에만 공유자원에 접근할 수 있고, 반납할 수 있다.
    • 뮤텍스는 무조건 1개의 열쇠만 가질 수 있다.
  3. 모니터
    • 뮤텍스와 모니터는 상호 배제를 함으로써 임계 구역에 하나의 스레드만 들어갈 수 있다.
    • 반면, 세마포어는 하나의 스레드만 들어가거나 혹은 여러개의 스레드가 들어가게 할 수 있다.

요약

뮤텍스 : 한 스레드, 프로세스에 의해 소유될 수 있는 키를 기반으로 한 상호배제 기법
세마포어 : 현재 공유자원에 접근할 수 있는 스레드, 프로세스의 수를 나타내는 값을 두어 상호배제를 달성하는 기법

-> 뮤텍스와 세마포어의 목적은 특정 동기화 대상이 이미 특정 스레드나 프로세스에 의해 사용중일 경우, 다른 스레드가 해당 동기화 대상에 접근하는 것의 제한하는 것으로 동일하지만, 관리하는 동기화 대상이 몇 개인가에 따라 차이가 생긴다. 뮤텍스는 소유할 수 있어서 뮤텍스를 소유하고 있는 스레드가 이 뮤텍스를 해제할 수 있다. 반면 세마포어는 소유하지 않고 있는 다른 스레드가 세마포어를 해제할 수 있다. 두 기법 모두 완벽하지는 않아 데드락이 발생할 수도 있다.

태그:

카테고리:

업데이트: