Có an toàn khi gọi print trong signal handler của Python không?

26 tháng 8, 2026·3 phút đọc

Bài viết phân tích vấn đề gọi hàm print bên trong signal handler của Python, tiết lộ rằng mặc dù CPython xử lý tín hiệu an toàn hơn C, nhưng trong điều kiện cực đoan (tín hiệu liên tục), việc gọi print có thể gây ra lỗi RuntimeError do reentrant call. Tác giả nhấn mạnh đây chỉ là kiến thức thú vị, không phải rủi ro thực tế, nhưng vẫn khuyên không nên thực hiện các tác vụ phức tạp trong signal handler.

Gọi print trong signal handler Python: An toàn hay không?

Signal handler trong Python luôn là chủ đề gây nhiều tranh cãi, đặc biệt khi lập trình viên cần xử lý tín hiệu hệ thống như SIGINT, SIGTERM hay SIGUSR1. Một câu hỏi tưởng chừng đơn giản nhưng lại ẩn chứa nhiều điều thú vị: liệu có an toàn khi gọi hàm print bên trong signal handler hay không?

Vì sao Python khác với C?

Khác với C, nơi signal handler chạy trực tiếp trong ngữ cảnh của tín hiệu và phải tuân thủ nghiêm ngặt danh sách các hàm an toàn (async-signal-safe functions), CPython sử dụng hai tầng xử lý tín hiệu. Tín hiệu đến sẽ được xử lý ở tầng C thấp, sau đó được lên lịch để gọi hàm Python handler khi interpreter ở trạng thái an toàn.

Điều này giúp Python signal handler tránh được những hạn chế khắt khe của C, nhưng không có nghĩa là hoàn toàn an toàn tuyệt đối.

Vấn đề nghiêm trọng: Reentrant call

Như đã đề cập trong loạt bài trước, Python signal handler có tính chất reentrant bất ngờ – nếu một tín hiệu khác đến trong khi handler đang chạy, Python có thể gọi lại handler đó ngay giữa lần thực thi đầu tiên.

Điều gì xảy ra nếu signal handler bị gọi đệ quy ngay giữa lúc đang thực hiện print? Tác giả bài viết đã thử nghiệm bằng cách tự gửi một loạt 50 tín hiệu liên tiếp:

import os, signal, subprocess

def sighandler(_signo, _frame):
    print("signal received")

signal.signal(signal.SIGUSR1, sighandler)
subprocess.run("for x in {1..50}; do kill -USR1 %s; done" % os.getpid(), shell=True)

Kết quả thu được trên máy thử nghiệm:

File "multiple_signals.py", line 6, in sighandler
print("signal received")
File "multiple_signals.py", line 6, in sighandler
print("signal received")
File "multiple_signals.py", line 6, in sighandler
print("signal received")
[Previous line repeated 2 more times]
RuntimeError: reentrant call inside '>

Đánh giá mức độ rủi ro thực tế

Điều quan trọng cần nhấn mạnh: lỗi này chỉ xảy ra trong điều kiện cực đoan, khi hàng chục tín hiệu được gửi đến trong thời gian rất ngắn. Trong thực tế, ít chương trình nào phải đối mặt với tình huống như vậy.

Nếu lỗi xảy ra, hậu quả chỉ là một exception – chương trình có thể bắt và xử lý được. So với việc gọi các hàm không an toàn trong C signal handler (có thể gây deadlock, hỏng cấu trúc dữ liệu, hoặc thất bại âm thầm), thì Python vẫn an toàn hơn rất nhiều.

Tuy nhiên, tác giả vẫn đưa ra lời khuyên rõ ràng:

Tôi xem đây là một kiến thức thú vị về signal, không phải là vấn đề thực tế khi viết signal handler – nhưng tôi vẫn khuyên không nên thực hiện các tác vụ phức tạp bên trong signal handler.

Kết luận và khuyến nghị cho lập trình viên Việt Nam

Đối với các lập trình viên Python tại Việt Nam, đặc biệt là những người làm việc với ứng dụng web, microservices, hoặc script tự động hóa, việc hiểu rõ về signal handler rất quan trọng. Một số gợi ý thực tế:

  • Hạn chế gọi print trong signal handler – thay vào đó, chỉ đặt cờ (flag) hoặc ghi vào hàng đợi (queue) để xử lý sau.
  • Sử dụng logging thay vì print nếu bắt buộc phải ghi log (nhưng cũng cần cẩn thận).
  • Trong các ứng dụng đồng thời (concurrent), cân nhắc sử dụng signal.set_wakeup_fd() để chuyển tín hiệu thành sự kiện I/O an toàn hơn.

Bản chất của vấn đề không nằm ở việc print có an toàn hay không, mà nằm ở việc bạn nên giữ signal handler càng đơn giản càng tốt – đúng như nguyên tắc vàng của lập trình hệ thống: "Signal handler không phải nơi để làm việc, mà là nơi để báo hiệu."

Chia sẻ:FacebookX
Nội dung tổng hợp bằng AI, mang tính tham khảo. Xem bài gốc ↗