파이썬으로 멀티프로세싱 vs 멀티 스레딩

조회수 16795회

멀티프로세싱이랑 멀티 스레딩 중 어떤 걸 쓰는 게 더 좋은가요? 멀티프로세싱은 GIL을 안 써도 된다고 하던데 이 외에 다른 점이 있나요?

둘의 차이점이랑 장단점을 알려주세요

1 답변

  • 좋아요

    2

    싫어요
    채택 취소하기

    threading은 스레드를 여러개, multiprocessing은 프로세스를 여러개 쓰는걸 의미하고

    하나의 프로세스 안에는 여러개 스레드가 있을 수 있기 때문에 프로세스가 스레드보다 더 큽니다.

    스레드는 프로세스 내에서 스레드끼리 같은 메모리 공간을 쓰고, 프로세스는 서로 다른 메모리 공간을 씁니다.

    따라서 멀티프로세싱을 쓰는 경우 프로세스 간에 같은 객체를 공유하는 게 어렵습니다 항상 객체를 공유하는 게 좋지만은 않은데요, 동시에 여러 개의 스레드가 같은 자원을 쓸 때는 race condition이 발생해 예상치 못한 결과가 날 수 있습니다.

    예를 들어 리스트에 원소를 하나씩 append해 mylist = [1,2,3,...,300]을 만들려고 할 때, 스레드를 여러 개 만들어 속도를 높이는 코드를 다음과 같이 만들었다고 하면

    import thread, time
    mylist = [[0,1]]
    
    def listTo300Elem(id):
        while len(mylist) < 300: #원소가 300개 이상이 되면 멈춤
            mylist.append([id, mylist[-1][1]+1]) #어느스레드가 append했는지 마크
    
    thread.start_new_thread(listTo300Elem, (1,)) #스레드 id 1
    thread.start_new_thread(listTo300Elem, (2,)) #스레드 id 2
    thread.start_new_thread(listTo300Elem, (3,)) #스레드 id 3
    thread.start_new_thread(listTo300Elem, (4,)) #스레드 id 4
    thread.start_new_thread(listTo300Elem, (5,)) #스레드 id 5
    thread.start_new_thread(listTo300Elem, (6,)) #스레드 id 6
    thread.start_new_thread(listTo300Elem, (7,)) #스레드 id 7
    
    time.sleep(5) #충분히 기다려줌
    print mylist
    

    결과 :

    [[0, 1], [1, 2], [1, 3], [1, 4], [1, 5], ..., [7, 222], [7, 223], [3, 224], [3, 225], [3, 226], [3, 227], [3, 228], [3, 229], [3, 230], [3, 231], [3, 232], [3, 233], [3, 234], [3, 235], [3, 236], [3, 237], [3, 238], [3, 239], [3, 240], [3, 241], [3, 242], [3, 243], [3, 244], [3, 245], [3, 246], [3, 247], [3, 248], [6, 249], [6, 250], [6, 251], [6, 252], [6, 253], [6, 254], [6, 255], [6, 256], [6, 257], [6, 258], [6, 259], [6, 260], [6, 261], [6, 262], [6, 263], [6, 264], [6, 265], [6, 266], [6, 267], [6, 268], [6, 269], [6, 270], [6, 271], [6, 272], [6, 273], [6, 274], [6, 275], [6, 276], [6, 277], [6, 278], [6, 279], [6, 280], [6, 281], [6, 282], [6, 283], [6, 284], [6, 285], [6, 286], [6, 287], [6, 288], [6, 289], [6, 290], [6, 291], [6, 292], [6, 293], [6, 294], [6, 295], [6, 296], [6, 297], [6, 298], [3, 299], [3, 300], [7, 224], [6, 225], [1, 107]]

    예상대로라면 마지막 원소는 [어떤 id, 300]이 돼야 하지만 300이 아니라 107이 나왔습니다. 심지어 이 코드는 실행할 때마다 다른 결과가 나옵니다.

    이렇게 실행할 때마다 다른 결과를 내는 이유는, thread가 같은 자원을 동시에 접근하기 때문입니다. thread7mylist에 접근해 [7,223]을 출력한 후 block되어 [7, 224]을 출력하기 전, 그 사이에 다른 스레드들이 mylist에 접근해 mylist의 값을 바꿔버립니다. 하지만 thread7은 더 이상 233이 마지막 원소가 아닌걸 모르기 때문에 [7, 224]를 출력한 후 mylist의 길이가 300이상이므로 함수를 종료합니다.

    이렇듯 동시에 같은 메모리를 쓰는 상황은 문제가 발생할 수 있기 때문에 이런 상황을 방지하기 위해 자원에 접근을 제한해주는 GIL(global interpreter lock)이 필요합니다.

    제가 생각하는 둘의 장단점은

    멀티프로세싱

    장점

    • 메모리를 공유하지 않음
    • 코드 흐름이 명확함
    • 멀티코어/CPU 의 장점을 쓸 수 있음
    • shared memory를 쓰지 않는 이상 동기화가 필요 없음
    • 자식 프로세스를 interrupt/kill할수 있음
    • 파이썬의 multiprocessing모듈이 제공하는 다양한 interface기능

    단점

    • IPC(Inter Process Communication)에서 오버헤드가 좀 더 큼
    • 메모리가 더 많이 필요함

    스레딩

    장점

    • 메모리가 적게 필요함
    • 메모리를 공유함 - 서로 상태를 공유하기 쉬움
    • GIL을 이용해 병렬 처리가 가능
    • I/O bound 어플리케이션에 옵션이 많음

    단점

    • interrupt/kill 할 수 없음
    • command queue/message pump 모델을 따르지 않는 경우 동기화해야 함
    • race condition이 발생할 수 있음

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)