Multithreading và multiprocessing trong python [P1]

Stack chính của tôi là nodejs/php nhưng nhân vào 1 ngày đẹp trời sau vài tháng nằm dài chơi ở nhà, tôi đã có offer vào 1 công ty đang dùng python. Vì thế, trước khi đi làm tôi đã tìm hiểu thêm về thread/process trong python. Bài viết này ra đời mục đích ghi lại những tìm hiểu và hiểu biết của tôi về multithreading và multiprocessing trong python.

Thread:

  • Là đơn vị nhỏ nhất của thực thi trong một tiến trình (process).
  • Một tiến trình có thể có nhiều thread, và chúng có thể chia sẻ tài nguyên với nhau trong tiến trình đó.
  • Thread chia sẻ bộ nhớ và các tài nguyên khác trong tiến trình với các thread khác.
  • Dễ quản lý và tương tác nhanh hơn vì chúng chia sẻ cùng một không gian bộ nhớ.
  • Thread không độc lập hoàn toàn, điều này có thể dẫn đến các vấn đề về đồng bộ hóa khi nhiều thread cố gắng truy cập và sửa đổi cùng một dữ liệu.

Process:

  • Là một tiến trình độc lập hoàn toàn với bộ nhớ và tài nguyên của nó.
  • Mỗi process có một không gian bộ nhớ riêng và không chia sẻ dữ liệu với các process khác.
  • Dễ quản lý tách biệt hơn và an toàn hơn vì các process không chia sẻ tài nguyên.
  • Process độc lập với nhau và không gây ra vấn đề đồng bộ hóa liên quan đến việc truy cập và sửa đổi dữ liệu.
  • Tích hợp sâu vào hệ điều hành và có thể hoạt động độc lập với các process khác.

Multithreading

Multithreading được định nghĩa là khả năng của processor sử dụng nhiều thread cùng một lúc.

Tại sao cần đến multithreading?

  1. Tận dụng CPU đa nhân: Máy tính hiện đại thường có nhiều CPU hoặc lõi CPU đa nhân. Sử dụng multithreading, bạn có thể tận dụng sức mạnh của nhiều CPU hoặc lõi CPU để thực hiện nhiều công việc đồng thời. Điều này cải thiện hiệu suất của ứng dụng và giúp tối ưu hóa việc sử dụng tài nguyên.
  2. Tăng độ phản hồi của ứng dụng: Multithreading cho phép bạn tạo các tiến trình hoặc luồng phụ để thực hiện các tác vụ I/O-bound mà không làm tắc nghẽn giao diện người dùng chính. Điều này làm cho ứng dụng trở nên nhanh hơn và thân thiện hơn với người dùng.
  3. Tích hợp xử lý đồng thời: Trong một số ứng dụng như máy chủ web, multithreading cho phép bạn phục vụ nhiều yêu cầu cùng một lúc mà không cần chờ hoàn thành mỗi yêu cầu trước khi bắt đầu xử lý yêu cầu khác. Điều này giúp tăng khả năng chịu tải của máy chủ.
  4. Tăng sự đa dạng về tác vụ: Bằng cách sử dụng multithreading, bạn có thể chia nhỏ ứng dụng thành nhiều thread, mỗi thread có thể thực hiện một tác vụ cụ thể. Điều này giúp tăng sự đa dạng về tác vụ mà ứng dụng có thể thực hiện đồng thời.
  5. Xử lý đồng thời các tác vụ I/O-bound: Multithreading là lựa chọn tốt cho các tác vụ I/O-bound như đọc/ghi tệp, truy vấn cơ sở dữ liệu, gửi yêu cầu mạng vì các thread có thể tiếp tục thực hiện công việc mà không cần chờ đợi kết quả I/O.

Ví dụ về việc sử dụng multithreading:

import threading

def print_hello(num):
    print(f'Hello {num}');
 
if __name__ =="__main__":
    t1 = threading.Thread(target=print_hello, args=('ethan',))
    t2 = threading.Thread(target=print_hello, args=('hinkeu',))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
 
    # both threads completely executed
    print("Done!")

Kết quả:

Hello ethan
Hello hinkeu
Done!

Một khái niệm mà tôi muốn nhắc tới là GIL (Global Interperter Lock)
Gil là một khóa toàn cục của python, nó giúp quản lý để đảm bảo rằng tại một thời điểm chỉ có một thread được phép thực hiện mã python. Hiểu nôm na là chỉ có một thread được truy xuất, duyệt các variable v…v

GIL đảm bảo tính nhất quán của dữ liệu chia sẻ và tránh các vấn đề liên quan đến race condition.

Các lý do cần GIL (từ chatgpt):

  1. Đơn giản hóa việc quản lý tài nguyên: GIL giúp đơn giản hóa việc quản lý tài nguyên trong Python. Nếu không có GIL, việc quản lý tài nguyên và bảo đảm tính nhất quán của dữ liệu chia sẻ giữa các thread sẽ trở nên phức tạp hơn.
  2. Giải quyết các vấn đề tiến trình: GIL đảm bảo tính nhất quán của trình thông dịch Python trong khi chạy trên nhiều thread. Nếu không có GIL, có thể xảy ra các lỗi không thể dự đoán và gây ra hiệu ứng đua thời (race condition) khi nhiều thread cố gắng truy cập và thay đổi cùng một biến hoặc dữ liệu.
  3. Tối ưu hóa cho các tác vụ I/O-bound: GIL không ảnh hưởng đến hiệu năng của các tác vụ I/O-bound, như đọc/ghi tệp hoặc thực hiện các tác vụ mạng. Vì các thread thường gặp I/O-bound trong các tác vụ này, GIL có thể được giải phóng để cho phép các thread thực hiện đọc/ghi I/O đồng thời.
  4. Bảo toàn tính nhất quán của dữ liệu: GIL đảm bảo tính nhất quán của dữ liệu chia sẻ giữa các thread. Trong môi trường đa luồng, việc đảm bảo tính nhất quán của dữ liệu là một vấn đề phức tạp và có thể gây ra các lỗi logic khó xác định nếu không được quản lý cẩn thận.

Tuy nhiên, GIL cũng có nhược điểm, đặc biệt trong các tác vụ CPU-bound (tính toán nhiều). Trong các tác vụ này, GIL có thể làm giảm hiệu năng của đa luồng trong Python. Để tối ưu hóa hiệu suất trong các tác vụ CPU-bound, bạn có thể xem xét sử dụng các tiến trình (processes) thay vì các thread, hoặc sử dụng các thư viện bên ngoài như multiprocessing để tận dụng tính đa luồng.
Ví dụ về GIL:

import threading
import time

def count_up():
    global count
    start_time = time.time()
    for _ in range(1000000):
        count += 1
    end_time = time.time()
    print(f"Thread 1 - Count Up took {end_time - start_time} seconds")

def count_down():
    global count
    start_time = time.time()
    for _ in range(1000000):
        count -= 1
    end_time = time.time()
    print(f"Thread 2 - Count Down took {end_time - start_time} seconds")

count = 0

thread1 = threading.Thread(target=count_up)
thread2 = threading.Thread(target=count_down)

start_time = time.time()

thread1.start()
thread2.start()

thread1.join()
thread2.join()

end_time = time.time()

print("Final count:", count)
print("Execution time:", end_time - start_time, "seconds")
Thread 1 - Count Up took 0.8097937107086182 seconds
Thread 2 - Count Down took 0.7898764610290527 seconds
Final count: -58698
Execution time: 0.8133831024169922 seconds

Tạm sơ sơ cơ bản vậy thôi. Phần 2 sẽ nói về multiprocessing

Một dev quèn kiếm tiền nuôi gia đình