PHP
Python

Trang chủ

Hướng dẫn cách đồng bộ hóa thread trong Python chi tiết

Ngôn ngữ Python là một ngôn ngữ lập trình đa mục đích, nổi tiếng với cú pháp dễ đọc, dễ học và tính ứng dụng cao. Trong lĩnh vực phát triển web, Python thường được sử dụng thông qua các framework như Django và Flask để xây dựng các ứng dụng web mạnh mẽ, bảo mật và dễ mở rộng. Trong chuyên mục này, Vietnix không chỉ cung cấp kiến thức nền tảng về ngôn ngữ Python mà còn hướng dẫn chi tiết cách xây dựng các ứng dụng web thực tế, sử dụng các framework phổ biến và áp dụng các kỹ thuật tiên tiến. Vietnix cam kết liên tục cập nhật những bài viết mới nhất về các tính năng mới của Python, các thư viện hỗ trợ hữu ích và những phương pháp tốt nhất, giúp bạn khai thác tối đa sức mạnh của Python và hoàn thiện kỹ năng lập trình web của mình.
html
CSS
javascript
sql
python
php
c
c++
bootstrap
react
mysql
reactjs
vuejs
Javascript Tutorials
18/03/2025
18 phút đọc
Theo dõi Vietnix trên

Hướng dẫn cách đồng bộ hóa thread trong Python chi tiết

Đồng bộ hóa thread trong Python là quá trình kiểm soát cách các luồng (threads) truy cập vào tài nguyên chia sẻ để đảm bảo tính toàn vẹn dữ liệu và tránh lỗi cạnh tranh. Nếu không có cơ chế đồng bộ, các luồng có thể ghi đè hoặc làm sai lệch dữ liệu khi chạy đồng thời. Trong bài viết này, mình sẽ cùng bạn tìm hiểu về các phương pháp đồng bộ hóa phổ biến trong Python, từ Lock, Condition đến Semaphore, Event và nhiều công cụ hữu ích khác. Ngoài ra, mình cũng sẽ hướng dẫn cách sử dụng phương thức join() để quản lý luồng một cách hiệu quả.

Điểm chính cần nắm

  • Cách thức đồng bộ hóa thread trong Python: Giới thiệu các phương pháp đồng bộ hóa thread để quản lý tài nguyên dùng chung một cách an toàn.
  • Đồng bộ hóa luồng với Lock: Lock giúp kiểm soát truy cập tài nguyên bằng cách chỉ cho phép một thread sử dụng tại một thời điểm.
  • Đồng bộ hóa luồng với Condition Objects trong Python: Condition Objects cung cấp cơ chế chờ và thông báo giữa các thread, giúp luồng chờ đợi một điều kiện cụ thể trước khi tiếp tục.
  • Đồng bộ hóa luồng với phương thức join(): join() đảm bảo luồng chính chờ các luồng con hoàn thành trước khi tiếp tục thực thi chương trình.
  • Các công cụ đồng bộ hóa khác: Giới thiệu các công cụ đồng bộ hóa nâng cao như Semaphore, Event, Barrier, RLock và Queue.
  • FAQ: Giải đáp các câu hỏi thường gặp về đồng bộ hóa thread trong Python, bao gồm khi nào cần dùng, sự khác biệt giữa các công cụ và cách tránh deadlock.
  • Vietnix – Nhà cung cấp dịch vụ lưu trữ tốc độ cao và bảo mật uy tín: Vietnix cung cấp giải pháp server, hosting, VPS chất lượng cao với hiệu suất ổn định và bảo mật vượt trội.

Cách thức đồng bộ hóa thread trong Python

Synchronizing Threads (Đồng bộ hóa thread) trong Python là quá trình đảm bảo rằng nhiều luồng (threads) trong một chương trình không truy cập hoặc thay đổi tài nguyên chia sẻ cùng lúc, nhằm tránh tình trạng xung đột hoặc lỗi dữ liệu. Điều này rất quan trọng khi bạn làm việc với nhiều luồng đồng thời và tài nguyên chia sẻ giữa chúng.

Nếu các luồng truy cập tài nguyên cùng một lúc mà không có cơ chế đồng bộ hóa, chúng có thể thay đổi tài nguyên đó đồng thời, dẫn đến các vấn đề như mất dữ liệu hoặc các lỗi logic trong chương trình.

Cách thức đồng bộ hóa thread trong Python
Cách thức đồng bộ hóa thread trong Python

Trong Python, khi nhiều luồng (threads) làm việc đồng thời với các tài nguyên chia sẻ, điều quan trọng là phải đồng bộ hóa truy cập của chúng để duy trì tính toàn vẹn của dữ liệu và đảm bảo tính chính xác của chương trình. Việc đồng bộ hóa các luồng trong Python có thể được thực hiện thông qua các nguyên hàm đồng bộ hóa được cung cấp bởi module threading, chẳng hạn như khóa (locks), điều kiện (conditions), semaphore, và barrier. Các công cụ này giúp kiểm soát quyền truy cập vào tài nguyên chia sẻ và phối hợp việc thực thi của nhiều luồng.

Đồng bộ hóa luồng với Lock

Lock trong Python là một công cụ đồng bộ hóa đơn giản giúp đảm bảo chỉ một luồng có thể truy cập vào một phần mã (critical section) tại một thời điểm. Điều này giúp tránh các vấn đề như xung đột dữ liệu khi nhiều luồng cùng truy cập tài nguyên chia sẻ.

Giải thích thêm:

  • Khóa (Lock) giúp ngăn chặn nhiều luồng truy cập tài nguyên cùng lúc, tránh gây ra lỗi hoặc xung đột dữ liệu.
  • Khi một luồng muốn truy cập tài nguyên chia sẻ, nó phải acquire khóa. Nếu khóa đang được chiếm dụng, luồng sẽ phải chờ cho đến khi khóa được release.
  • Mỗi khóa chỉ có thể được sử dụng bởi một luồng tại một thời điểm, giúp bảo vệ dữ liệu khỏi sự thay đổi đồng thời và các lỗi không mong muốn.

Để tạo khóa, dùng phương thức Lock(). Khóa có thể được lấy bằng acquire() và giải phóng bằng release().

Ví dụ:

Ví dụ sau đây minh họa cách sử dụng khóa (phương thức threading.Lock()) để đồng bộ hóa các luồng trong Python, đảm bảo rằng nhiều luồng truy cập tài nguyên chia sẻ một cách an toàn và chính xác.

import threading

counter = 10

def increment(theLock, N):
    global counter
    for i in range(N):
        theLock.acquire()  # Chiếm khóa
        counter += 1       # Thực hiện thay đổi tài nguyên chia sẻ
        theLock.release()  # Giải phóng khóa

lock = threading.Lock()
t1 = threading.Thread(target=increment, args=[lock, 2])
t2 = threading.Thread(target=increment, args=[lock, 10])
t3 = threading.Thread(target=increment, args=[lock, 4])

t1.start()
t2.start()
t3.start()

# Chờ tất cả các luồng hoàn thành
for thread in (t1, t2, t3):
    thread.join()

print("Tất cả luồng đã hoàn thành")
print("Giá trị Counter cuối cùng:", counter)

Kết quả:

Tất cả luồng đã hoàn thành
Giá trị Counter cuối cùng: 26

Trong ví dụ trên, các luồng t1, t2, và t3 sử dụng khóa để đồng bộ hóa việc thay đổi giá trị biến counter, đảm bảo rằng mỗi luồng chỉ thay đổi biến này một cách an toàn và không bị xung đột.

Đồng bộ hóa luồng với Condition Objects trong Python

Condition Objects trong Python cung cấp cơ chế đồng bộ hóa nâng cao, cho phép các luồng chờ đợi và được thông báo khi một điều kiện nào đó xảy ra. Điều này rất hữu ích khi bạn cần một luồng phải chờ đợi cho đến khi một hành động của luồng khác xảy ra, như việc thay đổi giá trị của tài nguyên chia sẻ.

Giải thích thêm:

  • Điều kiện (Condition) cho phép một hoặc nhiều luồng chờ đợi cho đến khi được thông báo bởi một luồng khác thông qua phương thức notify() hoặc notify_all().
  • Các luồng sử dụng wait() để chờ đợi và notify() để đánh thức các luồng đang chờ.
  • Điều kiện thường được sử dụng khi có sự phụ thuộc giữa các luồng, ví dụ như một luồng phải sản xuất dữ liệu cho luồng khác tiêu thụ.
  • Để sử dụng điều kiện, bạn cần tạo đối tượng Condition() từ module threading. Điều kiện này giúp kiểm soát việc đồng bộ hóa các luồng dựa trên các trạng thái được chia sẻ.

Ví dụ:

Ví dụ này minh họa cách đối tượng Condition có thể đồng bộ hóa các luồng bằng cách sử dụng các phương thức notify()wait().

import threading

counter = 0

# Hàm tiêu thụ
def consumer(cv):
    global counter
    with cv:
        print("Consumer is waiting")
        cv.wait()  # Chờ thông báo từ luồng khác
        print("Consumer has been notified. Current Counter value:", counter)

# Hàm tăng giá trị counter
def increment(cv, N):
    global counter
    with cv:
        print("Increment is producing items")
        for i in range(1, N + 1):
            counter += i  # Tăng giá trị counter
        cv.notify()  # Thông báo cho consumer rằng có dữ liệu
        print("Increment has finished")

# Tạo đối tượng Condition
cv = threading.Condition()

# Tạo và bắt đầu các luồng
consumer_thread = threading.Thread(target=consumer, args=[cv])
increment_thread = threading.Thread(target=increment, args=[cv, 5])

consumer_thread.start()
increment_thread.start()

consumer_thread.join()
increment_thread.join()

print("Giá trị Counter cuối cùng:", counter)

Kết quả:

Consumer is waiting
Increment is producing items
Increment has finished
Consumer has been notified. Current Counter value: 15
Giá trị Counter cuối cùng: 15

Giải thích về ví dụ:

  • consumer() sẽ chờ đợi (sử dụng cv.wait()) cho đến khi có thông báo từ luồng increment().
  • Luồng increment() sẽ tăng giá trị của counter và thông báo cho luồng consumer bằng cv.notify().
  • Điều này đảm bảo rằng luồng consumer chỉ hoạt động khi luồng increment đã hoàn thành công việc của mình.

Đồng bộ hóa luồng với phương thức join()

Phương thức join() trong Python giúp đồng bộ hóa các luồng bằng cách đảm bảo rằng luồng chính (main thread) phải đợi cho các luồng con (sub-threads) hoàn thành công việc của chúng trước khi tiếp tục thực thi. Điều này giúp tránh tình trạng luồng chính kết thúc trước khi các luồng con hoàn tất, giúp đảm bảo tính chính xác và hoàn chỉnh của chương trình.

Giải thích thêm:

  • Khi một luồng con gọi join() trên một luồng khác, nó sẽ chờ cho đến khi luồng đó hoàn thành trước khi tiếp tục thực thi.
  • Phương thức này hữu ích khi bạn cần đảm bảo rằng tất cả các luồng đã hoàn thành trước khi chương trình kết thúc, chẳng hạn như khi xử lý các tác vụ song song.
  • join() có thể nhận một tham số timeout (tùy chọn) để chỉ định thời gian tối đa mà một luồng sẽ đợi. Nếu luồng không hoàn thành trong khoảng thời gian này, phương thức join() sẽ dừng chờ.

Phương thức join() trong module threading của Python được sử dụng để chờ cho đến khi tất cả các luồng hoàn thành quá trình thực thi của chúng. Đây là cách đơn giản để đồng bộ hóa luồng chính với việc hoàn thành của các luồng khác.

Ví dụ:

Ví dụ sau minh họa việc đồng bộ hóa các luồng bằng cách sử dụng phương thức join() để đảm bảo rằng luồng chính chờ đợi tất cả các luồng được bắt đầu hoàn thành công việc của chúng trước khi tiếp tục.

import threading
import time

class MyThread(threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter
        
    def run(self):
        print("Starting " + self.name)    
        print_time(self.name, self.counter, 3)
        
def print_time(threadName, delay, counter):
    while counter:
        time.sleep(delay)
        print("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1

threads = []

# Tạo các luồng mới
thread1 = MyThread(1, "Thread-1", 1)
thread2 = MyThread(2, "Thread-2", 2)

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

# Đợi các luồng hoàn thành
thread1.join()
thread2.join()

print("Exiting Main Thread")

Kết quả:

Starting Thread-1
Thread-1: Thu Mar 17 15:15:22 2025
Thread-1: Thu Mar 17 15:15:23 2025
Thread-1: Thu Mar 17 15:15:24 2025
Starting Thread-2
Thread-2: Thu Mar 17 15:15:23 2025
Thread-2: Thu Mar 17 15:15:25 2025
Thread-2: Thu Mar 17 15:15:27 2025
Exiting Main Thread

Giải thích về ví dụ:

  • thread1thread2 là hai luồng con được tạo và bắt đầu thực thi với các tham số khác nhau.
  • Mỗi luồng sẽ thực hiện hàm print_time(), in thời gian hiện tại ba lần, với độ trễ khác nhau.
  • Sau khi các luồng con được bắt đầu, phương thức join() được gọi trên mỗi luồng để đảm bảo rằng luồng chính sẽ đợi cho đến khi tất cả các luồng con hoàn thành công việc của chúng. Sau đó, chương trình sẽ in “Exiting Main Thread” khi tất cả các luồng đã hoàn thành.

Các công cụ đồng bộ hóa khác

Ngoài LockCondition Objects, Python còn cung cấp một số công cụ đồng bộ hóa khác để quản lý truy cập tài nguyên chia sẻ giữa các luồng, giúp tránh các xung đột và bảo vệ tính toàn vẹn của dữ liệu. Dưới đây là một số công cụ đồng bộ hóa phổ biến:

1. Semaphore

Semaphore là một công cụ đồng bộ hóa giúp kiểm soát số lượng luồng có thể truy cập vào tài nguyên chia sẻ cùng lúc. Semaphore có một đếm (counter) đại diện cho số lượng luồng có thể truy cập tài nguyên. Mỗi khi một luồng truy cập tài nguyên, giá trị counter giảm đi một và khi một luồng giải phóng tài nguyên, giá trị counter tăng lại.

  • Các phương thức chính:
    • acquire(): Giảm giá trị của semaphore, nếu semaphore đã đạt giới hạn tối đa, luồng sẽ bị chặn cho đến khi giá trị semaphore lớn hơn 0.
    • release(): Tăng giá trị của semaphore, cho phép các luồng khác có thể truy cập tài nguyên.

2. Event

Event trong Python là một công cụ đồng bộ hóa đơn giản giúp một luồng có thể chờ đợi một sự kiện (event) xảy ra. Một luồng có thể đợi một sự kiện được “set” (được kích hoạt) và sau đó tiếp tục thực thi.

  • Các phương thức chính:
    • wait(): Luồng sẽ chờ đợi cho đến khi sự kiện được kích hoạt.
    • set(): Kích hoạt sự kiện, đánh thức tất cả các luồng đang chờ.
    • clear(): Đặt sự kiện về trạng thái chưa kích hoạt, khiến các luồng gọi wait() phải đợi.

3. Barrier

Barrier là một công cụ đồng bộ hóa cho phép một nhóm các luồng đồng thời “đợi” nhau ở một điểm chung (barrier). Tất cả các luồng phải chờ đợi nhau cho đến khi tất cả đã đến barrier, sau đó mới có thể tiếp tục thực thi.

  • Các phương thức chính:
    • wait(): Mỗi luồng gọi phương thức này để đợi tại barrier.
    • Khi tất cả các luồng đã gọi wait(), barrier sẽ được mở và tất cả các luồng sẽ tiếp tục thực thi.

4. RLock (Reentrant Lock)

RLock là một loại lock đặc biệt cho phép luồng chiếm giữ khóa nhiều lần mà không gây deadlock. Điều này có nghĩa là một luồng có thể “acquire” khóa nhiều lần mà không bị chặn, miễn là nó “release” đúng số lần tương ứng.

  • Các phương thức chính:
    • acquire(): Được sử dụng như Lock, nhưng cho phép luồng chiếm giữ khóa nhiều lần.
    • release(): Mỗi lần gọi release(), khóa sẽ được giải phóng một lần. Khi số lần release() bằng số lần acquire(), khóa sẽ được hoàn toàn giải phóng.

5. Queue (Queue.Queue)

Queue là một công cụ đồng bộ hóa được sử dụng khi cần giao tiếp giữa các luồng, đặc biệt là trong các trường hợp luồng sản xuất và tiêu thụ dữ liệu. Queue giúp các luồng sản xuất dữ liệu (producer) và các luồng tiêu thụ dữ liệu (consumer) có thể hoạt động đồng thời mà không gây xung đột.

  • Các phương thức chính:
    • put(): Đưa dữ liệu vào queue.
    • get(): Lấy dữ liệu từ queue.
    • task_done(): Được gọi sau khi hoàn thành công việc với một item trong queue.

Vietnix – Nhà cung cấp dịch vụ lưu trữ tốc độ cao và bảo mật uy tín

Vietnix là một trong những nhà cung cấp dịch vụ cho thuê máy chủ (server), hosting, VPSdomain hàng đầu tại Việt Nam. Họ cam kết mang đến giải pháp lưu trữ hiệu quả, bảo mật cao với dịch vụ chất lượng và đội ngũ hỗ trợ kỹ thuật chuyên nghiệp 24/7. Hơn 80.000 khách hàng tin tưởng Vietnix để tối ưu hóa hiệu suất và bảo mật dữ liệu cho doanh nghiệp.

Thông tin liên hệ:

  • Website: https://vietnix.vn/
  • Hotline: 18001093
  • Email: sales@vietnix.com.vn
  • Địa chỉ: 265 Hồng Lạc, Phường 10, Quận Tân Bình, TP. Hồ Chí Minh.

Câu hỏi thường gặp

Sự khác biệt giữa Lock và RLock là gì?

– Lock: Khi một luồng đã acquire, nếu nó cố gắng acquire lại, chương trình sẽ bị treo.
– RLock: Cùng một luồng có thể acquire nhiều lần mà không bị khóa chính nó, miễn là số lần release() tương ứng với số lần acquire().

Dùng Lock có làm chậm chương trình không?

. Vì Lock giới hạn chỉ một luồng có thể chạy trong một thời điểm nhất định, nên nếu không sử dụng hợp lý, có thể làm giảm hiệu suất chương trình.

Khi nào nên dùng threading và khi nào nên dùng multiprocessing?

– threading: Khi chương trình chủ yếu thực hiện tác vụ I/O như đọc ghi file, gọi API, kết nối database.
– multiprocessing: Khi chương trình thực hiện các tác vụ tính toán nặng, cần sử dụng nhiều CPU để tăng tốc độ xử lý.

Có cách nào tránh deadlock khi sử dụng Lock không?

, bạn có thể:
– Luôn đảm bảo mỗi acquire() có một release().
– Sử dụng acquire(timeout) để tránh chờ vô hạn.
– Dùng RLock thay vì Lock nếu cần nhiều lần acquire từ cùng một luồng.

Có cần đồng bộ hóa thread nếu chỉ đọc dữ liệu không?

Thông thường, nếu chỉ đọc dữ liệu mà không có ghi hoặc cập nhật, bạn không cần đồng bộ hóa. Tuy nhiên, nếu có nguy cơ dữ liệu thay đổi trong quá trình đọc, tốt nhất vẫn nên có cơ chế bảo vệ.

Lời kết

Việc đồng bộ hóa thread là một phần quan trọng trong lập trình đa luồng, giúp đảm bảo chương trình chạy ổn định và tránh các lỗi liên quan đến tài nguyên chia sẻ. Với các công cụ đồng bộ hóa như Lock, Condition, Semaphore hay Event, bạn có thể kiểm soát tốt hơn cách các luồng tương tác với nhau. Nếu bạn có bất cứ thắc mắc hay cần hỗ trợ gì, hãy để lại bình luận bên dưới mình hỗ trợ nhanh nhất. Cảm ơn bạn đã đọc!

Mọi người cũng xem:

Cao Lê Viết Tiến

PHP Leader
tại
Vietnix

Kết nối với mình qua

Icon Quote
Icon Quote

Học lập trình online cùng vietnix

Học lập trình online cùng Vietnix

PHPXem thêmThu gọn