기억의 실마리
2023. 6. 28. 15:53

이진탐색 (Binary Search)

정렬이 되어있는 리스트에서 탐색 범위를 반씩 좁혀가며 데이터를 탐색하는 것이다.

각 단계마다 탐색 범위를 2로 나누어 탐색하기 때문에 시간복잡도는 O(log N)이다.

때문에 이진탐색은 매우 넓은 탐색범위(억 단위 이상)에서 최적의 해를 찾아야 하는 경우

데이터를 정려한 후 다수의 쿼리를 날려야 하는 경우에 많이 사용할 수 있다.

 

이진탐색 동작 방식

아래 리스트 표에서 값이 15인 위치를 찾고자 한다고 가정해보자.

0 5 6 7 8 9 15 16 19 20
  1. 정렬이 이미 되어 있다는 것을 가정하고 이를 통해서 [start, mid, end] 3포인트를 지정한다.
    mid는 배열의 크기를 2로 나누었을때 소수점을 버린 값이다. (10 = 5, 9 = 4)
    {start: 0, mid: 8, end: 20}

  2. 이진탐색의 방식은 결과적으로 mid가 탐색값이 되면 탐색을 멈추고 mid를 반환하는 방식이다.
    탐색값과 mid를 비교해서 탐색값이 더 크다면 start = mid를 해주어 탐색 범위를 좁히고
    변경된 start를 기준으로 mid = parseInt((start + end) / 2) 를 작성해서 mid위치를 재지정해준다.
    반대로 탐색값이 더 작다면 end = mid, mid = parseInt((start + end) / 2)를 작성하여
    end위치와 mid위치를 재지정해주면 된다.

 

마치며...

이진 탐색코드 같은 경우엔 코딩테스트에서도 분명 이점이 있지만 프로젝트를 진행할 때 특정 값을 정렬된 데이터들 내에서 탐색하는 방법을 사용할때 많이 이용하게 될 것 같다.

2023. 6. 27. 15:15

탐욕알고리즘 (Greedy Algorithm)

현재 상황에서 가장 좋아보이는 상황만을 선택하는 알고리즘이다.

흔히 그리디, 탐욕알고리즘이라고 부리기도 하며 최적의 해를 구하기위한

방법으로 사용될 때가 많다.

 

탐욕 알고리즘의 접근방법

1. 현재 상황에서 어떤 것을 선택할지 방법을 고안한다.

2. 자신이 고안한 알고리즘이 항상 최적의 해를 보장하는지 정당성을 확인해야한다.

 

탐욕 알고리즘 문제

전형적인 탐욕 알고리즘을 이용해 풀 수 있느 문제로 "거스름 돈" 문제가 있다.

거스름 돈 문제를 통해서 그리디알고리즘에 대해서 이해해보자.

문제는 아래와 같은 조건과 답을 내어야 한다.

  • 카운터에 500원 100원 50원 10원이 무수히 많다고 가정한다.
  • 손님에게 11,480원을 거슬러 주어야 할때, 동전 개수의 최소값은?

결론부터 말하자면 이 문제의 해결방법은 가장 큰 화폐단위부터 거슬러 주는 것이다.

즉 500원부터 10원까지 거슬러 줄 수 있는 만큼 거술러주면 되는 것이다.

화폐단위 500원 100원 50원 10원
현재 금액 11,480 480 80 30
남은 금액 480 80 30 0
동전 개수 22 22 + 4 22 + 4 + 1 22 + 4 + 1 + 3

동전 개수의 최소값은 30이다.

 

큰 화폐부터동전의 개수를 구하며 내려왔을때 최적의 해를 구할 수 있는 이유는

각 화폐단위가 배수 관계에 속하기 때문에 그렇다.

 

예를 들어 640원을 거슬러 주어야한다고 하고 220원, 200원, 10원 동전이 있다고 가정하면

220원 2개, 200원 1개로 총 3개가 필요할 것이다. 이 또한 배수관계에 속하기 때문에

가장 큰 수의 동전부터 나눌 수 있는 만큼 나누고 점차 작은 수의 동전으로 나누어주는

같은 방식의 탐욕알고리즘을 사용하여 문제를 해결할 수 있다.

 

마치며...

탐욕알고리즘에 대해서는 간단하게 포스팅을 해보았다. 실질적으로 탐욕알고리즘은 코딩테스트를 볼 때 중요했던 개념이었다. 나는 탐욕알고리즘을 파악할때 예시로 주어지는 입력값과 출력값(정답)을 보며 연관성을 최대한 찾아보고 답을 만들기 위한 코드설계와의 연관성과 규칙성이 있는지를 확인하면서 코드를 작성할때 어떠한 연관성이 있는지를 파악하면서 실마리를 찾아냈던 것 같다. 개인적으로 익숙해지기 어려운 접근방식이었던 기억이 있다...

2023. 6. 26. 20:48

1. 선택정렬(Selection Sort)

단계별로 정렬을 수행할때 가장 작은 원소를 선택해서 앞으로 보내는 정렬 방법이다.

앞으로 보내진 원소는 위치가 변경되지 않고 시간복잡도 O(N²)으로 비효율적인 알고리즘에 속한다.

 

동작방식

1. 단계별로 수행하면서 가장 작은 원소를 선택한다.

2. 처리되지 않은 원소중에 가장 앞의 원소와 교체한다.

 

  • 선택정렬 함수 예시
function selectionSort(arr) {
  for (let i = 0; i < arr.length; i++) {
    let minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[minIndex] > arr[j]) {
        minIndex = j;
      }
    }
    // 스왑(swap)
    let temp = arr[i];
    arr[i] = arr[minIndex];
    arr[minIndex] = temp;
  }
}

 

 

2. 버블정렬(Bubble Sort)

인접한 두 원소를 체크하여 정렬이 안되어 있다면 위치를 서로 변경하는 정렬방식이다.

서로 인접한 두 원소를 비교하는 것이 마치 거품과 같다고 하여 붙여진 이름이다.

시간복잡도 O(N²)으로 비효율적인 정렬 알고리즘에 속한다.

 

동작방식

1. 단계별로 수행하면서 가장 큰 원소가 맨 뒤로 이동한다.

2. 다음단계에선 맨 뒤로 이동한 원소는 정렬에서 제외한다.

 

  • 버블정렬 함수 예시
function bubbleSort(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    for (let j = 0; j < i; j++) {
      if (arr[j] < arr[j + 1]) {
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
  }
}

 

 

3. 삽입정렬(Insertion Sort)

각 원소를 적절한 위치에 삽입하는 정렬이다.

 

동작방식

1. 단계별로 수행하면서 현재 원소가 삽입될 위치를 탐색한다.

2. 지정 위치에 도달할 때까지 반복적으로 왼쪽으로 이동한다.

 

  • 삽입정렬 함수 예시
function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    for (let j = i; j > 0; j--) {
      // 시작인덱스부터 1까지 1씩 감소하며 반복
      if (arr[j] < arr[j - 1]) {
        let temp = arr[j];
        arr[j] = arr[j - 1]; // 한 칸씩 왼쪽으로 이동
        arr[j - 1] = temp; // 스왑(swap)
      } else {
        break; // 자기보다 작은 데이터를 만나면 그 위치에서 멈춤
      }
    }
  }
}

 

 

4. 병합정렬(Merge Sort)

병합정렬은 분할정복(divide and conquer) 알고리즘이다.

 

분할정복이란?

  • 분할(divide): 큰 문제를 작은 부분 문제(쉬운 문제)로 분할한다.
  • 정복(conquer): 작은 부분 문제를 각각해결하도록 한다.
  • 조합(combine): 해결한 작은 부분의 답을 이용하여 큰 문제를 해결해나간다.

일반적으로 재귀함수를 사용하여 구현한다.

큰 문제를 작은 문제로 분할하는 방식이 같은 경우가 많기 때문이다.

그렇기 때문에 쪼개질 수 없는 크기가 될 때까지 계속해서 분할한다.

 

장점: 최악의 경우에도 O(N log N)을 보장할 수 있다는 점에서 효율적이다.

단점: 전반적으로 정복과정에서 임시배열이 필요한 경우가 지배적이다.

 

동작방식

1. 단계별로 수행하면서 현재 원소가 삽입될 위치를 탐색한다.

2. 지정 위치에 도달할 때까지 반복적으로 왼쪽으로 이동한다.

 

  • 병합정렬 함수 예시
function merge(left, right) {
  let sorted = [];
  while (left.length && right.length) {
    if (left[0] <= right[0]) sorted.push(left.shift());
    else sorted.push(right.shift());
  }
  return [...sorted, ...left, ...right];
}

function mergeSort(arr) {
  if (arr.length === 1) return arr;
  let half = Math.ceil(arr.length / 2);
  let left = arr.slice(0, half);
  let right = arr.slice(half);
  return merge(mergeSort(left), mergeSort(right));
}

let arr = [7, 4, 3, 2, 1, 6, 5];
const sortedArray = mergeSort(arr);

console.log(arr); //[7, 4, 3, 2, 1, 6, 5]
console.log(sortedArray); //[1, 2, 3, 4, 5, 6, 7]

function merge: 이미정렬된 배열 left와 right를 하나의 함수로 합쳐주는 순수 함수

function mergeSort: 배열을 반으로 나누어 merge함수에게 left와 right로 전달한다.

 

 

마치며...

오늘 포스팅한 정렬알고리즘은 사실, 알고리즘을 따로 공부를하기전까진 단순하게 정렬하는거지! 라고 생각했지만 정말 다양하고 어떤방식으로 정렬을 하느냐에 따라 시간복잡도가 제곱이상의 차이가 나기도 한다는 것이 새롭게 다가왔던 것 같다. 앞으로도 코드의 퀄리티를 위해서 프로젝트에서 정렬할때 잘 생각해보고 적용시켜야겠다.

2023. 6. 26. 13:44

1. 배열(Array)

가장 기본적인 자료구조이며 여러개의 변수를 담는 공간으로 이해할 수 있다.

배열은 인덱스(Index)가 존재하고 인덱스는 0부터 시작한다.

특정한 인덱스에 직접적으로 접근이 가능하고 수행시간은 O(1)을 가진다.

 

장점: 캐시(cache) 히트 적중률이 높고 조회가 빠르다.

단점: 배열의 크기를 미리 지정해야 하는 것이 일반적이며 데이터의 추가 및 삭제에 한계가 있다.

 

캐시히트 적중률이란?

CPU(클라이언트)에서 요청한 데이터가 캐시에 존재하여 이를 이행할 수 있을때 캐시 히트(Hit)라고 한다. 

Memory address 0x0000 0x0004 0x0008
Index 0 1 2
Value 10 30 70

 

2. 연결리스트(Linked List)

연결 리스트는 메인 메모리상에서 주소가 연속적이지 않으며 배열과 다르게

크기가 정해져있지 않고 동적으로 변경이 가능한 것이 특징이다.

 

장점: 포인터가 다음 데이터의 위치를 가지고 있기 때문에 삽입과 삭제가 간편하다.

단점: 특정 인덱스의 원소를 검색할때 앞에서부터 원소를 찾기 때문에 검색속도가 느리다.

3. 스택(Stack)

먼저 들어온 데이터가 나중에 추출되는 자료구조이며 박스가 쌓인 형태를 스택이라고 볼 수 있다.

새로운 원소를 삽입할때는 마지막위치에 삽입하고 삭제할때 마찬가지로 마지막 위치에서 삭제된다.

 

 

4. 큐(Queue)

큐는 먼저 삽입된 데이터가 먼저 추출되는 자료구조이다.

예시로 게임 대기 큐의 경우 먼저 대기한 사람이 먼저 게임에 매칭되는 것과 동일하다.

Javascript Queue

Javascript에서는 큐 라이브러리를 기본적으로 제공하고있지 않기 때문에

간단하게 Queue 라이브러리를 직접 작성하여 사용할 수 있다.

 

필자 역시 Javascript를 주로 사용하기 때문에 Queue라이브러리를 만들어 두었다.

class Queue {
  constructor() {
    this.items = {};
    this.headId = null;
    this.tailId = null;
    this.length = 0;
  }
  size() { return this.length; }
  isEmpty() { return this.length === 0; }
  enq(item) {
    if (!this.headId) this.headId = 0;
    if (this.headId == 0) {
      this.items[this.length] = item;
      this.length++;
      this.tailId = this.length - 1;
    }
    else  {
      this.tailId++;
      this.items[this.tailId] = item;
      this.length++;
    }
  }
  deq() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    if (this.length == 0) return
    this.length--;
    const item = this.items[this.headId];
    delete this.items[this.headId];
    this.headId++;
    if (this.length == 1) this.tailId = this.headId;
    if (this.length == 0) {
      this.headId = null;
      this.tailId = null;
    }
    return item;
  }
  head() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    return this.items[this.headId];
  }
  tail() {
    if (this.isEmpty()) throw new Error('Queue is empty');
    return this.items[this.tailId - 1];
  }
}

혹시나 블로그를 방문하시는분들께서 javasciprt Queue가 필요하셨다면 사용하셔도 무방합니다.

 

5. 트리(Tree)

트리는 계층적인 구조를 표현할 때 사용할 수 있는 자료구조다.

나무를 뒤집은 것 같은 형태이다.

  • 루트 노드(root node): 부모가 없는 최상위 노드
  • 단말 노드(leaf node): 자식이 없는 노드

서로 같은 부모를 가지는 노드간의 관계를 형제관계라고 하며 루트노드에서의 길이를

깊이(depth)라고 한다. 이는 즉 출발노드에서 목적지 노드까지 거쳐야 하는 간선의 수를 의미한다.

트리의 높이(height)는 루트 노드에서 가장 깊은 노드까지의 길이를 의미한다.

 

트리의 종류

  • 이진 트리(Binary Tree): 최대 2개의 자식을 가질 수 있는 구조의 트리를 말한다.

 

  • 포화 이진 트리(Full Binary Tree): 리프노드를 제외한 모든 노드가 두 자식을 가지고 있는 트리다.

 

  • 완전 이진 트리(Complete Binary Tree): 모든 노드가 왼쪽 자식부터 차례로 채워진 트리다.

 

  • 높이 균형 트리(Height Balanced Tree): 왼쪽과 오른쪽 자식 트리의 높이가 1이상 차이가 나지 않는 트리다.

 

6. 힙(Heap)

힙은 원소중 최댓값 또는 최솟값을 빠르게 찾아내는 자료구조이다.

원소 삽입과 삭제할때 O(logN)의 수행시간을 요구하며 N개의 데이터를 힙에 넣었다

모두 꺼내는 작업은 정렬과 동일하여 수행시간 O(N log N)을 요구한다.

 

힙은 완전 이진트리 자료구조를 따르며 우선순위가 가장 높은 노드가 루트에 위치한다.

 

1. 최대 힙(max heap)

  • 부모 노드의 키 값이 자식 노드의 키 값보다 항상 크다.
  • 루트 노드가 가장 크며, 값이 큰 데이터가 우선순위를 가진다.

2. 최소 힙(min heap)

  • 부모 노드의 키 값이 자식 노드의 키 값보다 항상 작다.
  • 루트 노드가 가장 작으며, 값이 작은 데이터가 우선순위를 가진다.

 

7. 우선순위 큐(Priority Queue)

우선순위에 따라서 데이터를 추출할 수 있는 자료구조다.

우선순위 큐는 일반적으로 힙(heap)을 이용해 구현하며 배열 자료형으로도 구현가능하다.

우선선순위 큐 구현방식 삽입 시간 삭제 시간
배열 자료형 O(1) O(N)
힙(Heap) O(logN) O(logN)

일반적으로 우선순위 큐는 이진트리 구조를 사용한다.

 

javascript는 기본적으로 우선순위 큐 라이브러리를 제공하지 않기 때문에 필요한 경우

별도의 라이브러리를 사용해야 한다.

 

참고할 수 있는 깃허브: https://github.com/ndb796/priorityqueuejs

 

8. 그래프(Graph)

그래프는 특정 노드의 정점과 노드와 노드의 간선을 나타내기 위한 자료구조이다.

그래프는 두가지 방식으로 구현할 수 있다.

 

  • 인접행렬: 2차원 배열을 사용하는 방식

각 인덱스에 각 노드가 자리해 있으며 위 이미지의 표를 배열로 표현한다면 아래와 같다.

[0, 3, 10]

[3, 0, infinity]

[10, infinity, 0]

 

각 행은 각 노드의 인덱스번호부터 출발했을때 얻을 수 있는 비용이다.

즉 0, 1, 2 의 인덱스를 가지고 있기 때문에 행에 3의 길이여야 하고

각 인덱스에서 출발한다고 가정하면 1행에서 인덱스 0번자리는 자기 자신이기 때문에

비용이 발생하지 않아 0이다. 다음 인덱스 1번자리는  0번노드 -> 1번노드로 이동하는

비용이 3이다. 그리고 0번노드 -> 2번노드는 비용이 10이기 때문에 결과적으로

1번째 행은 [0. 3, 10]이 되는 것이다. 나머지 값들도 모두 동일하게 적용되며

간선이 없어 갈 수 없는 경우는 무한(infinity)으로 표기한다.

 

  • 인접리스트: 연결 리스트를 이용하는 방식

인접 리스트의 경우 노드의 각 인덱스를 각 열로 나열하고 연결가능한 노드와 간선비용을 저장한다.

0번노드의 경우 1번노드와 2번노드를 연결 할 수 있고 1번노드와 연결하면 비용은 3,

2번노드와 연결하면 비용은 10이기 때문에 최상단 행이(index 0번 노드) [1, 3], [2, 10]를 가지게 된다.

나머지도 마찬가지로 연결이 불가능한 경우는 모두 제외하고 연결이 되는 경우만

연결되는 노드번호와 간선의 비용만 저장한다.

 

그래프의 시간 복잡도

  • 인접행렬: 모든 정점들의 연결 여부를 저장해 O(V²)의 공간을 요구한다.
    공간효율성이 좋지 않지만 두 노드의 연결 여부를 O(1)의 시간만을 요구한다.

  • 인접리스트: 연결된 간선의 정보만을 저장하여 O(V + E)의 공간을 요구한다.
    공간효율성이 좋은편이지만 두 노드의 연결여부를 확인하는데 O(V)의 시간이 요구된다.

 

마치며...

지난 알고리즘 학습을 복습하면서 포스팅을 진행하고 있는데 초기에 학습했던 내용들이 분명 학습당시엔 명료했던 이해도가 시간이 지나니 흐리게 남아있는 경우가 많은 것 같다. 정말 기록하는 것은 중요하고 어떠한 키워드를 가지고 생각하며 기록해야하는지를 다시한번 하게 되었다.

2023. 6. 25. 16:46

알고리즘 이란?

어떠한 문제를 해결하기 위한 절차나 방법을 뜻한다.

공식화한 형태로 표현한 것을 의미하며 유한성을 가지고 언젠가는 끝나야 하는 속성을 가지고 있다.

 

알고리즘의 조건

알고리즘은 아래 5가지 조건에 만족해야 한다.

  • 입력: 외부에서 제공되는 자료가 0개 이상 존재해야 한다.
  • 출력: 적어도 2개 이상의 서로 다른 결과를 도출해내야 한다.
  • 명확성: 수행과정은 무엇을 하기위한 것인지 정의가 명확해야 한다.
  • 유한성: 알고리즘의 명령어대로 수행하여 처리가 되었을때 종료되어야 한다.
  • 효율성: 모든 과정이 명백히 실행가능해야 하며 시간적, 공간적 효율성을 가져야 한다.

 

1. 선형 자료구조

하나의 데이터 뒤에 다른 데이터가 하나 존재하는 자료구조이다.
데이터가 일렬로 연속적으로(순차적) 연결되어있다.
배열, 연결리스트, 스택, 큐, 덱 등이 있다.

 

2. 비선형 자료구조

하나의 데이터뒤에 다른 데이터가 여러개 올 수 있는 자료구조이다.
데이터가 일직선상으로 연결되어있지 않아도 되며 트리, 그래프 같은 것이 있다.

 

자료구조와 알고리즘의 관계

효율적인 자료구조 설계를 위해 알고리즘 지식이 요구된다.
효율적인 알고리즘을 작성하기 위해서는 문제상황에 맞는 적절한 자료구조가 사용되어야 하며
프로그램을 작성할 때 자료구조와 알고리즘 모두 고려해야 한다.

 

프로그램의 성능 측정 방법

  • 시간복잡도(time complexity): 알고리즘에 사용되는 연산 횟수를 측정한다.
  • 공간복잡도(space complexity): 알고리즘에 사용되는 메모리의 양을 측정한다.

주로 공간을 많이 사용하는 대신 시간을 단축하는 방법을 선택한다.

 

Big-O 표기법

복잡도를 표현할 때는 Big-O 표기법을 사용한다.

특정한 알고리즘이 얼마나 효율적인지 수치적으로 표현할 수 있으며 가장 빠르게 증가하는

항만을 고려하는 표기법이다.

 

  • 다음 알고리즘은 O(n)의 시간 복잡도를 가진다.
let n =10;
let sum = 0;
for (let i = 0; i < n; i++) {
  sum += i;
}
console.log(sum); // 45

 

  • 다음의 알고리즘은 O(n²)의 시간 복잡도를 가진다.
let n = 9;

for(let i = 1; i <= n; i++) {
  for(let j = 1; j <= n; j++) {
    console.log(`${i} X ${j} = ${i * j}`);
  }
}
/*
실행결과:
1 X 1 = 1
1 X 2 = 2
...
9X 8 = 72
9 X 9 = 81
*/

 

연산횟수에 대한 소요시간

일반적으로 연산횟수가 10억을 넘어가면 1초이상의 시간이 소요된다.
[예시] n = 1000

  • O(log n) : 약 10번 연산
  • O(n) : 약 1,000번 연산
  • O(n log n) : 약 10,000번 연산
  • O(n²) : 약 1,000,000번 연산
  • O(n³) : 약 1,000,000,000번 연산

Big-O표기법으로 시간 복잡도를 표기할 때는 가장 큰 항만을 표시한다.
가장 큰 항에 붙어 있는 계수는 제거한다.
O(3n² + n) = O(n²)

 

 

Big-O 표기법을 사용한 시간복잡도의 예시

  • O(1) : 스택에서 Push, Pop
  • O(log n) : 이진트리
  • O(n) : for 문
  • O(n log n) : 퀵 정렬, 병합정렬, 힙 정렬
  • O(n²): 이중 for 문, 삽입정렬, 거품정렬, 선택정렬
  • O(2ⁿ) : 피보나치 수열

 

마치며...

마무리 글에 갑자기 알고리즘에 대해서 공부를 하게 된 이유에 대해 적을까 한다. 메모서비스 개발을 진행하고 있다가 마무리되어 갈 즈음에 문득 스스로 문제해결능력에 대한 불만와 방식에 대해서 의구심을 품게 되었다. "과연 지금까지의 방식이 효율적이고 가장 빠른 길일까?" 그러다 알고리즘에 대해서 문득 떠올리게 되었고 하나씩 알아보게 되었는데 지금까지 나에게 느꼈던 부족함을 해결해 줄 수 있을 것이라 생각이 들었다. 그렇게 개발에 1개월의 공백을 두게 되었지만 다양한 알고리즘을 학습하고 코드를 직접 만들어보기도 하면서 지금까지 나에게 부족했던 부분들이 많이 채워지는 것을 느꼈다. 문제를 해결하기 위해 사고하는 방식이 달라졌고 시간복잡도에 대한 개념이 생기니 효율적으로 문제해결에 다가설 수 있게 된 것 같았다. 완벽하다고 할 수 없겠지만 나에게 있어 개발자로서 필요한 역량을 향상시키기 위한 없어선 안될 1개월이었던 것 같다.

 

2022. 11. 26. 23:21

1. Blocking 통신 처리방식

클라이언트가 서버와 연결 됐을때 서버에서 스레드를 생성하고 클라이언트에게

스레드를 할당해준다. 클라이언트에 할당된 스레드는 요청을 체크하는 루프에 들어가게되어

스레드가 죽지않고 블로킹되어있는 상태가 된다. 이 후 클라이언트로부터 요청이 들어오면

블로킹 되어있던 스레드가 요청을 처리하고 처리가 끝나면 다시

요청을 체크하는 루프로 들어가게된다. 이렇게 한 클라이언트에 하나의 스레드를

전담으로 할당시켜주는 방식이다. 그렇기 때문에 동기식처리(직렬 처리)만 가능하다.

 

 

2. Non-Blocking 통신 처리방식

클라이언트가 서버와 연결되면 블로킹 방식이 아닌

요청을 리슨하는 eventLoop서버가 요청을 Event Queue에 넣는다.

Event Queue에 들어온 요청은 스레드풀에 담겨져 있던 스레드들이 할당받아

요청을 처리하고 클라이언트에 응답하게 된다.

요청을 처리하는 스레드는 하나의 클라이언트만을 전담하지 않고

어떤 요청이든 간에 Event Queue에 들어온 요청을 하나씩 하나씩 가져가

처리하기 때문에 비동기식처리(병렬 처리)가 가능하다.

 

 

Why Non-Blocking?

Blocking통신 처리방식은 각각 하나의 스레드를 전담마크식으로

클라이언트에게 할당시켜 주기 때문에 1000만명의 유저가 있다고

가정할 경우 1000만개의 스레드가 계속해서 대기를 하거나 요청을

처리하게 된다.

 

하지만 Non-Blocking의 경우 스레드 1000만개를 미리 만드는 것이 아니라

필요에 의해서 만들어진 만큼의 스레드만 스레드풀에 존재하고

1000만명의 유저가 동시 다발적으로 요청을 보낼 확률은 아주 낮기때문에

Blocking통신 처리방식에 비해 스레드의 총량이 획기적으로 차이가 난다.

그로 인해서 서버의 부담이 줄어들기 때문에 근래에는 Non-Blocking을

더 많이 채택하게 된 것이다.

 

'Computer Science > Network' 카테고리의 다른 글

[ TCP / UDP ] 특징과 차이점  (0) 2022.11.26
2022. 11. 26. 19:47

1. TCP ( Transmission Control Protocol )

  • 연결 지향 방식이다.
  • 3-way handshaking과정을 통해 연결을 설정하고 4-way handshaking을 통해 해제한다.
  • 흐름 제어 및 혼잡 제어.
  • 높은 신뢰성을 보장한다.
  • UDP보다 속도가 느리다.
  • 전이중(Full-Duplex), 점대점(Point to Point) 방식.

TPC는 연결지향 방식이며 이는 패킷을 전송하기 위해서 논리적 경로를 배정한다는 뜻이다.

연결형 서비스로 신뢰성을 보장하기에 3-way handshaking과정을 사용한다.

 TCP는 연속성보다 신뢰성있는 전송이 중요할 때에 사용하는 프로토콜이다.

예를 들면 파일 전송과 같은 경우에 사용된다.

 

TCP 서버의 특징

  • 서버소켓은 연결만을 담당한다.
  • 연결과정에서 반환된 클라이언트 소켓은 데이터의 송수신에 사용된다
  • 서버와 클라이언트는 1대1로 연결된다.
  • 스트림 전송으로 전송 데이터의 크기가 무제한이다.
  • 패킷에 대한 응답을 해야하기 때문에(시간 지연, CPU 소모) 성능이 낮다.
  • Streaming 서비스에 불리하다.(손실된 경우 재전송 요청을 하므로)

패킷(Packet)

인터넷 내에서 데이터를 보내기 위한 경로배정(라우팅)을 효율적으로 하기 위해

데이터를 여러 개의 조각들로 나누어 전송하는데 이 조각을 패킷이라고 한다.

 

TCP 패킷의 추적 및 관리

A,B,C 의 패킷이 있다고 가정하면 (A = 1), (B = 2), (C = 3) 이렇게 변수처럼
특정번호를 부여하여 패킷의 분실확인, 관리, 목적지 재조립을 한다.

 

 

2. UDP(User Datagram Protocol)

  • 비연결형 서비스로 데이터그램 방식을 제공한다
  • 정보를 주고 받을 때 정보를 보내거나 받는다는 신호절차를 거치지 않는다.
  • UDP헤더의 CheckSum 필드를 통해 최소한의 오류만 검출한다.
  • 신뢰성이 낮다
  • TCP보다 속도가 빠르다

UDP는 비연결형 서비스이며 연결을 설정하고 해제하는 과정이 존재하지 않는다.

서로 다른 경로로 독립적으로 처리하며 TCP와 반대로

순서부여->재조립, 흐름/혼잡제어 등을 하지 않아 속도가 빠르며

네트워크 부하가 적다는 장점이 있다.

신뢰성보다는 연속성이 중요한 서비스에 사용된다.

예를 들면 실시간 서비스(streaming)에 자주 사용된다.

 

UDP 서버의 특징

  • UDP에는 연결 자체가 없어서(connect 함수 불필요) 서버 소켓과 클라이언트 소켓의 구분이 없다.
  • 소켓 대신 IP를 기반으로 데이터를 전송한다.
  • 서버와 클라이언트는 1대1, 1대N, N대M 등으로 연결될 수 있다.
  • 데이터그램(메세지) 단위로 전송되며 그 크기는 65535바이트로, 크기가 초과하면 잘라서 보낸다.
  • 흐름제어(flow control)가 없어서 패킷이 제대로 전송되었는지, 오류가 없는지 확인할 수 없다.
  • 파일 전송과 같은 신뢰성이 필요한 서비스보다 성능이 중요시 되는 경우에 사용된다.

 

UDP의 흐름제어와 혼잡제어

흐름제어는 데이터를 송신하는 곳과 수신하는 곳의 데이터 처리 속도를 조절하여

수신자의 버퍼 오버플로우를 방지하는 것이다.

송신하는 곳에서 감당이 안되게 데이터를 빠르게, 많이 보내면 수신자에서 문제가 발생하기 때문이다.

 

혼잡제어는 네트워크 내의 패킷 수가 넘치게 증가하지 않도록 방지하는 것이다.

만약 정보의 소통량이 과다하면 패킷을 조금만 전송하여

혼잡 붕괴 현상이 일어나는 것을 막는다.

 

 

3. TCP와 UDP의 비교

프로토콜 종류 TCP UDP
연결 방식 연결형 서비스 비연결형 서비스
패킷 교환 방식 가상 회선 방식 데이터그램 방식
전송 순서 전송 순서 보장 전송 순서가 바뀔 수 있다
수신여부 확인 수신여부 확인 O 수신여부 확인 X
통신방식 1:1 통신 1:1  or  1:N  or  N:N 통신
신뢰성 높다 낮다
속도 느리다 빠르다

'Computer Science > Network' 카테고리의 다른 글

[ Blocking / Non-Blocking ] 통신 처리방식  (0) 2022.11.26