Multiprocessing Python là gì

Trước đây, mình từng là một tay mơ không biết cách làm sao để 1 tác vụ nào đó có thể chạy nhanh hơn. Lúc đó thì có học các môn liên quan đến nào là tạo group chat, bài toán 5 triết gia,... bằng việc áp dụng luồng.

Tuy nhiên thì vẫn "nước đổ đầu vịt" chả hiểu mịa gì =]]. Đi làm cũng hay nghe Multithread các kiểu rồi nghĩ nó được áp dụng để các tác vụ chạy song song, từ đó làm tăng tốc độ tính toán. Và đương nhiên đó là một nhận định sai bét tè lè nhè 🤕. Đã vậy sau đó tự nhiên đập vào mắt chữ Multiprocessing rồi nghĩ nó cũng như Multithread thế là cứ tìm hiểu so sánh thằng nào hot và nhanh hơn mà phang =]].

Sau khi có thời gian tìm hiểu ra những điều thú vị tiềm ẩn đằng sau thì òa, mặt trời chân lý chói qua tim 🤓.

Bài viết dưới đây bao gồm các nhận định cá nhân dựa việc tổng hợp các kiến thức từ google, có vấn đề gì mong các bạn góp ý nha!!

1. Multithread

Hãy từ bỏ ngay nếu có suy nghĩ  Multithread được sử dụng để tăng tốc độ tính toán. Bởi vì:

  • Multithread xử lý các tác vụ một cách đồng thời chứ không phải song song
  • Không những tốn thời gian xử lý gần như là theo xử lý tuần tự mà còn tốn thời gian switch giữa các luồng

Ta hãy đi qua một ví dụ để xem xét điều trên:

Define một số function như sau

import threading
import time


def process1[]:
    start_time = time.time[]
    for i in range[100000]:
        for j in range[10000]:
            pass
    end_time = time.time[]
    print[f'Thread 1: ', end_time - start_time]


def process2[]:
    start_time = time.time[]
    for i in range[100000]:
        for j in range[10000]:
            pass
    end_time = time.time[]
    print[f'Thread 2: ', end_time - start_time]

Với hàm main chạy mà không sử dụng Multithread như bên dưới. Tổng thời gian chạy chương trình sẽ là 32.41 giây

def main[]:
    start_time = time.time[]
    process1[]
    process2[]
    end_time = time.time[]
    print['Total', end_time - start_time]

Tiếp theo chúng ta sẽ sử dụng Multithread để chạy 2 function process1process2. Thử đoán xem nó sẽ nhanh hơn hay chậm hơn nào.

def main[]:
    start_time = time.time[]
    thread1 = threading.Thread[target=process1]
    thread2 = threading.Thread[target=process2]
    thread1.start[]
    thread2.start[]

    threads = [thread1, thread2]
    for t in threads:
        t.join[]

    end_time = time.time[]
    print['Total', end_time - start_time]

Và đương nhiên, kết qủa của việc sử dụng Multithread là tận 52.05 giây. Wow, một cách biệt không hề nhỏ. Lý do là việc swicth liên tục đã làm tăng thời gian này lên rất là nhiều.

Vậy, Multithread là phế? Không hề, đó là do bạn chơi đồ thôi. Vì nó được sinh ra với mục tiêu đó là: Hữu ích cho các quy trình liên kết IO như đọc tệp từ mạng hoặc cơ sở dữ liệu vì mỗi luồng có thể chạy quy trình liên kết IO đồng thời.

Usually this is because somewhere between you and the other server there's a firewall limiting each HTTP stream to 10Mbps. When you use multi-thread, you get 2x 10Mb [one for each thread].

Để làm rõ điều này, bạn có thể chạy đoạn mã tại đây. Kết qủa cho thấy với tác vụ IO, càng nhiều luồng  thì Multithread càng nhanh, bình thường phải tốn đến 7.86 giây thì giờ chỉ tốn 2.55, 1.11, 0.62 giây [tương đương với sử dụng 4, 8, 16 luồng].

2. Multiprocessing

Khi nói đến tăng tốc độ tính toán, ta phải nghĩ ngay đến Multiprocessing, vì:

  • Multiprocessing đề cập đến khả năng chạy nhiều bộ xử lý đồng thời [mỗi bộ xử lý có thể chạy 1 hoặc nhiều luồng]
  • Hữu ích cho các quy trình có sự ràng buộc của CPU , chẳng hạn như các tác vụ nặng về tính toán vì nó sẽ được hưởng lợi từ việc có nhiều bộ xử lý; tương tự như cách máy tính đa lõi hoạt động nhanh hơn máy tính có lõi đơn.

Với việc thay đổi hàm main ở phần 1 bằng cách thêm vào Multiprocessing, ta sẽ thấy rằng nó chỉ tốn 19.59 giây để hoàn thành [bình thường là 32.41 giây, Multithread là 52.05 giây], rất nhanh phải không

def main[]:
    print['Main Process start']
    start_time = time.time[]
    processlist = []

    p1 = Process[target=process1]
    p1.start[]
    processlist.append[p1]

    p2 = Process[target=process2]
    p2.start[]
    processlist.append[p2]

    for i in processlist:
        i.join[]
    end_time = time.time[]
    print['Total ', end_time - start_time]

Đó là khi chúng ta chạy với 2 Core, ví dụ chạy với nhiều core hơn thì sao?

Chúng ta sẽ tách function process1 [tương tự với process2] như sau để chạy 4 Core:

def temp[n, m]:
    for i in range[n]:
        for j in range[m]:
            pass


def process1[]:
    start_time = time.time[]
    processlist = []

    p1 = Process[target=temp, args=[50000, 10000,]]
    p1.start[]
    processlist.append[p1]

    p2 = Process[target=temp, args=[50000, 10000,]]
    p2.start[]
    processlist.append[p2]

    for i in processlist:
        i.join[]

    end_time = time.time[]
    print['Process 1: ', end_time - start_time]

Kết qủa là chỉ

def main[]:
    start_time = time.time[]
    process1[]
    process2[]
    end_time = time.time[]
    print['Total', end_time - start_time]
1 giâyyy.

Chẳng lẽ Multiprocessing là soái ca chính hiệu 🤪. Không đâu, việc quản lý luồng thì dễ hơn với các bài toán nổi tiếng như 5 triết gia ăn tối. Tuy nhiên việc quản lý và tạo các process sao cho hợp lý trong Multiprocessing lại là vấn đề lớn.

Ví dụ trên server đã sử dụng 15/20 Core, trong đó 6 Core là của bạn, thế mà giờ bạn thích là chia nhỏ chương trình của bạn ra thêm nhiều process, kết qủa lên đến 12 Core -> Server tăng thêm 6 Core là 21/20 Core -> Server tèo.

Thêm nữa việc tạo ra thêm các process cho 1 số tác vụ rất là nhẹ thì rõ ràng nó rất stupid. Ví dụ như process1, nếu đổi vòng for đầu tiên từ 100000 thành chỉ có 10 loop -> Chỉ tốn

def main[]:
    start_time = time.time[]
    process1[]
    process2[]
    end_time = time.time[]
    print['Total', end_time - start_time]
3 giây, vậy mà bạn lại đi tạo nguyên 1 process, cung cấp 1 Core cho nó. Người ta còn gọi là Giết kiến dùng dao mổ voi.

Thêm vào đó, từ hình bên dưới bạn có thể thấy rằng:

  • Multithread sẽ có nhiều luồng chia sẻ cùng một code, data và files nhưng chạy trên một thanh ghi và ngăn xếp khác nhau.
  • Multiprocessing thì lại copy code, data và files ra mỗi nơi riêng lẻ -> Phát sinh nhiều bộ nhớ hơn.

3. Kết luận

  • Đa xử lý là song song. Đa luồng là đồng thời.
  • Đa xử lý là để tăng tốc độ. Đa luồng là để ẩn độ trễ.
  • Đa xử lý là tốt nhất cho tính toán. Đa luồng là tốt nhất cho IO.
  • Khi sử dụng Multiprocessing, cần lựa chọn số Core phù hợp

4. Tham khảo

Multiprocessing vs Threading Python

I am trying to understand the advantages of multiprocessing over threading. I know that multiprocessing gets around the Global Interpreter Lock, but what other advantages are there, and can threadi...

Stack OverflowJohn

Multithreading vs. Multiprocessing in Python

Revealing the true face of MultithreadingIn this article, I will try to discuss some misconceptions about Multithreading and explain why they are false.

Chủ Đề