05의 개발 계발

[TIL] 230418 데이터 타입별 메서드 + 프로세스, 스레드 본문

TIL

[TIL] 230418 데이터 타입별 메서드 + 프로세스, 스레드

생각하는 코댕이 2023. 4. 18. 22:21
728x90

참고 깃허브


TIL 학습목표

  • 타입별(str list dict) 자주 사용되는 method의 용도와 사용법을 안다.
  • 프로세스와 스레드의 개념을 안다.
  • python에서의 멀티프로세싱과 멀티스레딩을 안다.

타입별 메서드

문자열(str)

#-------------------------- str --------------------------#

# count : 문자열 내에서 특정 문자가 몇 개나 있는지 세어주는 메서드
text = "안녕하세요! hellow! 05!"
count_k = text.count("안")
count_e = text.count("l")
count_n = text.count("5")
print(count_k) # 1 | 한글
print(count_e) # 2 | 영어
print(count_n) # 1 | 숫자

# fimd : 문자열 내에서 특정 문자열이 처음 나오는 위치를 찾아주는 메서드
# (없을 경우 -1 return)
text = "안녕하세요! hellow! 05!"
position1 = text.find("05")
position2 = text.find("5")
position3 = text.find("6")

print(position1) #15 | 05의 0을 추적
print(position2) #16 | 5을 추적
print(position3) #-1 | 6은 없다!

# index: 문자열 내에서 특정 문자열이 처음 나오는 위치를 찾아주는 메서드
# (없을 경우 ValueError)
text = "안녕하세요! hellow! 05!"
try:
    position = text.index("05")
    print(position) #15 | 05의 0을 추적
except ValueError:
    print("그런거 없눈뒈..?")
    
# join: 특정 문자열을 기준으로 다른 문자열들을 합쳐주는 메서드
# split과 기능적으로 반대
alp = ["a","b","c"]
joined_alp = ",".join(alp)
print(joined_alp) # "a,b,c"

# upper: 문자열 내의 모든 소문자를 대문자로 바꾸는 메서드
# lower: 문자열 내의 모든 대문자를 소문자로 바꾸는 메서드
text = "안녕하세요! hellow! 05!"
uppercase_text = text.upper()
print(uppercase_text) # "안녕하세요! HELLOW! 05!"

lovercase_text = text.lower()
print(lovercase_text) # "안녕하세요! hellow! 05!"

# replace: 문자열 내에서 특정 문자열을 다른 문자열로 바꾸는 메서드
text = "안녕하세요! hellow! 05!"
replaced_text = text.replace('05', '영오')
print(replaced_text) # "안녕하세요! hellow! 영오!"

# split: 문자열을 특정 문자를 기준으로 나누는 메서드(결과는 리스트 형태로 반환)
# join과 기능적으로 반대
text = "a,b,c"
alp = text.split(",")
print(alp) # ['a', 'b', 'c']

리스트(list)

#-------------------------- list --------------------------#

# len: 리스트의 길이를 반환하는 내장 함수
num = [1,2,3,4,5]
name = "영 오"
print(len(num)) # 5 | 리스트원소의 갯수
print(len(name)) # 2 | 문자열의 길이, 띄어쓰기도 카운트

# del: 리스트에서 특정 요소를 삭제하는 연산자
num = [1,2,3,4,5]
del num[3]
print(num) # [1, 2, 3, 5]

# append: 리스트의 맨 뒤에 새로운 요소를 추가하는 메서드
num = [1,2,3,4,5]
num.append(0)
print(num) # [1, 2, 3, 4, 5, 0]

# sort: 리스트를 오름차순으로 정렬하는 메서드
num = [4,5,1,2,3]
num.sort()
print(num) # [1, 2, 3, 4, 5]

# reverse: 리스트의 요소 순서를 반대로 뒤집는 메서드
num = [4,5,1,2,3]
num.reverse()
print(num) # [3, 2, 1, 5, 4] 

# index: 리스트에서 특정 요소의 인덱스를 반환하는 메서드
alp= ['a','b','c']
print(alp.index("b")) #1

# insert: 리스트의 특정 위치에 요소를 삽입하는 메서드
num = [1,2,3,4,5]
num.insert(4,0)
print(num) # [1, 2, 3, 4, 0, 5]

# remove: 리스트에서 특정 요소를 제거하는 메서드
num = [1,2,3,4,5]
num.remove(4)
print(num) # [1, 2, 3, 5]

# pop: 리스트에서 마지막 요소를 빼낸 뒤, 그 요소를 삭제하는 메서드
num = [1,2,3,4,5]
num.pop(3)
print(num) #[1, 2, 3, 5] | 위치를 정하면 해당위치 요소가 pop
num.pop()
print(num) #[1, 2, 3] | 타겟을 정하지 않으면 마지막요소가 pop

#count: 리스트에서 특정 요소의 개수를 세는 메서드
num = [1,2,3,4,5,5,5,4]
print(num.count(5)) #3

#extend: 리스트를 확장하여 새로운 요소들을 추가하는 메서드
num = [1,2,3,4]
num.extend([5,6,7,8])
print(num) # [1, 2, 3, 4, 5, 6, 7, 8]

# += 연산자를 사용해서도 구현할 수도 있다.
num = [1,2,3,4]
num += [5,6,7,8]
print(num) # [1, 2, 3, 4, 5, 6, 7, 8]

딕셔너리(dict)

#-------------------------- dict --------------------------#

#딕셔너리 초기화
empty_dict = {}

#초기화할 딕셔너리 만들기
my_dict = {"a":1,"b":2,"c":3}
print(my_dict)

# 쌍 추가
my_dict = {"a":1,"b":2,"c":3}
my_dict["d"] = 4
print(my_dict) # {'a': 1, 'b': 2, 'c': 3, 'd': 4}

# del: 딕셔너리에서 특정 요소를 삭제
my_dict = {"a":1,"b":2,"c":3}
del my_dict["c"]
print(my_dict) # {'a': 1, 'b': 2}

#딕셔너리에서 특정 Key에 해당하는 Value를 얻는 방법 (딕셔너리에 Key가 없는 경우, KeyError)
my_dict = {"a":1,"b":2,"c":3}
print(my_dict["b"]) #2

#keys: 딕셔너리에서 모든 Key를 리스트로 만들기
my_dict = {"a":1,"b":2,"c":3}
key_list = list(my_dict.keys())
print(key_list) # ['a', 'b', 'c']

# values: 딕셔너리에서 모든 Value를 리스트로 만들기
my_dict = {"a":1,"b":2,"c":3}
value_list = list(my_dict.values())
print(value_list) # [1, 2, 3]

# items: 딕셔너리의 모든 키와 값을 튜플 형태의 리스트로 반환
person = {"name":"영오", "age":30, "gender":"male"}
items = person.items()
print(items) #dict_items([('name', '영오'), ('age', 30), ('gender', 'male')])

# clear: 딕셔너리의 모든 요소를 삭제
person = {"name":"영오", "age":30, "gender":"male"}
person.clear()
print(person) # {}

# get: 딕셔너리에서 지정한 키에 대응하는 값을 반환 (딕셔너리에 Key가 없는 경우, None 반환)
person = {"name":"영오", "age":30, "gender":"male"}

name = person.get("name")
print(name) # 영오

email = person.get("email","없엉")
print(email) # 없엉

# in: 해당 키가 딕셔너리 안에 있는지 확인
person = {"name":"영오", "age":30, "gender":"male"}

print("name" in person) # True
print("email" in person) # False

프로세스와 스레드

▶프로세스 - 정의 // 실행중인 프로그램

윈도우 작업관리자에서 확인가능한 프로세스(실행중인프로그램)

* macOS Linux는  ps -ef  명령어로 확인가능


▶프로세스 - 종류

포그라운드 프로세스(foreground process)  | ex_앱, 브라우저 등
:사용자가 볼 수 있는 공간에서 실행되는 프로세스 
◆백그라운드 프로세스(background process) | ex_보안프로그램 등
:사용자가 볼 수 없는 공간에서 실행되는 프로세스
-사용자와 직접 상호작용이 가능한 백그라운드 프로세스 ex) 포그라운드 프로세스를 백그라운드화 했을 경우
-사용자와 상호작용 않고 정해진 일만 수행하는 백그라운드 프로세스 ex) 데몬, 서비스(window)


▶프로세스 - 프로세스 제어 블록(PCB)

◆프로세스 제어 블록(PCB) Process Control Block
: 프로세스는 CPU라는 한정적인 자원을 공유하기 때문에, 프로세스를 관리할 필요가 있다.
이를 위해 사용하는 자료구조가 프로세스 제어 블록(PCB)이다.
-프로세스 관련 정보 저장
-프로세싱 동안만 사용되는 상품의 태그 같은 기능
-프로세스 생성 시 커널 영역에 생성되었다가 종료시 폐기된다.

PCB가 담고 있는 대표적인 정보

  • 프로세스ID(=PID)
  • 레지스터 값
  • 프로세스 상태
  • CPU 스케줄링 정보
  • 메모리 정보
  • 사용한 파일과 입출력장치 정보

◆프로세스ID(=PID) : 특정 프로세스를 식별하기 위해 부여하는 고유번호

윈도우 작업관리자에서 확인가능한 프로세스 상태

◆프로세스 상태 : 프로세스의 현 상태
-입출력 장치를 사용하기 위해 기다리는 상태
-CPU를 사용하기 위해 기다리는 상태
-CPU 이용 중인 상태(실행중)
-실행 중 문제가 생긴 경우(응답없음)
... 
◆사용한 파일과 입출력장치 정보
: 할당된 입출력장치, 사용 중인(열린) 파일 정보

▶프로세스 - 메모리영역 (커널영역+사용자영역)

◆ 커널영역 : 운영체제(OS)가 프로세스를 관리하는 메모리 영역
PCB로 관리
◆ 사용자영역 : 사용자가 프로세스를 관리하는 메모리 영역 (대표적인 영역 4가지)
-스택영역 / 힙 영역 / 데이터 영역 / 코드 영역(=텍스트 영역)

 코드 영역(=텍스트 영역)

  • 실행가능한 코드,기계어로 이루어진 명령어 저장
  • 데이터가 아닌 CPU가 실행할 명령어가 담기기에 쓰기가 금지된 영역(read-only)

● 데이터 영역 ↔ 스택영역

  • 잠깐 썼다가 없앨 데이터가 아닌, 프로그램이 실행되는 동안 유지할 데이터 저장(스택 영역과 대조적)
  • ex) 전역변수 - scope(범위) 가 전역에 걸쳐 사용되는 변수 

힙 영역

  • 프로그램을 만드는 사용자, 즉 프로그래머가 직접 할당할 수 있는 저장공간
  • 크기가 가변적인 영역
  • 일반적으로 낮은주소 → 높은 주소로 할당

● 스택 영역 ↔ 데이터영역

  • 데이터가 일시적으로 저장되는 공간, 사용직후 데이터 폐기 (∴ 영역이 동적으로 변함)
  • 잠깐 쓰다가 말 값들이 저장되는 공간(데이터 영역과 대조적)
  • ex) 매개변수, 지역변수 - scope(범위)가 일부로 제한되어 사용되는 변수
  • 크기가 가변적인 영역
  • 일반적으로 높은주소 → 낮은 주소로 할당

▶스레드 // 프로세스 내 실행흐름의 구성요소

프로세스 실행중인 프로그램 프로세스의 실행이 다른 프로세스에 영향을 주지 않는다.
즉, 다른 프로세스는 실행된다.
프로세스to프로세스 자원을 공유하지 않는다 남남처럼 독립적으로 실행된다
스레드 프로세스 내 실행흐름의 구성요소 스레드의 실행이 다른 스레드에도 영향을 끼친다.
즉, 프로세스가 실행되지 않는다. 
스레드to스레드 프로세스의 자원을 공유한다 협력과 통신에 유리하다

 


멀티 프로세싱과 멀티스레딩

멀티 프로세싱(multiprocessing)

from multiprocessing import Process
import os
# os.system("cls")

# [참고] 매 실행마다 PID는 재할당 받기에 실행마다 다른 PID가 출력되는 것이 정상이다! 
# [참고] 또한 os.getpid는 시스템의 PID를 가져오는 것임으로 아래 두 경우 모두 parent PID는 동일하다 


# ============1. 동일한 작업을 하는 프로세스 생성=============

def foo():
    print('child process:', os.getpid())  # 자식프로세스의 PID값
    print('my parent is:', os.getppid())  # 부모프로세스의 PID값

# 자식프로세스는 얼마든지 생성 가능
if __name__ == '__main__':  # 현재 모듈(__name__)이 직접실행(__main__)될 때 조건문을 실행해
    print('parent same_process:', os.getpid())
    child1 = Process(target=foo).start()  # foo()를 실행하는 프로세스(child1~3)를 만들어서 실행해
    child2 = Process(target=foo).start()
    child3 = Process(target=foo).start()

'''결과
parent same_process: 45652
child process: 21772
child process: 4932
child process: 31768
my parent is: 45652
my parent is: 45652
my parent is: 45652
'''

# 시각적으로 실행 순서를 보기 위한 구분선
print("="*30)
'''
구분선이 여러개 생기는 것으로 보아
child process들은 각기 다른 process 과정임을 알 수 있고, 시스템 콜이 반복되어 자원이 낭비됨을 볼 수 있다.
구분선의 위치가 매 실행마다 바뀌는 것으로 보아
매실행마다 프로세스들의 실행속도 차이가 존재함을 알 수 있다.
'''



# ============2. 각기 다른 작업을 하는 프로세스 생성=============

def bar():
    print('This is bar:', os.getpid())

def baz():
    print('This is baz:', os.getpid())
    
def qux():
    print('This is qux:', os.getpid())


# 각기 다른 작업의 자식프로세스
if __name__ == '__main__':
    print('parent diff_process:', os.getpid()) 
    child4 = Process(target=bar).start()  
    child5 = Process(target=baz).start() 
    child6 = Process(target=qux).start()  

'''결과
parent diff_process: 45652
This is bar: 21780
This is baz: 31192
This is qux: 21388
'''

'''프로세스 테스트
의도적으로 bar()에 오타를 주어 error를 발생시켰을 때,
child4 가 error를 일으키며 프로세스가 도중에 멈춘다.
이는 process가 하나의 실행흐름을 갖기 때문이다.
'''

멀티 스레딩(threading)

import threading
import os
os.system("cls")

# [참고] 매 실행마다 PID는 재할당 받기에 실행마다 다른 PID가 출력되는 것이 정상이다! 
# [참고] 또한 os.getpid는 시스템의 PID를 가져오는 것임으로 아래 두 경우 모두 parent PID는 동일하다 


# ============1. 동일한 작업을 하는 스레드 생성=============

def foo():
    print('process id', os.getpid())
    print('thread id', threading.get_native_id())

if __name__ == '__main__':  # 현재 모듈(__name__)이 직접실행(__main__)될 때 조건문을 실행해
    print('same_process id', os.getpid())  
    thread1 = threading.Thread(target=foo).start()  # foo()를 실행하는 스레드(thread1~3)를 만들어서 실행해
    thread2 = threading.Thread(target=foo).start()
    thread3 = threading.Thread(target=foo).start()

'''결과 : 동일한 process를 공유하는 각기 다른 thread 이다.
same_process id 41160 
process id 41160
thread id 28592
process id 41160
thread id 16328
process id 41160
thread id 22820
'''


# 시각적으로 실행 순서를 보기 위한 구분선
print("="*30)
'''
구분선이 하나만 생기는 것으로 보아
thread들은 같은 process 과정임을 알 수 있고, 시스템 콜이 반복되지 않아 자원이 낭비되지 않음을 볼 수 있다.
구분선의 위치가 매 실행마다 바뀌는 것으로 보아
매실행마다 프로세스들의 실행속도 차이가 존재함을 알 수 있다.
'''

# ============2. 다른 작업을 하는 스레드 생성=============

def foo():
    print('This is foo', os.getpidㄴ())
    print('thread id', threading.get_native_ids())
    

def bar():
    print('This is bar', os.getpid())
    print('thread id', threading.get_native_id())
    

def baz():
    print('This is baz', os.getpid())
    print('thread id', threading.get_native_id())

if __name__ == '__main__':
    print('diff_process id', os.getpid()) 
    thread4 = threading.Thread(target=foo).start()  
    thread5 = threading.Thread(target=bar).start()  
    thread6 = threading.Thread(target=baz).start()  
    
'''결과
diff_process id 41160
This is foo 41160
thread id 22448
This is bar 41160
thread id 19828
This is baz 41160
thread id 18420
'''   

'''스레드 테스트
의도적으로 foo()에 오타를 주어 error를 발생시켰을 때,
thread4 가 error를 일으켜도 "Exception"하며 프로세스 내의 나머지 스레드를 실행한다.
이는 thread가 각기 다른 실행흐름을 갖기 때문이다.
'''

4) 알게 된 점  (Learnd)

알고있던 메서드들을 다시 정리해보며 타입별로 자유롭게 연결하려면 어떻게 해야할지 전체를 보는 시야가 생긴 것 같다.

프로세스와 스레드의 개념을 배웠다.
멀티프로세싱과 멀티스레드는 한번의 실행으로 여러가지 동작을 실행할 수 있다는 것이 정말 매력적이었다.
개념적으로는 알겠으나, 아직 실전에서 어떻게 활용하고 또 어떻게 디버깅과정을 가져야할지 감이 오지 않는다.
실제로 활용해보는 기회를 만들도록 하자.

728x90