본문 바로가기

미래 먹거리를 위하여

[파이썬 정복하기] 라이브러리 12장 - 동시에 실행하기1 (맥OS 기준) — 파이썬 안에서 직접 병렬 처리하기 threading, multiprocessing

OS: MAC

참고: 👉 점프 투 파이썬 - 라이브러리 예제 바로가기

066. 스레드를 이용하여 병렬로 처리하려면? ― threading(쓰레딩)

구분 설명 예시 결과
스레드(Thread)란? 하나의 프로세스 안에서 동시에 여러 작업을 수행하도록 만들어진 실행 흐름 단위 여러 작업을 하나씩 처리하면 느린데,
스레드를 쓰면 동시에 실행되는 것처럼 보임
I/O 중심 작업에서 속도 개선
threading 모듈 소개 파이썬에서 스레드를 만들고 관리하는 기본 모듈 import threading 스레드를 쉽게 생성하고 시작할 수 있음
스레드
생성하기
Thread(target=함수명) 형태로
스레드를 만들고 start()로 실행
import threading
def worker():
      print("작업 중...")
th = threading.Thread(target=worker)
th.start()

작업 중...

# 메인 흐름과 동시에 실행
인자
전달하기
Thread(target, args=(값,))
방식으로 함수에 인자 전달 가능
import threading
import time

def worker(n):
    print(f"{n}초 작업 시작")
    time.sleep(n)
    print(f"{n}초 작업 끝!")

thread = threading.Thread(target=worker, args=(3,))
thread.start()
# 스레드 함수가 전달받은 값으로 동작

3초 작업 시작
>>> 3초 작업 끝!

여러
스레드 실행
반복문을 이용하여
여러 개의 스레드 생성
import threading
import time

def worker(n):
    print(f"{n}번 작업 시작")
    time.sleep(1)
    print(f"{n}번 작업 끝")

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

for t in threads:
    t.join()
# 3개의 스레드가 동시에 실행

0번 작업 시작
1번 작업 시작
2번 작업 시작
0번 작업 끝
2번 작업 끝
1번 작업 끝

join() 모든 스레드가
끝날 때까지 기다리는 기능
import threading
import time

def worker(n):
    print(f"{n}번 작업 시작")
    time.sleep(1)
    print(f"{n}번 작업 끝")

threads = []

for i in range(3):
    t = threading.Thread(target=worker, args=(i,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print("모든 작업 완료!")
# 메인 프로그램이
스레드 종료까지 대기

0번 작업 시작
1번 작업 시작
2번 작업 시작
2번 작업 끝
0번 작업 끝
1번 작업 끝
모든 작업 완료!

스레드 vs 프로세스 스레드는 메모리를 공유하기 때문에 가볍지만,GIL 영향으로 CPU 작업에는 비효율적 CPU 작업은 multiprocessing 사용 권장 I/O 작업에서 강함
스레드 안전 문제(lock) 여러 스레드가 같은 데이터 접근 시 충돌 가능 → Lock() 필요 import threading
import time

# 공유 자원
counter = 0
lock = threading.Lock()

def worker():
    global counter
    for _ in range(100000):
        with lock:    # 🔒 락을 획득한 상태에서만 counter 변경
            counter += 1

threads = []

# 스레드 3개 실행
for i in range(3):
    t = threading.Thread(target=worker)
    t.start()
    threads.append(t)

# 모든 스레드를 기다림
for t in threads:
    t.join()

print("최종 counter 값:", counter)
# 데이터 꼬임 방지

최종 counter 값: 300000

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 12장 66번 문제

 

문제를 푸는데 오류가 난다. 이전에 작성했던 파일 이름이 충돌해서 그렇다고 한다.

import urllib.request 안에서 내부적으로 import hashlib 를 하는데
→ 원래는 파이썬 표준 라이브러리 hashlib 를 불러야 하는데
내가 만든 hashlib.py가 먼저 잡혀버림 → 그래서 AttributeError가 발생 

 

이것 먼저 해결하자.

1) 내가 만든 hashlib.py의 파일명을 my_hashlib.py로 변경해준다.

2) __pycache__ 폴더 내 hashlib.cpython-313.pyc 파일도 삭제한다.

3) 파이썬 셸을 완전히 끈다.

4) 터미널 > 파이썬 셸을 킨다. 

5) 아래 코드를 입력한다. 아무것도 안나와야 문제를 풀 수 있다.

import urllib.request, urllib.error

점프 투 파이썬 - 라이브러리 예제 편 12장 66번 문제 풀이 및 결과

067. 멀티 프로세스를 이용하여 병렬로 처리하려면? ― multiprocessing

- 멀티프로세싱은 REPL(대화형 셸)보다는 반드시 .py 파일로 만들어서 터미널에서 실행하는 방식이 가장 안정적.

구분 설명 예시 결과
멀티프로세싱이란? 하나의 프로그램 안에서 프로세스를 여러 개 만들어서 동시에 작업을 처리하는 방법. GIL의 영향을 받지 않아서 CPU를 빡세게 쓰는 작업에 유리함. 큰 리스트에서 소수 판별, 행렬 계산, 이미지 변환 등 코어 여러 개를 활용해서 속도 향상
기본 모듈 multiprocessing 모듈을
사용하여 프로세스를 만들고 제어함.
from multiprocessing import Process Process(target=함수) 형태로 프로세스를 생성
Process
사용
Process(target=함수, args=(인자,))
로 프로세스를 만들고
start()로 실행, join()으로 종료까지 대기
# mp_test.py로 생성

from multiprocessing import Process
import time

def worker(n):
    print(f"{n} 작업 시작")
   time.sleep(1)
   print(f"{n} 작업 끝")

if __name__ == "__main__":
   p = Process(target=worker, args=(3,))
   p.start()
   p.join()

# 터미널에서 이 파일 실행:
python mp_test.py
3 작업 시작
3 작업 끝
여러
프로세스
실행
리스트에 모아 두고
반복문으로 start(),
이후 다시 반복문으로 join()
# mp_test2.py로 생성
from multiprocessing import Process
import time

def worker(n):
    print(f"{n} 작업 시작")
    time.sleep(1)
    print(f"{n} 작업 끝")

if __name__ == "__main__":
    nums = [1, 2, 3, 4]
    processes = []

    for n in nums:
        p = Process(target=worker, args=(n,))
        p.start()
        processes.append(p) 

    for p in processes:
        p.join()

    print("모든 작업 완료")

# 터미널에서 이 파일 실행:
python mp_test2.py
# 여러 CPU 코어에서 동시에 worker 실행

1 작업 시작
2 작업 시작
4 작업 시작
3 작업 시작
1 작업 끝
4 작업 끝
2 작업 끝
3 작업 끝
모든 작업 완료



메인 가드
필요
맥·윈도우에서는 프로세스 생성 코드가 반드시
if __name__ == "__main__" 안에 있어야 함. 
if __name__ == "__main__":
    main()
안 그러면 프로세스가
무한 생성되거나 에러 발생.
프로세스 간 데이터 공유 프로세스는 메모리를 공유하지 않음. 값을 주고받으려면 Queue, Pipe, Manager 등을 사용해야 함. # mp_test3.py로 생성
from multiprocessing import Process, Queue

def worker(q, n):
   q.put(n*n)

if __name__ == "__main__":
   q = Queue()
  nums = [1, 2, 3]
  procs = []

  for n in nums:
        p = Process(target=worker, args=(q, n))
        p.start()
        procs.append(p)

  for p in procs:
        p.join()

        while not q.empty():
                  print(q.get())

# 터미널에서 이 파일 실행:
python mp_test3.py
# 각 프로세스가 큐를 통해 메시지/데이터 주고받음

1
4
9

Pool을 이용한 작업 분배(동시작업) 같은 함수를 여러 데이터에 적용할 때는 Pool이 편리함. 프로세스 풀을 만들고 map, apply 등 사용. # mp_test4.py로 생성
from multiprocessing import Pool
import time

def square(n):
       time.sleep(1)
        return n * n

if __name__ == "__main__":
   nums = [1, 2, 3, 4]
  
    with Pool(4) as pool:
            results = pool.map(square, nums)

    print(results)
# 리스트 nums의 각 원소에 대해 worker가 병렬 실행

[1, 4, 9, 16]

쓰레드와
차이
threading은 메모리 공유, GIL 때문에 CPU 작업엔 제한적.
multiprocessing은 프로세스별 독립 메모리 + GIL 우회.
대신 생성/통신 비용이 더 큼.
CPU 바운드 → multiprocessing / I/O 바운드
→ threading
올바른 선택으로 성능 최적화

라이브러리 예제 문제: 

점프 투 파이썬 - 라이브러리 예제 편 12장 67번 문제
점프 투 파이썬 - 라이브러리 예제 편 12장 67번 문제 풀이 및 결과