Chuyển đến nội dung chính

Hướng dẫn toàn diện: Threading trong Python

Tìm hiểu các khái niệm cơ bản về threading và cách bạn có thể sử dụng nó với Python.
Đã cập nhật 5 thg 10, 2026  · 12 phút đọc

Khám phá cùng AI

ChatGPTClaudePerplexity

Nếu bạn vừa bắt đầu với Python và muốn học thêm, hãy tham gia khóa học Giới thiệu Khoa học Dữ liệu với Python của DataCamp.

đồ họa

Giới thiệu

Threading cho phép các phần khác nhau của tiến trình của bạn chạy đồng thời (Nguồn: RealPython). Những phần khác nhau này thường độc lập và có đơn vị thực thi riêng thuộc cùng một tiến trình. Tiến trình chỉ đơn giản là một chương trình đang chạy có các đơn vị riêng có thể chạy song song. Ví dụ, một trình duyệt web có thể là một tiến trình, một ứng dụng chạy đồng thời nhiều camera có thể là một tiến trình; trò chơi điện tử là một ví dụ khác về tiến trình.

Bên trong một tiến trình xuất hiện khái niệm đa luồng (multi-threading), nơi nhiều luồng phối hợp để đạt một mục tiêu chung. Lợi ích quan trọng nhất của việc dùng luồng là cho phép bạn chạy chương trình song song.

Hãy hiểu khái niệm threading qua một ví dụ. Hãy tưởng tượng bạn có một ứng dụng đếm số xe ra vào bãi đỗ xe của trung tâm thương mại. Thiết bị của bạn có nhiều camera giám sát lối vào và lối ra, kết nối đến một thiết bị trung tâm. Mỗi camera sẽ có một thuật toán để theo dõi lưu lượng xe, thuật toán này thuộc cùng một tiến trình hay chương trình. Tuy nhiên, mỗi camera cùng với thuật toán đang chạy trên đó có thể thuộc về một luồng riêng. Không chỉ vậy, ngay cả phần đọc khung hình từ camera và thuật toán dự đoán trên các khung hình cũng có thể là hai luồng riêng biệt.

Một ví dụ khác là trò chơi điện tử, trong đó tiến trình phải chạy các tác vụ song song như đồ họa, tương tác người dùng và mạng (khi chơi nhiều người) vì trò chơi phải luôn phản hồi. Để đạt được điều này, nó cần sử dụng khái niệm đa luồng, nơi mỗi luồng chịu trách nhiệm chạy từng tác vụ độc lập.

Một luồng có luồng thực thi riêng, nghĩa là trong tiến trình sẽ có nhiều việc xảy ra cùng lúc.

Lưu ý rằng mỗi tiến trình có ít nhất một luồng, gọi là main thread (luồng chính). Nếu chương trình của bạn không có luồng nào được định nghĩa, thì nó sẽ có ít nhất luồng chính, tức là bản thân tiến trình.

Nếu bạn nhìn vào sơ đồ ở trên, có một tiến trình gồm luồng chính chứa các biến toàn cục và ba luồng khác nhau t1, t2 và t3. Mỗi luồng có các biến cục bộ và luồng thực thi mã riêng. Các luồng thường chia sẻ tài nguyên của tiến trình, như tài nguyên sẵn có cho luồng chính cũng sẽ được chia sẻ giữa ba luồng t1, t2 và t3. Tất cả các luồng sẽ có quyền truy cập vào biến toàn cục đồng thời có các biến cục bộ của riêng mình.

Có các loại luồng khác nhau:

  • Luồng kernel (kernel thread)
  • Luồng người dùng (user thread)
  • Kết hợp luồng kernel và luồng người dùng

Giờ hãy xem một số ưu điểm khi dùng threading trong chương trình của bạn:

  • Đa luồng cho phép chương trình tăng tốc thực thi với điều kiện có nhiều CPU.

  • Nó cũng cho phép bạn thực hiện các tác vụ khác trong khi các thao tác I/O đang diễn ra với sự trợ giúp của nhiều luồng hoặc thậm chí luồng chính cùng với một luồng. Ví dụ, tốc độ đọc khung hình từ camera và tốc độ suy luận của thuật toán sẽ được xử lý bởi các luồng khác nhau. Do đó, thuật toán sẽ không phải chờ khung hình được đưa vào, và phần đọc khung hình cũng không phải đợi thuật toán chạy xong mới đọc khung tiếp theo.

  • Các luồng trong cùng một tiến trình có thể chia sẻ bộ nhớ và tài nguyên của luồng chính.

Thách thức của Threading

  • Hãy nhớ rằng Python dựa trên triển khai CPython, vốn giới hạn chỉ một luồng được chạy tại một thời điểm, do đó threading có thể không tăng tốc mọi tác vụ. Lý do cốt lõi là Global Interpreter Lock (GIL).

    Nếu bạn muốn tìm hiểu về GIL, hãy xem hướng dẫn này.

  • Nếu bạn muốn tăng tốc các tác vụ nặng CPU, thì threading có thể không phải giải pháp tốt nhất. Trong những trường hợp như vậy, multi-processing thường hữu ích.

  • Để thực thi một chương trình gồm nhiều luồng, cần chuyển đổi nhanh giữa các luồng; do đó, việc lập lịch cần được chú ý trong các chương trình có nhiều luồng.

  • Chia sẻ tài nguyên cũng có thể là vấn đề vì tất cả các luồng cùng chia sẻ tài nguyên và bộ nhớ của biến toàn cục. Do đó, thao tác thực hiện ở một luồng có thể gây lỗi bộ nhớ cho luồng khác, hoặc luồng kia có thể không có đủ bộ nhớ để thực hiện tác vụ.

Threading trong Python

  • Trong Python, mô-đun threading là mô-đun tích hợp sẵn có tên threading và có thể import trực tiếp.

  • Vì gần như mọi thứ trong Python đều được biểu diễn dưới dạng đối tượng, threading cũng là một đối tượng trong Python. Một luồng có khả năng

    • Lưu trữ dữ liệu,
    • Được lưu trong các cấu trúc dữ liệu như dictionary, list, set, v.v.
    • Có thể được truyền làm tham số cho một hàm.

  • Một luồng cũng có thể được thực thi như một tiến trình.

  • Một luồng trong Python có thể ở nhiều trạng thái như:

    • Chờ (Wait),
    • Khóa (Locked).

Giờ hãy học cách bạn có thể triển khai threading trong Python.

Mô-đun Thread trong Python3

Lưu ý Python3 tương thích ngược với mô-đun thread tồn tại trong Python2.7. Ở Python3, có thể import nó dưới dạng mô-đun _thread. Vậy hãy lấy một ví dụ để hiểu mô-đun _thread.

import _thread #thread module imported
import time #time module

Hãy định nghĩa một hàm tên thread_delay, nhận hai tham số đầu vào, tức là tên luồng và độ trễ. Bên trong hàm này, bạn sẽ:

  • Đặt một biến đếm bằng 0,
  • Sau đó lặp bằng vòng lặp while chạy ba lần,
  • Bên trong vòng while, dùng time.sleep() để thêm độ trễ; độ trễ này hữu ích để hiểu việc thực thi của luồng. Độ trễ tính bằng giây,
  • Tăng biến đếm thêm 1,
  • Để xem việc thực thi luồng có diễn ra trơn tru hay không, bạn sẽ in tên luồng và thời điểm luồng được thực thi.
def thread_delay(thread_name, delay):
    count = 0
    while count < 3:
        time.sleep(delay)
        count += 1
        print(thread_name, '-------->', time.time())

Giờ để thêm chức năng luồng cho hàm trên hoặc chạy hàm trên trong luồng, bạn sẽ dùng phương thức start_new_thread trong mô-đun _thread.

Hãy xem docstring của phương thức start_new_thread.

?_thread.start_new_thread
Hướng dẫn Threading trong Python

Hãy truyền vào hàm thread_delay không có ngoặc và hai đối số, tức là tên luồng và độ trễ (để trực quan hóa việc thực thi luồng vì nó rất nhanh).

_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 3))
10916



t1 --------> 1588077856.4221454
t1 --------> 1588077857.4418247
t2 --------> 1588077858.422453
t1 --------> 1588077858.4431
t2 --------> 1588077861.4371448
t2 --------> 1588077864.438915

Từ kết quả trên, bạn có thể thấy luồng t1 bắt đầu thực thi trước. Trong khi đó, luồng t2 chờ vì có độ trễ 3 giây, và ngay khi độ trễ kết thúc, luồng t2 được thực thi; đồng thời, độ trễ của luồng t1 chỉ 1 giây.

Giờ hãy đổi độ trễ của t2 thành 5 giây, điều này sẽ khởi chạy t2 sau khi t1 đã thực thi xong vì t1 chỉ mất 3 giây để hoàn tất.

_thread.start_new_thread(thread_delay, ('t1', 1))
_thread.start_new_thread(thread_delay, ('t2', 5))
14632



t1 --------> 1588078001.4087512
t1 --------> 1588078002.4272866
t1 --------> 1588078003.432218
t2 --------> 1588078005.4082909
t2 --------> 1588078010.4268317
t2 --------> 1588078015.4283602

Triển khai threading bằng mô-đun Threading

Hãy dùng lại ví dụ như trên, nhưng lần này bạn sẽ dùng mô-đun threading thay vì mô-đun _thread.

import threading
import time
def thread_delay(thread_name, delay):
    count = 0
    while count < 3:
        time.sleep(delay)
        count += 1
        print(thread_name, '-------->', time.time())

Bên trong mô-đun threading có lớp Thread, có tinh thần tương tự hàm start_new_thread của mô-đun _thread.

Hãy xem docstring của lớp Thread, lớp này nhận nhiều tham số như group, target (như một hàm), args, v.v.

?threading.Thread
Hướng dẫn Threading trong Python

Trong hàm khởi tạo của lớp Thread, bạn sẽ truyền vào hàm đích thread_delay và các đối số của hàm đó.

t1 = threading.Thread(target=thread_delay, args=('t1', 1))
t2 = threading.Thread(target=thread_delay, args=('t2', 3))

Trong mô-đun threading, để chạy hay thực thi luồng, bạn sử dụng phương thức start(), phương thức này đơn giản là chịu trách nhiệm chạy luồng.

?t1.start
Hướng dẫn Threading trong Python
t1.start()
t2.start()
t1 --------> 1588081121.7790291
t1 --------> 1588081122.7962046
t2 --------> 1588081123.7789369
t1 --------> 1588081123.7980876
t2 --------> 1588081126.7961557
t2 --------> 1588081129.7995002

Bạn cũng sẽ dùng phương thức join, nghĩa là chờ cho đến khi tất cả luồng thực thi xong. Vậy nên bất kỳ mã nào bạn viết sau phương thức join sẽ chỉ chạy khi các luồng này đã kết thúc.

?t1.join
Hướng dẫn Threading trong Python
t1.start()
t2.start()

t1.join()
t2.join()

print("Thread execution is complete!")
t1 --------> 1588081798.7118263
t1 --------> 1588081799.7231226
t2 --------> 1588081800.7129223
t1 --------> 1588081800.730732
t2 --------> 1588081803.7298765
t2 --------> 1588081806.7309563
Thread execution is complete!

Giờ hãy tính thể tích hình lập phương và hình vuông bằng khái niệm threading, khá đơn giản.

def volume_cube(a):
    print ("Volume of Cube:", a*a*a)
def volume_square(a):
    print ("Volume of Square:", a*a)
t1 = threading.Thread(target=volume_cube, args=(2))
t2 = threading.Thread(target=volume_square, args=(3))
t1.start()
t2.start()

t1.join()
t2.join()

print("Thread execution is complete!")
Thread execution is complete!


Exception in thread Thread-9:
Traceback (most recent call last):
  File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
TypeError: volume_square() argument after * must be an iterable, not int
Exception in thread Thread-8:
Traceback (most recent call last):
  File "C:\Program Files\Anaconda3\lib\threading.py", line 926, in _bootstrap_inner
    self.run()
  File "C:\Program Files\Anaconda3\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
TypeError: volume_cube() argument after * must be an iterable, not int

Khoan đã? Ngay khi bạn gọi phương thức start, nó dẫn đến lỗi. Đó là vì tham số args cần một tuple, và vì volume_cube và volume_square chỉ nhận một tham số, nên bạn cần đặt dấu phẩy trong tham số args sau khi chỉ định đối số đầu tiên của các hàm.

t1 = threading.Thread(target=volume_cube, args=(2,))
t2 = threading.Thread(target=volume_square, args=(3,))
t1.start()
t2.start()

t1.join()
t2.join()

print("Thread execution is complete!")
Volume of Cube: 8
Volume of Square: 9
Thread execution is complete!

Giờ hãy học cách dùng threading như một lớp con.

Threading như một lớp con

Trong phần này, bạn sẽ học cách tạo lớp con từ lớp Thread có trong mô-đun threading, rồi tạo các thể hiện luồng t1 và t2 từ lớp đó.

Bạn sẽ dùng lại hàm thread_delay ở đây.

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


    def run(self):
        print('Starting Thread:', self.name)
        thread_delay(self.name,self.delay)
        print('Execution of Thread:', self.name, 'is complete!')


t1 = DataCampThread('t1', 1)
t2 = DataCampThread('t2', 3)    

t1.start()
t2.start()

t1.join()
t2.join()

print("Thread execution is complete!")
Starting Thread: t1
Starting Thread: t2
t1 --------> 1588089083.4879806
t1 --------> 1588089084.489222
t1 --------> 1588089085.4909666
Execution of Thread: t1 is complete!
t2 --------> 1588089085.4979634
t2 --------> 1588089088.499365
t2 --------> 1588089091.5012596
Execution of Thread: t2 is complete!
Thread execution is complete!

Giờ hãy hiểu đoạn mã trên từng bước:

  • Bạn định nghĩa một lớp DataCampThread, và ở tham số lớp, bạn truyền vào lớp threading.Thread. Lý do là bạn muốn tạo một lớp con từ mô-đun threading. Làm vậy cho phép bạn dùng tất cả các phương thức có sẵn trong lớp threading.Thread tương tự khái niệm kế thừa.

  • Tiếp theo, bạn định nghĩa phương thức init của lớp DataCampThread và truyền vào hàm cùng với độ trễ. Init là hàm khởi tạo tương tự như các tham số bạn đã truyền cho lớp threading.Thread; ở đây bạn sẽ truyền cho lớp DataCampThread.

  • Sau đó bạn khởi tạo phương thức init của lớp chính threading.Thread rồi gán name và delay.

  • Tiếp theo, bạn định nghĩa phương thức run; nhớ lại ở đây bạn đã định nghĩa phương thức start, vốn thực chất sẽ gọi phương thức run ở tầng dưới. Vì bạn đã kế thừa lớp chính, bạn được phép thay đổi phương thức run. Do đó, khi bạn gọi phương thức start, mọi chỉnh sửa bạn thực hiện trong phương thức run sẽ được phản ánh.

Kết luận

Chúc mừng bạn đã hoàn thành hướng dẫn.

Hướng dẫn này là phần giới thiệu cơ bản về threading trong Python. Tuy nhiên, threading là một chủ đề rất rộng và quan trọng, và có thể một số khái niệm chưa được đề cập. Hãy thoải mái tìm hiểu thêm các phương thức threading như active count, current thread, v.v.

Nếu bạn muốn học thêm, hãy xem Xử lý song song dựa trên Thread của Python.

Nếu bạn mới bắt đầu với Python và muốn học thêm, hãy tham gia khóa học Giới thiệu Khoa học Dữ liệu với Python của DataCamp.

Chủ đề
Python

Khóa học Python

Khóa học

Nhập môn Python

4 giờ
7.1M
Nắm vững phân tích dữ liệu với Python chỉ trong 4 giờ. Khóa học online này giúp bạn làm quen với giao diện Python và các thư viện phổ biến.
Xem chi tiếtRight Arrow
Bắt Đầu Khóa Học
Xem thêmRight Arrow
Liên quan

blog

Claude Opus 4.6: Tính năng, Điểm chuẩn, Bài kiểm tra thực hành và hơn thế nữa

Mô hình mới nhất của Anthropic dẫn đầu ở mã hóa tác tử và lập luận phức tạp. Thêm vào đó, nó có cửa sổ ngữ cảnh 1M.
Matt Crabtree's photo

Matt Crabtree

10 phút

Xem ThêmXem Thêm