Multithreading trong Python

Multithreading trong Python là một kỹ thuật để thực hiện nhiều tác vụ đồng thời bằng cách sử dụng nhiều luồng (thread) trong cùng một tiến trình. Điều này giúp chương trình tận dụng được tài nguyên CPU tốt hơn và cải thiện hiệu suất trong các tác vụ liên quan đến I/O, như đọc ghi file hoặc gửi yêu cầu mạng. Tuy nhiên, vì sự tồn tại của Global Interpreter Lock (GIL), Python không hỗ trợ multithreading thực sự khi xử lý các tác vụ liên quan đến CPU.

Trong bài viết này, tôi sẽ giải thích cách hoạt động của multithreading trong Python, cùng với các ví dụ cụ thể.

1. Multithreading là gì?

Multithreading là quá trình thực thi nhiều luồng (thread) trong một tiến trình duy nhất. Mỗi luồng là một con đường thực thi độc lập, cho phép chương trình thực hiện nhiều tác vụ cùng một lúc.

Multithreading có ích trong các tác vụ I/O (nhập/xuất) vì trong quá trình đợi (ví dụ: chờ kết nối mạng), luồng có thể bị chặn, và thay vì ngồi chờ, các luồng khác có thể tiếp tục thực thi.

2. Global Interpreter Lock (GIL)

Một trong những điểm đặc biệt của Python là sự tồn tại của GIL. GIL là một mutex bảo đảm chỉ có một luồng Python được thực thi tại một thời điểm trong không gian chương trình Python. Điều này làm cho Python không tận dụng tốt đa luồng trong các tác vụ xử lý CPU nặng. Dù bạn có tạo nhiều luồng thì Python vẫn thực thi tuần tự do GIL.

Tuy nhiên, trong các tác vụ I/O như đọc file, gửi yêu cầu HTTP, truy xuất database, hoặc xử lý tác vụ không liên quan đến CPU, multithreading trong Python vẫn hữu ích.

3. Cách Sử Dụng Multithreading trong Python

Python cung cấp thư viện threading để hỗ trợ việc sử dụng multithreading. Các bước cơ bản để tạo và quản lý luồng là:

  1. Tạo một đối tượng Thread từ lớp threading.Thread().
  2. Xác định hàm mà luồng sẽ thực hiện.
  3. Bắt đầu luồng bằng cách gọi phương thức start().
  4. Kết thúc luồng bằng cách gọi join() để đợi luồng kết thúc.

4. Ví dụ về Multithreading trong Python

a) Ví dụ Cơ Bản

Ví dụ dưới đây minh họa cách tạo và thực thi hai luồng đơn giản trong Python:

import threading
import time

def print_numbers():
    for i in range(1, 6):
        print(f"Number: {i}")
        time.sleep(1)

def print_letters():
    for letter in ['A', 'B', 'C', 'D', 'E']:
        print(f"Letter: {letter}")
        time.sleep(1)

# Tạo hai luồng
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)

# Bắt đầu hai luồng
thread1.start()
thread2.start()

# Đợi cả hai luồng kết thúc
thread1.join()
thread2.join()

print("Hoàn thành!")

Trong ví dụ này, hai luồng sẽ thực thi cùng lúc: một luồng in số và một luồng in chữ cái. Chương trình sẽ in số và chữ xen kẽ nhau.

b) Ví dụ với Đối tượng Luồng Tùy Chỉnh

Bạn cũng có thể tạo một lớp tùy chỉnh bằng cách kế thừa từ threading.Thread. Điều này cho phép bạn kiểm soát tốt hơn hành vi của từng luồng.

import threading
import time

class CustomThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        for i in range(3):
            print(f"Thread {self.name} đang chạy")
            time.sleep(1)

# Tạo và khởi động luồng tùy chỉnh
thread1 = CustomThread("A")
thread2 = CustomThread("B")

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print("Tất cả luồng đã hoàn thành.")

Trong ví dụ này, CustomThread là một lớp kế thừa từ threading.Thread, và run() là phương thức mà mỗi luồng sẽ thực thi khi start() được gọi.

5. Tính đồng bộ và Khóa trong Multithreading

Khi nhiều luồng thực thi đồng thời, có thể xảy ra các tình huống mà nhiều luồng truy cập vào cùng một tài nguyên (như một biến hoặc tệp) cùng một lúc, dẫn đến xung đột dữ liệu. Để tránh tình trạng này, ta sử dụng các cơ chế đồng bộ, như lock (khóa).

Ví dụ về sử dụng khóa:

import threading

lock = threading.Lock()
balance = 0

def update_balance(n):
    global balance
    lock.acquire()  # Giành quyền truy cập
    for _ in range(100000):
        balance += n
        balance -= n
    lock.release()  # Giải phóng quyền truy cập

# Tạo các luồng
thread1 = threading.Thread(target=update_balance, args=(5,))
thread2 = threading.Thread(target=update_balance, args=(10,))

# Bắt đầu các luồng
thread1.start()
thread2.start()

# Đợi các luồng kết thúc
thread1.join()
thread2.join()

print(f"Số dư cuối cùng: {balance}")

Trong ví dụ này, ta sử dụng lock.acquire()lock.release() để đảm bảo rằng chỉ có một luồng có thể thay đổi biến balance tại một thời điểm, tránh việc xung đột dữ liệu.

6. Lợi ích và Hạn chế của Multithreading trong Python

Lợi ích:

  • Tối ưu I/O-bound tasks: Multithreading trong Python có lợi trong các tác vụ I/O, như đọc/ghi file, truy xuất mạng, vì các tác vụ này không bị ảnh hưởng bởi GIL.
  • Đơn giản trong sử dụng: Thư viện threading cung cấp một cách đơn giản để tạo và quản lý các luồng.

Hạn chế:

  • GIL: Global Interpreter Lock khiến Python không thể thực thi multithreading thực sự với các tác vụ CPU-bound, do chỉ một luồng được chạy tại một thời điểm.
  • Khó kiểm soát đồng bộ: Quản lý sự đồng bộ giữa các luồng có thể gây phức tạp, đặc biệt trong các ứng dụng lớn, vì có thể xảy ra lỗi race conditions hoặc deadlocks.

7. Giải pháp thay thế Multithreading trong Python

Nếu bạn cần thực thi nhiều tác vụ liên quan đến CPU, multiprocessing có thể là một giải pháp thay thế tốt hơn multithreading, vì nó tạo ra nhiều tiến trình độc lập với GIL.

Ví dụ:

import multiprocessing

def square(n):
    return n * n

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

Trong ví dụ trên, multiprocessing.Pool sẽ tạo ra nhiều tiến trình độc lập và không bị ảnh hưởng bởi GIL.

Kết Luận

Multithreading trong Python rất hữu ích cho các tác vụ I/O-bound như xử lý dữ liệu từ mạng, đọc/ghi file, nhưng lại không hiệu quả trong các tác vụ CPU-bound do GIL. Đối với các ứng dụng cần tối ưu CPU, bạn nên cân nhắc sử dụng multiprocessing thay vì multithreading. Tuy vậy, với các ứng dụng I/O, multithreading vẫn là một công cụ mạnh mẽ giúp cải thiện hiệu suất và tiết kiệm tài nguyên.