프로세스와 스레드

5 분 소요

프로그램의 실행과정

프로그램: 컴퓨터를 실행시키기 위해 작성된 명령어들의 집합

프로세스: 프로그램이 실행되어 컴퓨터가 돌아가고 있는 상태

실행 과정

  1. 전처리기에 의한 소스코드 치환작업
  2. 소스코드를 컴파일하여 어셈블리어로 변환
  3. 어셈블러가 명령어를 머신코드(바이너리 코드)로 변환
  4. 링커가 어슬블러를 통해 만든 코드를 결합하여 하나의 실행파일을 만든다
  5. 만들어진 실행파일을 RAM에 저장한다
    • Stored Program Computer (a.k.a. 폰 노이만 아키텍처)
      • 프로그램 명령어를 전자식 기억장치(RAM)에 저장하는, 프로그램 내장 방식 컴퓨터
      • CPU, 메모리, 프로그램 세 가지 요소로 구성되어 있다
  6. 메인 메모리에 저장되어 있는 명령어를 CPU 내부로 가져온다 - Fetch
    • 저장된 코드를 다음에 실행할 명령어의 주소를 가지고 있는 PC(Program Counter)가 주소로 가서 명령어를 읽어들여 명령어 레지스터(IR, Instruction Register)에 저장한다
      • Register: CPU 내부의 임시기억 장치. PC, IR, MAR(메모리 주소 레지스터), MBR(메모리 버퍼 레지스터) 등이 있다
  7. CPU의 제어장치(CU, Control Unit)에 의해 fetch 된 코드를 분석한다 - Decode
  8. 분석한 명령어를 제어장치가 연산장치(ALU, Arithmetic Logic Unit)로 전달하면 명령어를 명령대로 실행한다 - Execute

멀티태스킹

유니태스킹: 한 순간에 한 가지 프로세스만 진행 (예, DOS 운영체제 - 한 번에 한 가지 커맨드(명령)만 처리가능)

멀티 태스킹: 한 순간에 여러 프로세스를 진행. 하나의 프로세스를 task 단위로 쪼개서 구현한다.

  • CPU 하나가 여러 작업을 돌아가면서 처리하는 동시성처리(Concurrency)가 있고 CPU 여러개가 각각 작업을 나눠서 처리하는 병렬처리(Parallelism) 방식이 존재. (여기에서는 동시성처리를 중심으로 설명)
  • OS 안에는 스케줄러라는 모듈이 존재하는데, 스케줄러는 어느 순간에 어떤 task를 실행할지 우선순위를 결정해주고 이에 따라 작업을 진행한다.
    • 커널(Kernel): OS 안에서 중심이 되는 모듈들을 가지고 있는 부분으로 스케줄러도 커널에 속해있다.
  • 하나의 task에는 실행가능한 시간이 지정되어있고 이를 ‘퀀텀/타임슬라이스’라고 한다.
  • 실행시간을 넘으면 task가 끝나지 않았더라도 다음 task로 넘어가는 식으로해서 여러 프로세스가 동시에 처리되는 것 처럼 보이게 하는데 이를 context switching 이라고 한다

스레드(Thread)

멀티태스킹을 통해 한 번에 여러개의 프로세스를 진행할 수 있게 되었다면, 프로세스 안에서도 동시에 여러 작업을 진행해야할 필요가 있다. 예를 들어 웹브라우저에서는 동시에 여러 탭을 열 수도 있어야 하고, 유튜브에서 동영상을 다운받는 동시에 재생할 수도 있다.

이렇게 한 프로세스 안에서 동작되는 실행의 단위, 혹은 작업을 수행하는 주체를 스레드(thread)라고 한다. 그리고 하나의 프로세스에서 여러 개의 스레드를 병행적으로 처리하는 것을 멀티스레드라고 한다.

특히 온라인 게임이나 채팅 프로그램처럼 사용자의 input과 네트워크로부터 전송되는 input 2개를 동시에 병렬적으로 처리해야하는 네트워크 프로그래밍에서 멀티스레딩이 반드시 필요하다.

파이썬 멀티스레딩

스레드 생성

th = threading.Thread(target=함수지정, arg=튜플)

  • target: 이 스레드가 실행할 코드를 함수로 만들어서 파라미터로 지정
  • arg: target으로 지정한 함수가 파라미터가 있다면 arg에 그 인자를 튜플형태로 전달

스레드 시작

th.start(): 스레드를 ready 상태로 만듦. 스레드를 실질적으로 시작시켜주는 것은 스케줄러다.

스레드 대기상태로 변경

time.sleep(t): 실행중인 스레드를 t시간만큼 대기(wait) 상태로 만듦. t시간이 끝나면 다시 ready 상태로 돌아가고, 스케줄러에 의해 실행 순서가 돌아오면 cpu를 사용을 할당받아 다시 run 상태가 된다.

스레드를 이용한 출력

스레드로 인해 f( ) 함수 실행과 s의 출력이 순차적으로 일어나지 않고 동시에 진행된다

import threading, time

def f():
    for i in range(0, 26):  #0부터 25까지 출력하는 함수
        print('th:',i)

def main():
    th = threading.Thread(target=f)  #스레드 생성
    th.start()  #스레드 시작 - 함수 f() 실행을 시작한다
    s = 'abcdefghijklmnopqrstuvwxyz'
		for i in s:
        print('main:', i)

main()

결과:

th:main: a
main: b
main: c
main: d
main: 0
th: 1
th: 2 e
main: f
main:
g
main: h
main: i
main: j
main:th: 3
th: 4
th: 5
th: 6 k
main: l
main: m
main:
th: 7
th: 8 n
main: o
main: p
th: 9
th:
main: q
main: r
10main: s
main: t
main: u
main:
th: 11
th: 12
th: 13
th: 14
th: 15
th: 16v
main: w

main:th: 17
th: 18
th: 19
th: 20
th:  x
main: 21y

th:main: z 22
th: 23
th: 24

th: 25


 

위의 예시에서는 출력이 스케줄러에 의한 처리가 랜덤하게 일어났지만, time.sleep(t)을 사용해 태스크 사이에 텀을 주면 스레드가 강제로 대기 상태로 있게되고 비교적 서로 교차하며 실행하게 된다

def f2():
    for i in range(0, 26):  #0부터 25까지 출력하는 함수
        print('th:',i)
        time.sleep(0.2)

def main():
    th = threading.Thread(target=f2)  #스레드 생성
    th.start()  #스레드 시작 - 함수 f() 실행을 시작한다
    s = 'abcdefghijklmnopqrstuvwxyz'
    for i in s:
        print('main:', i)
        time.sleep(0.1)

main()

결과:

th: 0
main: a
main: b
th: 1
main: c
main: d
th: 2
main: e
main: f
th: 3
main: g
main: h
th: 4
main: i
main: j
th: 5
main: k
main: l
th: 6
main: m
main: n
th: 7
main: o
main: p
th: 8
main: q
main:th: r
 9
main: s
th: 10
main: t
main: u
th: 11
main: v
main: w
th: 12
main: x
main: y
th: 13
main: z


 

2개 이상의 스레드 진행시키기

def f1(num):
    for i in range(0,25):
        print('th'+str(num)+':',i)
        time.sleep(1)

def f2():
    s = 'abcdefghijklmnopqrstuvwxyz'
    for i in s:
        print('th4:', i)
        time.sleep(1)

def main():
    th1 = threading.Thread(target=f1, args=(1,))
    th1.start()
    th2 = threading.Thread(target=f1, args=(2,))
    th2.start()
    th3 = threading.Thread(target=f1, args=(3,))
    th3.start()
    th4 = threading.Thread(target=f2)
    th4.start()
    for i in range(100, 126):
        print('main:', i)
        time.sleep(1)

main()

결과:

th: 14
th1: 0
th2: 0
th3: 0
th4: a
main: 100
th: 15
th: 16
th: 17
th: 18
th2:th1: 1
 1
main:th4: 101
th:th3:  b
 19
1
th: 20
th: 21
th: 22
th: 23
th1:th2: 2
 2
main: 102
th:th3:  2
th4: c
24
th: 25
th1:th2: 3
 3
main: 103
th4:th3: d
 3
th2:th1: 4
 4
main: 104
th3:th4:  e
4
main:th2: 105
 5
th1: 5
th4:th3: f
 5
th1:th2:main: 106
 6
 6
th3:th4: g
 6
th2:main: 107
 7
th1: 7
th3:th4: 7
 h
th1:main:th2: 8
 108
 8
th4:th3:  8
i
th1:main:th2: 9
 9
 109
th4:th3: j
 9
th1:main: 110
th2:  10
10
th3:th4:  10
k
th1:main: 111 11

th2: 11
th3:th4: l
 11
main:th1: 112
 12
th2: 12
th3:th4: 12
 m
main:th1: 13
 113
th2: 13
th3:th4: n
 13
main:th1: 14
 114
th2: 14
th3:th4: o
 14
main:th1: 15
 115
th2: 15
th3:th4:  15
p
main:th2: 116
 16
th1: 16
th3:th4: q 16

th1:main: 17
 th2:117
 17
th4:th3: r
 17
th1:th2: 18
main: 118
 18
th3:th4: 18
 s
main:th1: 19th2: 19

 119
th3:th4:  19
t
main:th1: 20
th4: u
th2: 20
th3: 20
 120
th3:th4:main: 121
th1: th2: v
21
  2121

th1:th3:th4: 22 22main: 122
 th2: 22

w

th1:th3:main: 23
 23
th4: xth2:  23

123
th1:th2: 24
th4: th3: 24
24
main: 124
 y
main:th4: 125
 z


  다른 방식의 2개 작업을 동시 실행

  • 작업1은 키보드 입력 받아서 입력 받은 메시지 출력 (10번)
  • 작업2는 리스트 s에서 문자열 1초마다 1개씩 읽어서 출력
def msg():
    for i in range(0,10):
        msg = input('input:')
        print(f'msg{i+1}:{msg}')

def sread():
    s = ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg', 'hhh', 'iii', 'jjj']
    for i in s:
        print(f'th: {i}')
        time.sleep(2)

def main():
    th1 = threading.Thread(target=msg)
    th1.start()

    th2 = threading.Thread(target=sread)
    th2.start()

main()

댓글남기기