Ghi Chú Phát Triển (24): Chia Sẻ Dữ Liệu Giữa Các Lua State
Sau khi mở rộng quy mô làm việc gần đây, hiện tại chúng tôi có tổng cộng 10 lập trình viên đang tham gia vào dự án. Trong thời gian này, tôi tạm thời không cần phải phối hợp trực tiếp với các thành viên khác. Tuần vừa qua tôi tập trung tối ưu lại một số tính năng cũ, nâng cao hiệu năng mà không thay đổi giao diện, đồng thời hoàn thiện những chức năng còn dang dở để chuẩn bị cho việc mở rộng sau này.
Một trong những điểm đáng chú ý gần đây liên quan đến cấu trúc dữ liệu dùng chung trong hệ thống.
Thiết kế ban đầu: Hướng đến chia sẻ đa tiến trình
Ban đầu, khi thiết kế hệ thống, chúng tôi có mục tiêu hỗ trợ chia sẻ dữ liệu cấu trúc giữa nhiều tiến trình thông qua bộ nhớ chia sẻ. Tuy nhiên, thiết kế này gặp phải hai thách thức lớn:
- Địa chỉ bộ nhớ chia sẻ không đồng nhất giữa các tiến trình – không thể sử dụng con trỏ để trỏ đến các phần tử trong cấu trúc dữ liệu.
- Vấn đề đồng bộ hóa – tránh sử dụng các cơ chế khóa phức tạp để đảm bảo an toàn khi đọc/ghi đồng thời.
Tuy nhiên, sau khi chuyển sang sử dụng khung xử lý Erlang, chúng tôi nhận ra thiết kế ban đầu quá phức tạp. Vì Erlang hoạt động trên cơ chế actor đơn tiến trình nên tôi đã dành 3 ngày để viết lại một phiên bản mới, đơn giản hơn, tập trung vào việc chia sẻ dữ liệu giữa các thread trong cùng một tiến trình, cụ thể là giữa các Lua State độc lập.
Giải pháp hiện tại: Tối ưu cho Lua State
Giải pháp mới chỉ hỗ trợ chia sẻ dữ liệu cấu trúc tương tự như kiểu table
trong Lua, không yêu cầu phức tạp như phiên bản ban đầu. Hiện tại, tôi đã hoàn thiện và công khai mã nguồn trên GitHub tại địa chỉ:
Mô-đun này cung cấp hai cấp API:
- Raw API: Giao diện cấp thấp, kết nối trực tiếp với các hàm C và cấu trúc dữ liệu thuần C. Lớp này không phụ thuộc vào Lua, có thể sử dụng độc lập.
- Lớp bọc Lua:封装 đơn giản, sử dụng
lightuserdata
thay vì tạouserdata
để lưu trữ con trỏ.
Cơ chế hoạt động:
- Tạo cấu trúc dữ liệu trong một thread ghi thông qua Lua State.
- Truyền con trỏ sang Lua State của thread đọc.
- Sử dụng Raw API để truy cập dữ liệu qua con trỏ.
Tất cả thao tác đọc/ghi đều được đảm bảo an toàn đa luồng (thread-safe).
Vì sao không dùng cấu trúc không khóa (lock-free)?
Ban đầu tôi từng cân nhắc sử dụng thuật toán không khóa như HAMT hoặc Ctrie. Tuy nhiên, trong C/C++ việc quản lý vòng đời của các bản sao (snapshot) trong môi trường đa luồng cực kỳ phức tạp do thiếu cơ chế GC (garbage collection) cấp ngôn ngữ.
Ví dụ:
- Khi sửa đổi cấu trúc dữ liệu, bạn cần tạo bản sao, chỉnh sửa, rồi dùng CAS (Compare-And-Swap) để cập nhật.
- Các thread đọc có thể đang truy cập phiên bản cũ, dẫn đến việc phải hủy các bản sao lỗi thời một cách an toàn.
Giải pháp như Hazard Pointer có thể giải quyết vấn đề này, nhưng đòi hỏi lượng mã nguồn lớn hơn cả cấu trúc dữ liệu cần triển khai. Vì vậy, tôi quyết định dùng khóa đơn giản để quản lý việc tăng/giảm tham chiếu.
Tối ưu cho Lua: Meta-table và kiểu dữ liệu
Để tương thích với Lua, tôi thêm meta-information vào cấu trúc C, giúp Lua có thể truy cập như một table
thông thường. Các cải tiến bao gồm:
- Meta-table duy nhất cho mỗi kiểu dữ liệu (thay vì tạo riêng cho từng đối tượng như trước), giúp tiết kiệm bộ nhớ.
- Chuyển đổi khóa chuỗi sang khóa số để tăng tốc độ truy cập từ C.
- Thêm thông tin kích thước cho cấu trúc dạng mảng, giúp thao tác thuận tiện hơn.
Hướng phát triển tiếp theo
- Tích hợp từ điển nguyên tử (atomic dictionary): Tôi đang xem xét dùng STM (Software Transactional Memory) như TinySTM, nhưng lo ngại việc phụ thuộc quá nhiều thư viện bên ngoài.
- Hỗ trợ kiểu int64: Trong phiên bản 64-bit, tôi dùng
lightuserdata
để mô phỏng hiệu quả. Tuy nhiên, phần này sẽ không đưa vào mã nguồn công khai do phụ thuộc nền tảng. - Khắc phục hạn chế của
__eq
: Lua không kích hoạt__eq
cholightuserdata
, nên không thể tự động chuyển đổi kiểu số sanglightuserdata
.
Công cụ tuần hoàn (Serialization) cho Lua
Trong quá trình phát triển, tôi nhận thấy cần một công cụ tuần hoàn dữ liệu Lua để truyền tham số RPC giữa các thread. Thay vì dùng Protocol Buffer, tôi viết một mô-đun tuần hoàn đơn giản, chuyển đổi trực tiếp Lua Value thành dữ liệu nhị phân. Ưu điểm:
- Nhanh hơn do không cần định nghĩa file
.proto
. - Tối ưu cho Lua, hiệu năng cao hơn định dạng通用.
Mã nguồn đã được công khai tại:
Kết luận
Mô-đun lua-stable
hiện tại khá độc lập, phù hợp để chia sẻ cộng đồng. Các cải tiến sau này sẽ tích hợp sâu với module dự án cụ thể, nên có thể không công khai. Nếu bạn quan tâm, hãy ghé thăm GitHub của tôi để xem chi tiết!