Cộng đồng lập trình đang tham gia vào một cuộc tranh luận sôi nổi về việc liệu Go có thể thực sự được coi là một ngôn ngữ an toàn về bộ nhớ hay không. Cuộc thảo luận tập trung xung quanh một câu hỏi cơ bản: một ngôn ngữ có thể tuyên bố tính an toàn bộ nhớ khi lập trình đồng thời có thể dẫn đến hành vi không xác định và các lỗ hổng bảo mật tiềm ẩn không?
Tranh cãi bắt nguồn từ cách tiếp cận của Go trong việc xử lý các cấu trúc dữ liệu đa từ như interface và slice trong môi trường đồng thời. Không giống như các ngôn ngữ như Java hoặc C#, đã đầu tư mạnh mẽ để đảm bảo rằng ngay cả các chương trình có data race vẫn được định nghĩa rõ ràng, Go cho phép một số điều kiện race phá vỡ các đảm bảo an toàn cơ bản của ngôn ngữ.
Vấn đề kỹ thuật
Khi nhiều goroutine truy cập đồng thời vào các kiểu interface hoặc slice của Go mà không có đồng bộ hóa thích hợp, runtime có thể gặp phải tình trạng torn read - các tình huống mà một thread đọc dữ liệu được cập nhật một phần. Điều này xảy ra vì các cấu trúc dữ liệu này được triển khai dưới dạng các giá trị đa từ không thể được cập nhật một cách nguyên tử trên hầu hết các bộ xử lý.
Vấn đề biểu hiện khi một goroutine cập nhật một biến interface trong khi một goroutine khác đọc nó. Goroutine đọc có thể quan sát sự kết hợp giữa dữ liệu cũ và mới, có khả năng coi một giá trị số nguyên như một con trỏ bộ nhớ. Điều này có thể dẫn đến lỗi segmentation fault khi chương trình cố gắng dereference các địa chỉ bộ nhớ không hợp lệ.
Các Cấu Trúc Dữ Liệu Có Vấn Đề Của Go
- Kiểu Interface: Được lưu trữ dưới dạng cặp con trỏ (dữ liệu + vtable) không thể được cập nhật một cách nguyên tử
- Slice: Cấu trúc đa từ chứa con trỏ dữ liệu, độ dài và dung lượng
- Map: Không an toàn luồng theo thiết kế, có thể gây ra lỗi dữ liệu khi truy cập đồng thời
- Channel: An toàn cho trường hợp sử dụng dự định nhưng không ngăn chặn việc chia sẻ dữ liệu không an toàn
Phản ứng của cộng đồng và tác động ngành công nghiệp
Cộng đồng lập trình đã có những phản ứng trái chiều với những phát hiện này. Một số nhà phát triển cho rằng điều này thể hiện một khoảng cách đáng kể giữa tính an toàn được quảng cáo của Go và các đảm bảo thực tế của nó. Những người khác tranh luận rằng những tình huống như vậy hiếm khi xảy ra trong thực tế và race detector tích hợp của Go giúp xác định những vấn đề này trong quá trình phát triển.
Mỗi lần cuộc trò chuyện này được nhắc đến, tôi lại nhớ đến nhóm của mình tại Dropbox , nơi mà việc các kỹ sư mới giới thiệu một segfault trong server Go của chúng tôi bằng cách không đồng bộ hóa việc ghi vào một cấu trúc dữ liệu là một nghi thức bắt buộc.
Cuộc tranh luận có những tác động rộng lớn hơn đối với cách ngành công nghiệp phân loại các ngôn ngữ lập trình. Các tổ chức như NSA và nhiều cơ quan chính phủ khác đã đưa Go vào danh sách các ngôn ngữ an toàn về bộ nhớ, những khuyến nghị có thể cần được xem xét lại do những hạn chế kỹ thuật này.
Bối cảnh rộng lớn hơn về tính an toàn bộ nhớ
Cuộc thảo luận này làm nổi bật một sự bất đồng cơ bản về thuật ngữ trong thế giới lập trình. Các chuyên gia bảo mật thường định nghĩa tính an toàn bộ nhớ dựa trên tiềm năng khai thác thực tế, trong khi các nhà lý thuyết ngôn ngữ lập trình tập trung vào các đảm bảo chính thức và việc ngăn chặn hành vi không xác định.
Các ngôn ngữ như Rust và Swift đã có những cách tiếp cận khác nhau, sử dụng các hệ thống kiểu phức tạp để ngăn chặn hoàn toàn data race. Java và C# đã đầu tư vào các mô hình bộ nhớ đảm bảo rằng ngay cả các chương trình có race vẫn được định nghĩa rõ ràng, mặc dù có thể không chính xác về mặt logic.
So sánh An toàn Bộ nhớ theo Ngôn ngữ
Ngôn ngữ | Mô hình Bộ nhớ | Xử lý Data Race | An toàn Chính thức |
---|---|---|---|
Go | Cơ bản | Có thể gây ra UB | Hạn chế |
Java | JMM ( Java Memory Model ) | Hành vi được định nghĩa rõ | Mạnh |
C | CLR Memory Model | Hành vi được định nghĩa rõ | Mạnh |
Rust | Ownership + Send/Sync | Ngăn chặn tại thời điểm biên dịch | Mạnh |
Swift | Strict Concurrency | Ngăn chặn tại thời điểm biên dịch | Mạnh |
JavaScript | Đơn luồng + Web Workers | Truyền thông điệp | Mạnh |
Kết luận
Mặc dù Go vẫn an toàn hơn đáng kể so với các ngôn ngữ như C hoặc C++, bằng chứng cho thấy nó không thể cung cấp cùng mức độ đảm bảo an toàn bộ nhớ như các ngôn ngữ thực sự an toàn. Đối với hầu hết các ứng dụng, tính an toàn thực tế của Go có thể là đủ, nhưng các tổ chức yêu cầu đảm bảo an toàn chính thức nên cân nhắc cẩn thận những hạn chế này khi đưa ra lựa chọn công nghệ.
Cuộc tranh luận cuối cùng phản ánh sự hiểu biết đang phát triển về những gì cấu thành nên lập trình an toàn và sự đánh đổi giữa hiệu suất, sự đơn giản và tính chính xác chính thức trong phát triển phần mềm hiện đại.