2024/02/29 (목) TIL - 프로세스와 스레드, Synchronous(동기) VS Asynchronus(비동기) , Deadlock

이재원·2024년 2월 29일

프로세스와 스레드

  • 컴퓨터가 프로세스 여러개를 한번에 돌리는 멀티태스킹이 가능해지기 전까지 옛날 컴퓨터는 브라우저로 웹서핑을 하다가 원하는 파일을 다운받을 때 키보드 및 마우스가 눌리지 않아 아무것도 하지 못했다.

  • 실행할 수 있는 파일, window의 경우 .exe가 뒤에 붙어 있는 파일을 프로그램이라고 한다. 이러한 프로그램을 실행시켜 돌아가고 있는 상태를 프로세스라고 한다. 위에 예시와 달리 오늘날 처럼 쾌적하게 컴퓨터를 사용할 수 있는건 운영체제가 여러 개의 프로세스를 함께 돌리고 있기 때문이다.

  • window의 경우 작업관리자(Ctrl + Alt + Del)를 확인해보면 많은 프로세스가 실행되고 있는지 확인할 수 있다.

  • 여러개의 프로세스를 한번에 돌리는 작업은 동시적, 병렬적 혹은 둘다 혼합하여 이루어진다.

    • 동시성은 프로그램 하나가 조금씩 여러 가지의 일을 하는 것을 말한다.

    • 조금씩 작업을 하면서 다른 작업으로 이동하는(작업을 바꾸는) 과정을 Context Switching이라고한다.

      • Context Switching에는 다양한 알고리즘이 쓰인다.
      • Context Switching 과정이 빠르게 진행되어 사람들은 마치 동시에 진행되는 것 처럼 느껴진다.
    • 병렬성은 프로세서 하나에 코어 여러개가 달려서 각각 동시에 작업들을 수행하는 것을 말한다.

      • dual core, quad core octa core 등 멀티 코어 프로세서가 달린 컴퓨터에서 할 수 있는 방식이다.
        • CPU의 속도가 발열 등 물리적 제약 때문에 예전만큼 빠르게 발전하지 못하자 그 대안으로, 코어를 여러 개 달아서 작업을 분담할 수 있도록 만들었다.

컴퓨터는 이러한 방식으로 여러 개의 프로세스를 한 번에 돌릴 수 있게 되었다. 그러나 우리는 현재 브라우저에서 파일을 다운받는 동시에 웹 브라우저를 돌아다닐 수 있고, 유튜브 영상을 시청하면서 웹 서핑이 가능하다. 이렇게 한 프로세스 안에서도 여러 갈래의 작업들이 동시에 진행될 필요가 있는데 이 갈래를 Thread(스레드)라고 부른다.

스레드 예시

프로세서를 요리사라 하고 대량주문이 오는 식당에서 끊임없이 만들어 내는 메뉴 하나하나를 프로세스라고 하자 컴퓨터는 프로세스마다 자원을 분할해서 할당한다.

  • 라면 섹션, 김밥 섹션, 햄버거 섹션 이렇게 조리 공간을 나눠서 요리사 혼자서 조금씩 요리(동시성)를 하던 요리사 여러명이 요리(병렬성)를 하던 혹은 둘을 섞어서 하던 계속해서 메뉴들을 만들어낸다.
  • 햄버거를 만드는 스레드가 진행되는 동안 빵에 야채를 얹고 소스를 뿌리는 스레드도 진행 될 수 있다.
  • 한 메뉴의 스레드들은 같은 조리대에서 진행이 된다. 같은 메뉴를 만들 때는 같은 공간과 장비 즉 같은 자원 을 공유하는 것이 더 효율적이다.
  • 프로세스들은 컴퓨터의 자원을 분할해서 쓰지만 스레드는 프로세스마다 주어진 전체 자원을 함께 사용한다.
    • 스레드는 스택을 제외한 코드, 데이터, 힙 영역을 공유한다.
    • 속도와 효율면에서는 좋겠지만 단점이 존재한다.
    • 프로세스 안에서 공유되는 변수에 스레드 두 개가 접근하는 경우 원하는 결과가 나오지 않을 수 있다.ex) 스레드 두 개가 각각 10번 씩 버튼을 누르는 경우 20번이 되야하지만 동시에 누르는 경우 20번이 되지 않을 수 있다.
      • 이런 상황을 방지해야 하기 때문에 스레드를 사용하는 프로그래밍은 코드짜기, 디버깅 후 오류를 찾아내서 원인을 밝히기 등이 매우 까다로운 경우가 많다.
      • 다행히 이런 작업들을 더 쉽고 안전하게 할 수 있는 방법들이 많다.
        ex) Closure, Functional Programming, Lambda, Actor
  • 스레드 사용시 두개를 사용했는데 하나만 줄어드는 경우를 방지하기 위해 Java에는 synchronized block이 있다. synchronized block안에 있는 변수들은 한 번에 한 스레드만 접근할 수 있게한다. 이 공간을 임계영역이라고 한다.
  • 임계영역
    • 멀티 스레딩 환경에서 여러 스레드들이 동시에 접근하면 안 되는, 공유 데이터 또는 자원에 대한 접근을 수행하는 코드의 부분을 말한다.
    • 여러 스레드(또는 프로세스)가 동시에 공유 자원을 사용하려 할 때 데이터의 일관성과 동기화를 유지하기 위해 반드시 해결해야한다.
    • 임계영역을 관리하는 주된 목적은 동시성 제어를 통해 데이터의 정합성을 보장하고, 경쟁 상태(Race Condition)를 방지하는 것이다.

경쟁상태(Race Condition)

  • 여러 스레드 또는 프로세스가 동시에 공유 자원에 접근할 때, 접근의 순서에 따라 실행 결과가 달라지는 상황
  • 이를 방지하기 위해 임계영역에 대한 접근은 언제나 상호 배제(Mutual Exclusion) 원칙에 따라 이루어져야 한다.

하지만 이 공유되는 자원들로 인해 동시성 제어와 자원의 동기화에 주의해야 한다. 예를 들어, 하나의 스레드가 공유 데이터를 변경하고 있을 때 다른 스레드가 동시에 같은 데이터에 접근하려고 하면 예상하지 못한 결과가 발생할 수 있다. 이를 방지하기 위해 뮤텍스(mutexes), 세마포어(semaphores), 모니터(monitors)와 같은 동기화 기술들이 사용된다.

물리적 스레드, 논리적스레드 ? 추상화



chronous(동기) VS Asynchronus(비동기)

  • 동기적 : 작성된 순서대로 코드가 실행된다.
  • 비동기적 : 꼭 작성된 순서대로 코드가 실행되지 않는다.

한줄 한줄 실행되지 않아 헷갈리는 비동기 코드를 쓰는 이유는 무엇인가?

-> 순서대로 하면 일을 하는 동안 기다려야 하는데 기다리는 시간동안 다른 일을 하기 위해서

Synchonized -> 순서대로 , 차례가 있는 부분 동기화 한다. -> 코드를 순서대로 작동하게 한다.

  • 프로그램이 비동기적으로 일을 한다. -> 쓰레드나 프로세스가 여럿이 돌고있다.

https://www.youtube.com/watch?v=m0icCqHY39U



Deadlock(데드락) - 교착상태

여러 프로세스나 스레드가 서로 상대방이 가진 자원을 기다리며 무한히 대기하는 상태를 말한다.

  • 교착 상태라고도 한다.
  • 이 상태에서는 어떤 프로세스도 자신의 작업을 계속 진행할 수 없으며, 시스템의 일부분이 멈춰버리는 현상이 발생한다.

Deadlock 발생 조건

교착 상태는 한 시스템 내에서 네 가지 조건이 동시에 성립 할 때 발생한다.

  1. 상호배제(Mutual Exclusion : 뮤텍스): 자원은 한 번에 한 프로세스만이 사용할 수 있어야 한다.
  2. 점유 대기(Hold and Wait): 프로세스는 최소한 하나의 자원을 점유하고 있으면서, 다른 프로세스가 점유한 자원을 추가로 얻기 위해 대기해야 한다.
  3. 비선점(No Preemption): 프로세스가 자원을 점유하고 있을 때, 다른 프로세스가 그 자원을 강제로 뺏을 수 없다.
  4. 순환 대기(Circular Wait): 대기하는 프로세스들 사이에 한 방향의 순환 사슬이 형성되어야 하며, 각 프로세스는 다음 프로세스가 요구하는 자원을 가지고 있어야 한다.

데드락 예시

  • 프로세스 A와 프로세스 B가 있다.
  • 프로세스 A는 자원 1을 가지고 있고, 자원 2를 요구한다.
  • 프로세스 B는 자원 2를 가지고 있고, 자원 1을 요구한다.
  • 두 프로세스 모두 다른 프로세스가 가진 자원을 기다리고 있으므로, 둘 다 진행할 수 없는 데드락 상태가 된다.

데드락 해결 방법

1. 데드락 예방(Deadlock Prevention)법

  • 데드락 발생 조건 중 하나 이상을 사전에 제거하여 데드락이 발생하지 않도록 하는 방법
    • 자원 낭비가 심하다
    1. 상호 배제 부정
      • 여러 개의 프로세스가 공유 자원을 사용할 수 있도록 한다.
    2. 점유 대기 부정
      • 프로세스가 실행되기 전 필요한 모든 자원을 할당하여 점유 대기 조건을 없앤다.
    3. 비선점 부정
      • 자원을 점유하고 있는 프로세스가 다른 자원을 요구할 때 점유하고 있는 자원을 반납하고, 요구한 자원을 사용하기 위해 기다리게한다.
    4. 순환대기 부정
      • 자원에 고유한 번호를 할당하고, 번호 순서대로 자원을 요구하도록 한다.

2. 데드락 회피(Deadlock Avoidance)

  • 자원 할당 시 데드락의 가능성을 회피하는 방법
  • 가장 유명한 방법 중 하나는 은행원 알고리즘(Banker’s Algorithm)이다.

3. 데드락 탐지 및 회복(Deadlock Detection and Recovery)

  • 데드락이 발생했을 때 이를 탐지하고, 해결하는 방법

  • 데드락을 탐지한 후에는 프로세스를 종료하거나 할당된 자원을 강제로 회수하여 다른 프로세스에 할당하고 해당 프로세스를 일시 정지 시키는 등의 조치를 취할 수 있다.

  • 대부분의 시스템은 교착 상태가 잘 발생하지 않으며, 교착 상태 예방, 회피, 탐지, 복구하는 것은 비용이 많이 든다

은행원 알고리즘(Banker's Algorithm)

E,J,Dijkstra가 제안한 방법으로 데드락 회피(Deadlock Avoidance)를 위한 알고리즘 중 하나디. 운영체제가 프로세스에 자원을 안전하게 할당할 수 있는지 결정하는 데 사용한다.

  • 은행에서 모든 고객의 요구가 충족되도록 현금을 할당하는 데서 유래한 기법이다.
  • 프로세스가 자원을 요구할 때 시스템은 자원을 할당한 후에도 안정 상태로 남아있게 되는지를 사전에 검사하여 교착 상태를 회피하는 기법

안전 상태(Safe State)

  • 시스템이 현재의 자원 할당 상태에서 모든 프로세스가 최대로 요구할 수 있는 자원을 순서대로 할당해 줄 수 있는 상태
  • 안전 상태는 시스템이 데드락 없이 모든 프로세스가 완료될 수 있음을 의미한다.

불안전 상태(Unsafe State)

  • 안전 상태가 아닌 모든 상태
  • 불안전 상태가 반드시 데드락을 의미하는 것은 아니지만, 데드락으로 이어질 가능성이 있다.

작동원리

  1. 자원 요청: 프로세스가 자원을 요청할 때, 시스템은 요청이 이루어질 경우 시스템이 여전히 안전 상태에 있는지를 검사한다.
  2. 안전성 검사: 요청된 자원을 할당한 후에도 모든 프로세스가 요구하는 최대 자원까지 안전하게 할당할 수 있는 순서(안전 순서)가 존재하는지를 확인합니다. 이 과정에서는 각 프로세스의 최대 자원 요구량, 현재 할당된 자원, 시스템에 남아 있는 자원 등을 고려한다.
  3. 자원 할당: 시스템이 안전 상태를 유지할 수 있다면, 자원을 프로세스에 할당합니다. 그렇지 않다면, 자원 요청을 거절하고 프로세스는 대기 상태로 들어갑니다.

안정 상태에 있으면 자원을 할당하고, 그렇지 않으면 다른 프로세스들이 자원을 해지할 때까지 대기한다. 교착 상태가 되지 않도록 보장하기 위하여 교착 상태를 예방하거나 회피하는 프로토콜을 이용하는 방법

은행원 알고리즘의 장점과 단점

장점

  • 데드락을 효과적으로 회피할 수 있으며, 시스템이 안전 상태를 유지하도록 보장

단점

  • 알고리즘 구현이 복잡하고, 모든 프로세스의 최대 자원 요구량을 미리 알아야 한다는 점에서 현실적인 제약이 있다.
  • 자원의 이용률이 낮아질 수 있으며, 오버헤드가 클 수 있다.

결론

은행원 알고리즘은 이론적으로 데드락 회피에 매우 유용하지만, 실제 시스템에서는 자원 요구량의 불확실성, 알고리즘의 복잡성 등으로 인해 직접 적용되기보다는 다른 자원 관리 및 데드락 회피 기법과 함께 사용될 때 더 효과적일 수 있다.


뮤텍스(Mutex), 모니터(Monitor), 세마포어(Semaphore)

  • 동시성 프로그래밍에서 사용되는 동기화 기법
  • 여러 스레드나 프로세스가 공유자원에 동시에 접근할 때 발생할 수 있는 문제를 방지하기 위해 사용된다.
  • 데드락 같은 문제를 방지하기 위해 필수적이다.

뮤텍스(Mutex)

  • Mutual Exclusion(상호 배제)의 약자로, 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 하는 동기화 메커니즘이다.
  • 뮤텍스를 획득한 스레드만이 자원에 접근할 수 있으며, 다른 스레드는 뮤텍스가 해제될 때까지 대기해야 한다.
  • 소유 가능한 잠금입니다. 즉, 잠금을 획득한 스레드만이 그 잠금을 해제할 수 있다
  • 상호 배제를 보장하지만, 데드락과 같은 다른 문제들을 자동으로 해결하지는 않는다.
  • 임계영역에 대한 접근을 제어하기 위해 락(Lock)과 언락(Unlock) 메커니즘을 사용

모니터(Monitor)

  • 고수준의 추상화를 제공하는 동기화 메커니즘으로, 임계영역 코드를 모니터 내에 캡슐화하여 스레드의 안전한 실행을 보장한다.
  • 뮤텍스와 조건 변수(condition variables)를 함께 사용하여, 스레드 간의 동기화를 더욱 쉽게 구현할 수 있도록 설계된 추상 데이터 타입 또는 클래스다.
  • 모니터 내의 모든 메서드는 자동으로 상호 배제를 보장받아, 한 번에 하나의 스레드만이 모니터의 메서드를 실행할 수 있다.
  • 조건 변수를 사용하여 특정 조건 하에서 스레드가 대기하거나 깨어날 수 있게 한다.

세마포어(Semaphore)

  • 시성 제어를 위해 사용되는 변수로, 리소스의 수를 나타낸다.
  • 카운트를 기반으로 한 더 일반화된 동기화 메커니즘으로, 뮤텍스보다 복잡한 동기화 문제를 해결할 수 있다.
  • 이진 세마포어
    • 0 과 1의 값만 가질 수 있으며, 뮤텍스와 매우 유사한 방식으로 동작한다.
  • 카운팅 세마포어
    • 동시에 여러 스레드가 리소스에 접근할 수 있도록 허용할 수 있는 리소스의 최대 개수를 정의한다.
  • 세마포어는 wait()(또는 P 연산)과 signal()(또는 V 연산) 두 가지 주요 연산으로 구성된다.
    • wait()
      • 리소스를 사용하려 할 때 호출되며, 리소스가 없을 경우 스레드를 대기 상태로 만든다.
    • signal()
      • 리소스 사용이 끝났음을 알리고, 대기 중인 스레드 중 하나를 깨운다.

부동소수점과 그 오차

0.1 + 0.2 != 0.3 인 이유
0.1과 0.2 가 진짜 0.1과 0.2가 아니기 때문에 값이 다르게 나온다. 십진수의 0.1을 이진수로 나타내는 경우 -> 0.0001100110011.....(2)
0.1에 가까워질 뿐 맞아 떨어지지 않는다.
-> 2진법에서는 1/10이 무한소수가 된다.

고정소수점, 부동소수점
고정소수점은 가수와 지수 부분을 반으로 자른다?
그러면 소수가 긴 경우 제대로 표현할 수 없다.

부동소수점은 그 방식을 해결할 수 있다.
IEEE 754 방식

  • 소수점 오차를 해결하기 위해 언어의 라이브러리를 사용하거나
  • int를 사용한다.
    ex ) 탱크 연산 문제 ?

int와 integer 차이점

`int`와 `integer`는 프로그래밍 언어와 컨텍스트에 따라 다양한 의미를 가질 수 있지만, 일반적으로 두 용어는 정수형 데이터 타입을 지칭하는 데 사용됩니다. 그러나 `int`는 주로 원시 데이터 타입(primitive data type)을, `integer`는 래퍼 클래스(wrapper class)나 추상화된 정수 타입을 의미하는 경우가 많습니다. 여기서 구체적인 차이점을 몇 가지 예를 들어 설명하겠습니다.

### Java에서의 `int`와 `Integer`

- **`int`**: Java에서 `int`는 원시 데이터 타입으로, 32비트 정수 값을 저장하는 데 사용됩니다. `int`는 스택 메모리에 저장되며, 객체가 아니기 때문에 `int` 변수에는 정수 값만 저장할 수 있습니다. 또한, `int`는 null 값을 가질 수 없습니다.
- **`Integer`**: `Integer`는 `int`의 래퍼 클래스로, Java의 java.lang 패키지에 속해 있습니다. `Integer`는 객체이므로 힙 메모리에 저장되며, 이를 통해 `int` 값을 객체로 사용할 수 있습니다. `Integer`는 null 값을 가질 수 있으며, 클래스이기 때문에 메서드와 속성을 가질 수 있습니다. 예를 들어, `Integer` 클래스는 정수를 문자열로 변환하는 등의 유용한 메서드를 제공합니다.

### C와 C++에서의 `int`

- **`int`**: C와 C++에서 `int`는 원시 정수형 데이터 타입으로 사용됩니다. 이 언어들에서는 `int`에 대한 래퍼 클래스나 `integer`라는 명시적인 타입은 제공하지 않습니다.

### SQL에서의 `INTEGER`

- **`INTEGER`**: SQL에서 `INTEGER`는 정수형 컬럼을 정의하는 데 사용되는 데이터 타입입니다. 특정 데이터베이스 시스템에서는 `INT`와 `INTEGER`를 동의어로 사용하기도 합니다.

### Python에서의 정수

- **정수형**: Python에서는 `int`를 정수형 데이터 타입으로 사용합니다. Python 3 이상에서는 `int`와 `long`이 통합되어, 모든 정수는 단순히 `int`로 표현됩니다. Python에서는 `integer`라는 용어는 사용되지 않으며, 모든 정수는 `int` 타입입니다.

### 결론

`int`와 `integer`의 차이는 주로 사용되는 프로그래밍 언어와 컨텍스트에 따라 달라집니다. 일반적으로 `int`는 원시 데이터 타입을 의미하는 반면, `integer`는 객체 지향 언어에서 원시 `int` 타입의 값을 객체로 취급할 수 있는 래퍼 클래스나 추상화된 타입을 의미하는 경우가 많습니다.

출처
https://www.youtube.com/watch?v=iks_Xb9DtTM

profile
최고가 되기 위한 여정

0개의 댓글