Xử Lý Đối Tượng Lặp Chính Xác - nói dối e blog

Xử Lý Đối Tượng Lặp Chính Xác

Hôm qua khi xây dựng một module AOI, tôi lại gặp phải bài toán cũ về việc lặp đối tượng trong thiết kế hệ thống. Đây là vấn đề cần được xử lý cẩn trọng, xin ghi lại kinh nghiệm này.

Nguyên nhân sự cố: Khi đối tượng A bước vào vùng AOI của B sẽ kích hoạt sự kiện Enter. Sự kiện này được xử lý thông qua cơ chế callback. Nếu trong hàm callback lại tiếp tục gọi đến module AOI, tạo ra đệ quy gián tiếp, rất dễ làm gián đoạn quá trình lặp nội bộ của module AOI.

Nguy hiểm hơn, nếu callback xóa một số đối tượng liên quan, có thể dẫn đến lỗi truy cập bộ nhớ đã giải phóng. Những vấn đề kiểu này thường xuyên xuất hiện trong các framework quản lý đối tượng. Ngoài module AOI, chúng cũng rất phổ biến trong thiết kế module GUI.

Giải pháp của tôi: Do tính chất khó lường của logic trong hàm callback, tôi luôn đặt phần xử lý chính ở cuối mỗi API. Nếu để xử lý thực sự ở giữa, khả năng cao là ngữ cảnh nội bộ module sẽ bị phá vỡ do đệ quy gián tiếp.

Giải pháp tối ưu là tạo một hàng đợi xử lý tin nhắn độc nhất trong môi trường module. Tất cả các tin nhắn có thể kích hoạt callback đều được đưa vào hàng đợi này. Vì hàng đợi chỉ thay đổi trạng thái qua hai phương thức enter và leave, việc lặp qua hàng đợi sẽ không gặp vấn đề.

Vấn đề giải phóng đối tượng: Khi sử dụng C/C++ (không có cơ chế GC), thời điểm giải phóng đối tượng là bài toán đau đầu. Bất kỳ callback nào cũng có thể xóa đối tượng, trong khi con trỏ đến đối tượng đó có thể đang nằm trong hàng đợi tin nhắn.

Tôi không khuyến khích dùng smart pointer vì:

  • Phải sửa toàn bộ hệ thống dùng smart pointer
  • Giao diện API sẽ bị “bẩn” do lộ lớp smart pointer
  • Tốn kém hiệu năng do overhead của smart pointer

Hai phương án khả thi:

  1. Dùng ID duy nhất: Thay vì lưu con trỏ, dùng ID tồn tại duy nhất trong suốt quá trình chạy. Kết hợp bảng băm để ánh xạ ID sang đối tượng. Khi đối tượng bị xóa, ID sẽ không còn hiệu lực.
  2. Con trỏ gián tiếp: Sử dụng con trỏ trung gian chứa cờ đánh dấu trạng thái. Khi đối tượng bị xóa, cờ này sẽ được kích hoạt. Con trỏ gián tiếp được cấp phát từ pool riêng và thu gom định kỳ.

Tôi chọn phương án 2 vì hiệu quả cao hơn. Chi tiết triển khai như sau:

Cơ chế thu gom rác ẩn:

  • Tự động kích hoạt khi tạo đối tượng mới (thời điểm cần tài nguyên)
  • Chỉ thực hiện khi hàng đợi tin nhắn đang rỗng
  • Quy trình 2 bước:
    1. Kiểm tra cờ trạng thái trên tất cả con trỏ gián tiếp để loại bỏ đối tượng đã xóa
    2. Xóa toàn bộ con trỏ gián tiếp khi hàng đợi trống

Khi triển khai đầy đủ các cơ chế trên, hệ thống sẽ đạt độ an toàn gần như tuyệt đối. 😊

0%