Maximize Your Potential

CSKnowledge

[CS 지식 공부하기] 프로세스와 스레드의 이해 - 파이썬에서 쓰레드 예제 만들기

maxworld 2024. 7. 29. 21:28
728x90
반응형

[CS 지식 공부하기]프로세스와 스레드의 이해 - 파이썬에서 쓰레드 예제 만들기

OS에서 가장 중요한 것은 프로세스와 스레드 일 것입니다. 기본적으로 운영체제에서 빠질 수 없는 주제입니다.

이 글에서는 프로세스와 스레드의 개념을 탐구하고, 다중 스레드 프로그래밍의 장점에 대해 논의해보겠습니다.

프로세스와 스레드의 이해

프로세스는 컴퓨터에서 실행 중인 프로그램의 인스턴스입니다. 운영 체제는 프로세스에 메모리 공간, 파일 핸들, 입출력 장치 등 실행에 필요한 자원을 할당합니다. 각 프로세스는 고유한 주소 공간에서 독립적으로 실행되며, 다른 프로세스와 자원을 공유하지 않습니다.

스레드는 프로세스 내에서 실제로 작업을 수행하는 실행 단위입니다. 프로세스는 하나 이상의 스레드를 가질 수 있으며, 이 스레드들은 프로세스의 자원을 공유하면서 동시에 실행될 수 있습니다. 각 스레드는 고유의 스레드 ID, 프로그램 카운터, 레지스터 세트 및 스택을 가지고 있지만, 코드, 데이터 섹션 및 열린 파일과 같은 프로세스의 자원은 다른 스레드와 공유합니다.

프로세스(Process)

프로세스는 실행 중인 프로그램을 의미합니다. 이는 사용자 프로그램일 수도 있고 시스템 프로그램일 수도 있습니다. 프로세스는 단순히 프로그램 코드뿐만 아니라, 그 코드가 실행되기 위한 데이터와 자원도 포함합니다. 예를 들어, 메모리 공간, CPU 시간, I/O 장치 등이 프로세스에 할당됩니다.

PCB(프로세스 제어 블록, Process Control Block)

PCB는 운영체제가 프로세스에 대한 중요한 정보를 저장하는 데이터 구조입니다. 각 프로세스가 생성될 때마다 고유의 PCB가 생성되고, 프로세스가 종료되면 PCB도 함께 제거됩니다. 

  • 프로세스 식별자 (PID): 프로세스의 고유 ID
  • 프로세스 상태: 실행 중, 대기 중, 준비 중 등
  • 프로그램 카운터: 현재 실행 중인 명령어의 주소
  • 레지스터 값: 현재 프로세스의 레지스터 상태
  • 메모리 관리 정보: 할당된 메모리 블록 및 페이지 테이블 정보
  • I/O 상태 정보: 프로세스가 사용하는 I/O 장치와 관련된 정보
  • 프로세스 우선순위: 스케줄링 시 우선 순위를 결정하는 정보

프로세스 상태 전이

프로세스는 다양한 상태를 거치면서 실행됩니다. 주요 상태와 전이 과정은 다음과 같습니다:

  1. 제출 (Submit): 사용자가 작업을 시스템에 제출한 상태입니다. 이 상태에서는 작업이 준비되지 않았으며, 스풀 공간(디스크 등)에 저장됩니다.
  2. 접수 (Hold): 제출된 작업이 스풀 공간에 저장된 상태로, 아직 준비 상태로 전이되지 않았습니다. Spooling은 I/O 장치의 처리 속도를 보완하기 위해 데이터를 디스크에 저장하는 과정입니다.
  3. 준비 (Ready): 프로세스가 CPU를 할당받기 위해 기다리고 있는 상태입니다.
  4. 실행 (Run): 프로세스가 CPU를 할당받아 실행되고 있는 상태입니다.
  5. 대기 (Wait, Block): 프로세스가 I/O 처리를 필요로 하여 대기하는 상태입니다. I/O 작업이 완료되면 다시 준비 상태로 돌아갑니다.
  6. 종료 (Terminated, Exit): 프로세스의 실행이 끝나고, 프로세스에 대한 자원이 해제된 상태입니다.

프로세스 상태 전이 과정

  • Dispatch: 준비 상태에서 대기하고 있는 프로세스 중 하나가 CPU를 할당받아 실행 상태로 전이되는 과정입니다.
  • Wake Up: 대기 상태의 프로세스가 I/O 작업 완료로 인해 준비 상태로 전이되는 과정입니다.

스케줄링 (Scheduling)

스케줄링은 프로세스가 실행되기 위해 시스템 자원을 할당받는 과정을 관리합니다. 스케줄링은 크게 장기 스케줄링, 중기 스케줄링, 단기 스케줄링으로 나눌 수 있습니다.

  1. 장기 스케줄링 (Long-term Scheduling): 시스템 자원을 차지할 프로세스를 결정하고, 이를 준비 상태 큐로 보내는 작업입니다. 이 과정은 프로세스의 입출력 요구나 자원 사용 패턴을 고려하여 프로세스를 시스템에 진입시키는 역할을 합니다.
  2. 중기 스케줄링 (Medium-term Scheduling): 준비 상태 큐에서 어떤 프로세스가 CPU를 할당받을지를 결정하는 작업입니다. 이 단계는 프로세스가 실제로 CPU를 사용할 시기를 결정합니다.
  3. 단기 스케줄링 (Short-term Scheduling): 프로세스가 CPU를 할당받기 위한 시기와 특정 프로세스를 지정하는 작업입니다. 주로 프로세스가 실행 중인 CPU를 얼마나 자주 교체할지를 결정합니다.

비선점 스케줄링

비선점 스케줄링에서는 CPU를 한 번 할당받은 프로세스가 자발적으로 CPU를 반환할 때까지 계속 사용할 수 있습니다. 주요 비선점 스케줄링 기법으로는 다음이 있습니다.

  • FCFS (First-Come, First-Served): 가장 먼저 도착한 프로세스가 먼저 CPU를 할당받는 방식입니다.
  • SJF (Shortest Job First): 실행 시간이 가장 짧은 프로세스가 우선적으로 CPU를 할당받는 방식입니다.
  • 우선순위 (Priority Scheduling): 우선순위가 높은 프로세스가 먼저 CPU를 할당받는 방식입니다.
  • HRN (Highest Response Ratio Next): 대기 시간이 긴 프로세스에 더 높은 우선순위를 부여하는 방식입니다.
  • 기한부 (Deadline Scheduling): 프로세스의 기한을 고려하여 우선순위를 결정하는 방식입니다.

선점 스케줄링

선점 스케줄링에서는 우선 순위가 높은 프로세스가 현재 CPU를 사용 중인 프로세스를 강제로 중단시키고 CPU를 차지할 수 있습니다. 이 방식은 다음과 같은 기법들이 있습니다:

  • Round Robin (RR): 각 프로세스가 정해진 시간 슬라이스(퀀텀) 동안 CPU를 사용하는 방식입니다. 모든 프로세스가 공평하게 CPU 시간을 나눠가집니다.
  • SRT (Shortest Remaining Time): 남은 실행 시간이 가장 짧은 프로세스가 우선적으로 CPU를 할당받는 방식입니다.
  • 선점 우선순위 (Preemptive Priority Scheduling): 우선순위가 높은 프로세스가 현재 실행 중인 프로세스를 중단시키고 CPU를 차지하는 방식입니다.
  • 다단계 큐 (Multilevel Queue Scheduling): 여러 개의 큐를 사용하여 각 큐의 우선순위에 따라 프로세스를 스케줄링하는 방식입니다.
  • 다단계 피드백 큐 (Multilevel Feedback Queue Scheduling): 프로세스가 실행 중의 상태에 따라 우선순위를 조정하는 방식입니다. 실행 시간이 길어지면 우선순위가 낮아지고, 반대로 짧아지면 우선순위가 높아집니다.

FCFS 스케줄링 (First Come First Served Scheduling)

FCFS는 가장 먼저 도착한 프로세스에게 CPU를 할당하는 방식입니다. 이 방법은 간단하지만 긴 작업이 먼저 도착하면, 뒤에 도착한 짧은 작업들이 기다려야 하는 "호위효과"가 발생할 수 있습니다.

작업과 Burst Time

    • P1: 24
    • P2: 3
    • P3: 3
  • 도착 순서: P1 -> P2 -> P3
| P1 | P2 | P3 |
0 24   27   30
  • 평균 반환 시간:
    • P1: 24
    • P2: 27
    • P3: 30
    • (24 + 27 + 30) / 3 = 27
  • 평균 대기 시간:
    • P1: 0
    • P2: 24 (P1의 실행 완료 후 대기)
    • P3: 27 (P1과 P2의 실행 완료 후 대기)
    • (0 + 24 + 27) / 3 = 17

FCFS는 간단하지만 긴 프로세스가 대기시간을 증가시키는 단점이 있습니다.

SJF 스케줄링 (Shortest Job First Scheduling)

SJF는 CPU Burst Time이 가장 짧은 작업을 우선적으로 처리하는 기법입니다. 평균 대기 시간을 최소화하는 알고리즘으로, 단기 작업에 유리합니다.

  • 작업과 Burst Time:
    • P1: 6
    • P2: 3
    • P3: 8
    • P4: 7
  • 작업 순서: P2 -> P1 -> P4 -> P3
| P2 | P1 | P4 | P3 | 
0  3   9    16   24
  • 평균 반환 시간:
    • P1: 9
    • P2: 3
    • P3: 24
    • P4: 16
    • (9 + 3 + 24 + 16) / 4 = 13
  • 평균 대기 시간:
    • P1: 3 (P2의 실행 완료 후 대기)
    • P2: 0
    • P3: 16 (P2, P1, P4의 실행 완료 후 대기)
    • P4: 9 (P2와 P1의 실행 완료 후 대기)
    • (3 + 0 + 16 + 9) / 4 = 7

SJF는 평균 대기 시간이 짧지만, 긴 작업이 기아에 빠질 수 있습니다.

HRN 스케줄링 (Highest Response Ratio Next)

HRN은 SJF의 단점을 보완하기 위해 대기 시간과 실행 시간을 고려한 우선순위 기반의 스케줄링 방법입니다. 각 프로세스의 우선순위는 (대기 시간 + 실행 시간) / 실행 시간으로 계산됩니다.

예시

  • 작업과 대기 시간, 실행 시간:
    • P1: 대기 시간 5, 실행 시간 5
    • P2: 대기 시간 10, 실행 시간 6
    • P3: 대기 시간 15, 실행 시간 7
    • P4: 대기 시간 20, 실행 시간 8
  • 우선순위 계산:
    • P1 우선순위 = (5 + 5) / 5 = 2.0
    • P2 우선순위 = (10 + 6) / 6 = 2.67
    • P3 우선순위 = (15 + 7) / 7 = 3.14
    • P4 우선순위 = (20 + 8) / 8 = 3.5
  • 작업 순서: P4 -> P3 -> P2 -> P1

간트 차트

| P4 | P3 | P2 | P1 | 
0  8   15   21   26
 
  • 평균 반환 시간:
    • P1: 26
    • P2: 21
    • P3: 15
    • P4: 8
    • (26 + 21 + 15 + 8) / 4 = 17.5
  • 평균 대기 시간:
    • P1: 21 - 5 = 16
    • P2: 15 - 6 = 9
    • P3: 8 - 7 = 1
    • P4: 0 - 8 = -8 (기본값 0으로 처리)
    • (16 + 9 + 1 + 0) / 4 = 6.5

HRN은 긴 작업도 처리할 수 있도록 도와주지만, 여전히 기아 현상이 발생할 수 있습니다.

우선순위 스케줄링 (Priority Scheduling)

우선순위 스케줄링은 프로세스에 우선순위를 부여하고, 가장 높은 우선순위의 프로세스부터 CPU를 할당합니다. 우선순위가 같을 경우 FCFS 방식을 사용합니다.

예시

  • 작업과 우선순위:
    • P1: Burst Time 10, 우선순위 3
    • P2: Burst Time 1, 우선순위 1
    • P3: Burst Time 2, 우선순위 3
    • P4: Burst Time 1, 우선순위 4
    • P5: Burst Time 5, 우선순위 2
  • 작업 순서: P2 -> P5 -> P1 -> P3 -> P4
| P2 | P5 | P1 | P3 | P4 |
0  1    6   16   18   19
  • 평균 반환 시간:
    • P1: 16
    • P2: 1
    • P3: 18
    • P4: 19
    • P5: 6
    • (16 + 1 + 18 + 19 + 6) / 5 = 12
  • 평균 대기 시간:
    • P1: 16 - 10 = 6
    • P2: 0
    • P3: 18 - 2 = 16
    • P4: 19 - 1 = 18
    • P5: 6 - 5 = 1
    • (6 + 0 + 16 + 18 + 1) / 5 = 8.2

우선순위 스케줄링은 기아 현상을 일으킬 수 있으며, 에이징 기법으로 해결할 수 있습니다.

선점 스케줄링 (Preemptive Scheduling)

선점 스케줄링에서는 우선순위가 높은 프로세스가 현재 프로세스를 중단시키고 CPU를 차지할 수 있습니다. 응답이 빠르지만, 오버헤드가 발생할 수 있습니다.

- Round Robin (RR)

Round Robin은 각 프로세스에 동일한 시간 슬라이스를 할당하여, 각 프로세스가 공평하게 CPU를 사용하는 방식입니다.

예시

  • 작업과 Burst Time:
    • P1: 10
    • P2: 5
    • P3: 8
  • 시간 슬라이스: 4
| P1 | P2 | P3 | P1 | P3 | P1 | 
0  4    8   12   16   20   24
 
  • 평균 반환 시간:
    • P1: 24
    • P2: 8
    • P3: 20
    • (24 + 8 + 20) / 3 = 17.33
  • 평균 대기 시간:
    • P1: 24 - 10 = 14
    • P2: 8 - 5 = 3
    • P3: 20 - 8 = 12
    • (14 + 3 + 12) / 3 = 9.67

- SRT (Shortest Remaining Time)

SRT는 SJF의 선점형 버전으로, 남은 실행 시간이 가장 짧은 프로세스를 우선적으로 처리합니다.

예시

  • 작업과 Burst Time:
    • P1: 10
    • P2: 5
    • P3: 8
| P2 | P1 | P3 | P1 | 
0  5   10   18   20
 
  • 평균 반환 시간:
    • P1: 20
    • P2: 5
    • P3: 18
    • (20 + 5 + 18) / 3 = 14.33
  • 평균 대기 시간:
    • P1: 20 - 10 = 10
    • P2: 0
    • P3: 18 - 8 = 10
    • (10 + 0 + 10) / 3 = 6.67

스레드의 종류

 

스레드의 구성 요소

  • 스레드 ID: 각 스레드의 고유 식별자입니다.
  • 프로그램 카운터: 스레드가 다음에 실행할 명령어의 주소를 가리킵니다.
  • 레지스터 세트: 현재 작업의 중간 결과를 저장하는데 사용됩니다.
  • 스택: 함수 호출, 지역 변수 등 임시 데이터를 저장하는데 사용됩니다.

이러한 구성 요소는 스레드가 독립적으로 실행되는데 필요한 최소한의 정보를 포함합니다.

다중 스레드 프로그래밍의 장점

다중 스레드 프로그래밍은 여러 스레드를 사용하여 하나의 프로세스 내에서 여러 작업을 동시에 처리하는 기법입니다. 이 접근 방식은 다음과 같은 여러 장점을 가집니다.

  • 응답성 향상: 하나의 스레드가 차단되어도, 다른 스레드가 계속 실행되어 사용자 인터페이스의 응답성을 높일 수 있습니다.
  • 자원 공유 용이: 스레드는 같은 프로세스의 자원을 공유하기 때문에, 프로세스 간 통신보다 데이터 공유가 쉽습니다.
  • 경제성: 스레드를 생성하고 전환하는 비용이 프로세스에 비해 훨씬 적습니다. 이는 시스템의 효율성을 높이는 데 기여합니다.
  • 확장성: 멀티코어 및 멀티프로세서 시스템에서 각 코어 또는 프로세서에 스레드를 할당하여 병렬 처리와 성능을 향상시킬 수 있습니다.

프로세스와 스레드는 모던 컴퓨팅 시스템의 기본 구성 요소입니다. 스레드를 효과적으로 사용함으로써, 개발자는 응답성이 높고 자원을 효율적으로 사용하는 멀티태스킹 응용 프로그램을 구현할 수 있습니다. 다중 스레드 프로그래밍은 소프트웨어의 복잡성을 증가시킬 수 있지만, 그로 인해 얻을 수 있는 성능과 유연성의 이점은 이를 상쇄합니다. 따라서 컴퓨터 공학에서 프로세스와 스레드에 대한 깊은 이해는 모든 개발자에게 필수적인 기술이라 할 수 있습니다.

 

 

멀티스레딩은 현대 프로그래밍에서 중요한 개념 중 하나입니다. 특히, 파이썬은 멀티스레딩을 손쉽게 구현할 수 있는 threading 모듈을 제공하여, 동시에 여러 작업을 수행할 수 있게 합니다. 이를 통해 사용자 경험을 향상시키고, 자원을 효율적으로 사용하며, 복잡한 작업을 더 빠르게 처리할 수 있습니다.

파이썬에서 스레드 생성하기

파이썬에서 스레드를 생성하는 기본 단위는 Thread 클래스입니다. 스레드를 사용하면 프로세스 내에서 독립적인 실행 흐름을 만들 수 있어, 여러 작업을 동시에 처리할 수 있습니다.

import threading 
def worker(): """스레드에서 실행될 작업""" 
    print("Worker 실행") # 스레드 생성 및 시작 
    t = threading.Thread(target=worker) t.start()

스레드 동기화: Lock 클래스

멀티스레딩 환경에서는 여러 스레드가 동시에 같은 데이터에 접근하려 할 때 문제가 발생할 수 있습니다. 이를 방지하기 위해 Lock 클래스를 사용하여 자원에 대한 동시 접근을 제어합니다.

lock = threading.Lock()

def worker_with_lock():
    with lock:
        # 임계 영역: 한 번에 하나의 스레드만 접근 가능
        print("Lock을 사용하는 Worker 실행")

멀티스레딩의 실제 예제

멀티스레딩은 네트워크 요청 처리, 파일 입출력, 대규모 데이터 처리 등 복잡한 작업을 효과적으로 관리할 수 있게 합니다. 다음은 간단한 멀티스레딩 예제입니다.

 

def worker(number):
    print(f"Worker {number} 실행")

threads = []
for i in range(5):
    thread = threading.Thread(target=worker, args=(i,))
    threads.append(thread)
    thread.start()

for thread in threads:
    thread.join()

 

 

위 예제에서는 5개의 스레드를 생성하여 각각의 스레드가 worker 함수를 실행합니다. 이를 통해 병렬적으로 다수의 작업을 처리할 수 있습니다.

멀티스레딩은 파이썬 프로그래밍에서 중요한 기술 중 하나이며, 이를 효율적으로 활용함으로써 프로그램의 성능을 향상시킬 수 있습니다. 하지만 스레드 간의 동기화와 관련된 주의사항을 잘 이해하고 적절히 처리하는 것이 중요합니다.

멀티스레딩(Multithreading)은 모던 프로그래밍에서 중요한 주제 중 하나로, 여러 작업을 동시에 수행하거나 병렬로 실행하여 프로그램의 성능을 향상시키는 기술입니다. 이를 효율적으로 활용하기 위해서는 동시성(Concurrency), 병렬성(Parallelism), 그리고 스레드 안전(Thread Safety)에 대한 이해가 필수적입니다.

동시성과 병렬성은 비슷해 보이지만 약간의 차이가 있습니다. 동시성은 여러 작업을 동시에 처리할 수 있는 능력을 의미합니다. 이는 하나의 프로세서에서 여러 작업이 번갈아가면서 실행되는 것을 의미합니다. 병렬성은 여러 작업이 실제로 동시에 실행되는 것을 말합니다. 이는 여러 개의 프로세서가 동시에 여러 작업을 처리하는 것을 의미합니다.

 

파이썬에서는 threading 모듈을 사용하여 멀티스레딩을 구현할 수 있습니다. 멀티스레딩은 주로 I/O 바운드 작업(입출력 작업이 많은 작업)에서 성능 향상을 가져옵니다. 이는 예를 들어 네트워크 요청, 파일 입출력 등이 해당됩니다. 멀티스레딩을 통해 이러한 작업들을 비동기적으로 처리함으로써 전체 프로그램의 성능을 향상시킬 수 있습니다.

 

그러나 파이썬의 GIL(Global Interpreter Lock) 때문에 멀티스레딩이 CPU 바운드 작업(계산 작업이 많은 작업)에서는 병렬성을 제대로 발휘하지 못하는 경우가 있습니다. GIL은 한 번에 하나의 스레드만 파이썬 바이트코드를 실행하도록 하는 제한입니다. 이러한 이유로 CPU 바운드 작업에서는 병렬성을 활용하기 위해서는 multiprocessing 모듈을 사용하여 프로세스 간 통신을 해야 합니다.

멀티스레딩을 구현할 때 스레드 안전성도 고려해야 합니다. 스레드 안전성은 여러 스레드가 동시에 하나의 자원에 접근할 때 발생할 수 있는 문제를 해결하는 것을 의미합니다. 이를 위해 뮤텍스(Mutex), 세마포어(Semaphore) 등의 동기화 기법을 사용하여 공유 자원에 대한 접근을 제어할 수 있습니다.

 

 

따라서 멀티스레딩을 이해하고 활용하기 위해서는 동시성과 병렬성의 개념을 명확히 이해하고, 스레드 안전성을 고려하여 안정적인 멀티스레드 프로그래밍을 구현할 수 있어야 합니다. 이를 통해 모던 프로그래밍에서 멀티코어 프로세서의 성능을 최대한 활용하여 더욱 효율적인 애플리케이션을 개발할 수 있습니다.