CodeGym /Các khóa học /Python SELF VI /Mô-đun threading

Mô-đun threading

Python SELF VI
Mức độ , Bài học
Có sẵn

2.1 Mô-đun threading

Đa luồng trong Python là cách thực thi nhiều luồng (threads) cùng lúc, giúp sử dụng tài nguyên CPU hiệu quả hơn, đặc biệt cho các thao tác I/O hoặc các nhiệm vụ khác có thể thực hiện song song.

Những khái niệm cơ bản về đa luồng trong Python:

Luồng — là đơn vị thực thi nhỏ nhất có thể chạy song song với các luồng khác trong cùng một tiến trình. Tất cả các luồng trong một tiến trình chia sẻ bộ nhớ chung, cho phép trao đổi dữ liệu giữa các luồng.

Tiến trình — là một instance của chương trình đang chạy trong hệ điều hành với không gian địa chỉ và tài nguyên riêng. Không giống như luồng, các tiến trình cách ly nhau và trao đổi dữ liệu thông qua giao tiếp liên tiến trình (IPC).

GIL — là cơ chế trong trình thông dịch Python ngăn cản việc thực thi nhiều luồng Python cùng lúc. GIL đảm bảo an toàn cho việc thực thi mã Python, nhưng hạn chế hiệu suất của các chương trình đa luồng trên bộ vi xử lý đa nhân.

Quan trọng! Cần lưu ý rằng do Global Interpreter Lock (GIL) đa luồng trong Python có thể không cung cấp sự gia tăng đáng kể về hiệu suất cho các nhiệm vụ tính toán chuyên sâu, vì GIL ngăn cản việc thực thi đồng thời nhiều luồng Python trên bộ vi xử lý đa nhân.

Mô-đun threading

Mô-đun threading trong Python cung cấp giao diện mức cao để làm việc với luồng. Nó cho phép tạo và quản lý luồng, đồng bộ hóa và tổ chức tương tác giữa các luồng. Hãy cùng xem xét các thành phần chính và chức năng của mô-đun này chi tiết hơn.

Các thành phần chính của mô-đun threading

Các thực thể để làm việc với luồng:

  • Thread — lớp cơ bản để tạo và quản lý các luồng.
  • Timer — bộ đếm thời gian để thực thi hàm sau một khoảng thời gian xác định.
  • ThreadLocal — cho phép tạo dữ liệu cục bộ cho luồng.

Cơ chế đồng bộ hóa luồng:

  • Lock — nguyên thủy đồng bộ hóa để ngăn chặn truy cập đồng thời đến tài nguyên chung.
  • Condition — biến điều kiện cho đồng bộ hóa luồng phức tạp hơn.
  • Event — nguyên thủy để thông báo giữa các luồng.
  • Semaphore — nguyên thủy để giới hạn số lượng luồng có thể chạy đồng thời một đoạn mã nhất định.
  • Barrier — đồng bộ hóa số lượng luồng xác định, chặn chúng cho đến khi tất cả đạt đến rào cản.

Dưới đây mình sẽ nói về 3 lớp để làm việc với luồng, cơ chế đồng bộ hóa luồng bạn sẽ không cần tới trong thời gian tới.

2.2 Lớp Thread

Lớp Thread — lớp cơ bản để tạo và quản lý các luồng. Nó có 4 phương thức chính:

  • start(): Bắt đầu thực thi luồng.
  • join(): Luồng hiện tại bị tạm dừng và đợi hoàn thành của luồng khởi chạy.
  • is_alive(): Trả về True, nếu luồng đang chạy.
  • run(): Phương thức chứa mã, sẽ được thực thi trong luồng. Ghi đè khi kế thừa từ lớp Thread.

Mọi thứ đơn giản hơn nhiều so với vẻ ngoài — ví dụ sử dụng lớp Thread.

Khởi động một luồng đơn giản


import threading

def worker():
    print("Worker thread is running")
            
# Tạo một luồng mới
t = threading.Thread(target=worker) #tạo đối tượng luồng mới
t.start() #Khởi động luồng
t.join() # Đợi hoàn thành của luồng
print("Main thread is finished")
        

Sau khi gọi phương thức start, hàm worker sẽ bắt đầu thực thi. Hoặc, chính xác hơn, luồng của nó sẽ được thêm vào danh sách các luồng hoạt động.

Sử dụng đối số


import threading

def worker(number, text):
    print(f"Worker {number}: {text}")
            
# Tạo một luồng mới với đối số
t = threading.Thread(target=worker, args=(1, "Hello"))
t.start()
t.join()
        

Để chuyển tham số vào luồng mới, chỉ cần chỉ ra chúng dưới dạng tuple và gán cho tham số args. Khi gọi hàm đã được chỉ định trong target, các tham số sẽ được chuyển một cách tự động.

Ghi đè phương thức run


import threading

class MyThread(threading.Thread):
    def run(self):
        print("Custom thread is running")
            
# Tạo và khởi động luồng
t = MyThread()
t.start()
t.join()
        

Có hai cách để xác định hàm bắt đầu thực thi luồng mới — có thể truyền nó qua tham số target khi tạo đối tượng Thread, hoặc kế thừa từ lớp Thread và ghi đè hàm run. Cả hai cách đều hợp lệ và thường được sử dụng.

2.3 Lớp Timer

Lớp Timer trong mô-đun threading được dùng để khởi động một hàm sau một khoảng thời gian xác định. Lớp này hữu ích cho việc thực hiện các nhiệm vụ trì hoãn trong môi trường đa luồng.

Timer được tạo và khởi tạo với hàm cần gọi và thời gian trì hoãn tính bằng giây.

  • Phương thức start() khởi động timer, đếm ngược khoảng thời gian xác định, sau đó gọi hàm chỉ định.
  • Phương thức cancel() cho phép dừng đồng hồ nếu nó chưa sập nổ. Điều này hữu ích để ngăn chặn thực hiện hàm nếu timer không còn cần nữa.

Ví dụ sử dụng:

Khởi động hàm với độ trễ

Trong ví dụ này, hàm hello sẽ được gọi sau 5 giây kể từ khi khởi động timer.


import threading

def hello():
    print("Hello, world!")
            
# Tạo timer, sẽ gọi hàm hello sau 5 giây
t = threading.Timer(5.0, hello)
t.start()  # Bắt đầu timer
        

Ngừng timer trước khi thực thi

Ở đây timer sẽ bị ngừng trước khi hàm hello kịp thực thi, vì thế không có gì được in ra.


import threading

def hello():
    print("Hello, world!")
            
# Tạo timer
t = threading.Timer(5.0, hello)
t.start()  # Bắt đầu timer
            
# Ngừng timer trước khi thực thi
t.cancel()
        

Timer với tham số

Trong ví dụ này, timer sẽ gọi hàm greet sau 3 giây và chuyển cho nó tham số "Alice".


import threading

def greet(name):
    print(f"Hello, {name}!")
            
# Tạo timer với tham số
t = threading.Timer(3.0, greet, args=["Alice"])
t.start()
        

Lớp Timer tiện lợi cho việc lập kế hoạch thực hiện các nhiệm vụ trong một khoảng thời gian nhất định. Tuy nhiên, timer không đảm bảo thời gian thực thi chính xác tuyệt đối, vì điều này phụ thuộc vào tải của hệ thống và hoạt động của bộ lập lịch luồng.

2.4 Lớp ThreadLocal

Lớp ThreadLocal được dùng để tạo luồng mà mỗi luồng có dữ liệu cục bộ riêng của nó. Điều này hữu ích trong các ứng dụng đa luồng khi mỗi luồng cần có phiên bản dữ liệu riêng để tránh xung đột và vấn đề đồng bộ hóa.

Mỗi luồng sử dụng ThreadLocal sẽ có các bản sao dữ liệu độc lập. Dữ liệu được lưu trữ trong đối tượng ThreadLocal là duy nhất cho từng luồng và không chia sẻ với các luồng khác. Điều này tiện lợi để lưu trữ dữ liệu chỉ sử dụng trong ngữ cảnh của một luồng duy nhất như người dùng hiện tại trong ứng dụng web hoặc kết nối hiện tại với cơ sở dữ liệu.

Ví dụ sử dụng:

Sử dụng cơ bản

Trong ví dụ này, mỗi luồng gán tên của chính nó cho biến cục bộ value và in ra. Giá trị value là duy nhất cho mỗi luồng.


import threading

# Tạo đối tượng ThreadLocal
local_data = threading.local()
            
def process_data():
    # Gán giá trị cho biến cục bộ của luồng
    local_data.value = threading.current_thread().name
    # Truy cập biến cục bộ của luồng
    print(f'Value in {threading.current_thread().name}: {local_data.value}')

threads = []
for i in range(5):
    t = threading.Thread(target=process_data)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Lưu trữ dữ liệu người dùng trong ứng dụng web

Trong ví dụ này, mỗi luồng xử lý yêu cầu cho người dùng của riêng nó. Giá trị user_data.user là duy nhất cho mỗi luồng.


import threading
# Tạo đối tượng ThreadLocal
user_data = threading.local()
def process_request(user):
    # Gán giá trị cho biến cục bộ của luồng
    user_data.user = user
    handle_request()

def handle_request():
    # Truy cập biến cục bộ của luồng
    print(f'Handling request for user: {user_data.user}')

threads = []
users = ['Alice', 'Bob', 'Charlie']
for user in users:
    t = threading.Thread(target=process_request, args=(user,))
    threads.append(t)
    t.start()

for t in threads:
    t.join()

Đó là 3 lớp hữu ích nhất của thư viện threading. Có lẽ bạn sẽ sử dụng chúng trong công việc của mình, còn các lớp khác — chắc là không cần lắm. Giờ mọi người đều chuyển sang sử dụng chức năng bất đồng bộ và thư viện asyncio. Chúng ta sẽ nói về nó trong thời gian sắp tới.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION