+4

[Phỏng vấn Golang] Pointer là gì và ưu nhược điểm của nó?

I. Pointer và ưu điểm?

Thông thường khi khai báo 1 biến, giá trị của biến đó sẽ được lưu vào trong vào 1 vùng nhớ. Còn với pointer nó sẽ lưu địa chỉ bộ nhớ của một biến khác. Con trỏ thường chiếm 4 - 8 byte nên khá là nhẹ.

1.Tối ưu hóa bộ nhớ

Chỉ mất 4-8 byte để lưu trữ giúp tối ưu hoá việc lưu trữ, tránh việc sao chép dữ liệu không cần thiết, đặc biệt với các cấu trúc dữ liệu lớn.

2.Thay đổi giá trị gốc

Cho phép bạn truyền con trỏ vào hàm thay vì truyền giá trị, bạn có thể thao tác trực tiếp. Hữu ích khi cần sửa đổi dữ liệu từ bên trong hàm.

3. Tạo cấu trúc dữ liệu động

Hữu ích trong việc tạo các cấu trúc dữ liệu phức tạp như danh sách liên kết, cây, đồ thị.

4. Biểu diễn giá trị nil

Con trỏ cho phép biểu diễn khái niệm "không có giá trị" (nil) cho các kiểu dữ liệu không thể có giá trị zero. Khi sử dụng con trỏ nil, chúng ta chỉ cần lưu trữ một giá trị nil (thường là một địa chỉ bằng 0), thay vì phải cấp phát bộ nhớ cho toàn bộ cấu trúc dữ liệu. Con trỏ nil cho phép chúng ta trì hoãn việc cấp phát bộ nhớ cho đến khi thực sự cần thiết.

5. Làm việc với slice và map

Con trỏ được sử dụng ngầm định trong slice và map, 2 loại dữ liệu rất hữu ích trong golang.

II. Nhược điểm

1.Độ phức tạp

Có thể khó hiểu và dễ gây lỗi cho người mới học. Với go thì cũng đơn giản đi nhiều rồi. Lạm dụng con trỏ có thể làm code khó đọc và bảo trì.

2. Nguy cơ lỗi null dereference

Khi một con trỏ chưa được khởi tạo hoặc trỏ đến giá trị nil mà lập trình viên cố tình dereference (truy cập giá trị tại địa chỉ mà con trỏ trỏ tới), sẽ dẫn đến lỗi truy cập vào vùng nhớ không hợp lệ, gây ra panic và dừng chương trình.

3. Race condition

Race condition là một hiện tượng xảy ra trong lập trình đồng thời khi hai hoặc nhiều tiến trình (hoặc luồng) cùng truy cập và thao tác trên dữ liệu chung mà không có cơ chế đồng bộ hóa phù hợp. Điều này có thể dẫn đến kết quả không xác định hoặc không mong muốn.Khi sử dụng con trỏ và goroutine có thể dẫn tới race condition

4. Cache missing

Cách hoạt động của bộ nhớ cache:

  • CPU có các tầng bộ nhớ cache (L1, L2, L3) để lưu trữ tạm thời dữ liệu từ RAM.
  • Cache nhanh hơn RAM nhiều lần, giúp CPU truy cập dữ liệu nhanh hơn.

Cache miss xảy ra khi CPU cố gắng đọc hoặc ghi dữ liệu từ bộ nhớ cache, nhưng dữ liệu đó không có trong cache. Điều này có thể xảy ra khi thao tác với con trỏ, bạn không truy cập trực tiếp vào dữ liệu mà đang gián tiếp truy cập thông qua một địa chỉ bộ nhớ. Dữ liệu tại địa chỉ này có thể không nằm trong cache, dẫn đến cache miss. Con trỏ có thể dẫn đến cache miss nhiều hơn so với các kiểu dữ liệu tuyến tính (như array) vì con trỏ có thể trỏ đến các vùng nhớ phân tán, thay vì liên tiếp.

// VD1: ác Node có thể nằm rải rác trong bộ nhớ. Có thể gây nhiều cache misses
type Node struct {
    Value int
    Next  *Node
}

// VD2: dữ liệu liền kề nhau, Ít cache misses hơn
type Node struct {
    Value [1000]int
}
nodes := make([]Node, 1000)

5. Overhead

Khi sử dụng con trỏ, CPU cần thực hiện ít nhất hai lần truy cập bộ nhớ:

  • Đọc địa chỉ từ con trỏ
  • Truy cập dữ liệu tại địa chỉ đó

Điều này tạo ra overhead so với truy cập trực tiếp vào dữ liệu. Tuy nhiên, overhead này thường nhỏ, nhưng khi bạn sử dụng nhiều con trỏ hoặc con trỏ đến những vùng nhớ không liền kề, nó có thể trở thành vấn đề.

Sử dụng nhiều con trỏ có thể tăng số lượng đối tượng mà Garbage Collection phải theo dõi và quản lý.

6. Branch prediction

Branch prediction là cơ chế mà CPU dự đoán hướng đi của một câu lệnh nhánh (như if), và nếu dự đoán đúng, CPU sẽ tiết kiệm thời gian.

Ví dụ, đến bạn đi từ SG tới ngã 3 Vũng Tàu, bạn chưa biết tiếp theo nên rẽ trái hay phải. Theo cách thông thường bạn phải mở bản đồ lên để xem đi hướng nào (if rẽ phải == true), rồi mới đi tiếp (then thực thi code). Nhưng để tiết kiệm thời gian thì CPU sẽ dựa vào trí nhớ của nó, rẽ trái trước đã rồi tính, đồng thời vẫn mở bản đồ ra xem. Nếu nó dự đoán đúng thì nó đi nhanh hơn được một khúc, nếu dự đoán sai thì nó phải quay lại và rẽ phải.

Kiểm tra null pointer có thể ảnh hưởng đến branch prediction của CPU (vì mất thêm thời gian kiểm tra). Nếu phải kiểm tra null pointer thường xuyên mà tần suất xảy ra khó đoán định, điều này có thể làm giảm hiệu quả dự đoán, dẫn đến việc CPU phải dừng lại để xử lý các trường hợp dự đoán sai, gây tốn thời gian và công sức của CPU, hiện tượng gọi là branch misprediction.

func sum(ptr *int, sum int) int {
    if ptr != nil { // Có thể gây branch misprediction
        sum += *ptr
    }
    return sum
}
func sum(val int, sum int) int {
    sum += val // Không cần kiểm tra, tránh misprediction
    return sum
}

III. Cách khắc phục

1. Sử dụng các công cụ phân tích tĩnh:

  • Các công cụ như go vet hay golangci-lint có thể giúp phát hiện lỗi tiềm ẩn liên quan đến con trỏ.

2. Sử dụng các biện pháp thay thế

  • Không dùng nếu không cần thiết để không phải lo các vấn đề tiềm ẩn. Dùng thì khai báo rõ ràng tý để tránh người sau khó đọc.
  • Sử dụng slices thay vì con trỏ mảng: Slices trong Go đã tích hợp sẵn cơ chế con trỏ, dễ sử dụng và an toàn hơn.

3. Xử lý null deference

Sử dụng deferrecover có thể giúp chương trình không bị crash. Tuy nhiên, đây không phải là cách khắc phục tốt nhất. Thay vào đó, phòng bệnh hơn chữa bệnh, kiểm tra trước xem nó có nil không để tránh panic ngay từ đầu. Recover chỉ nên được sử dụng khi không thể dự đoán lỗi trước, hoặc trong các trường hợp ngoại lệ.

4. Xử lý race condition

  • Sử dụng Go Race Detector: Chạy chương trình với flag race để phát hiện race conditions:

    Copy
    go run -race myprogram.go
    
    

Thiết kế để tránh chia sẻ dữ liệu: Cố gắng thiết kế chương trình để mỗi goroutine làm việc với dữ liệu riêng, giảm thiểu việc chia sẻ thông qua con trỏ.

  • Sử dụng atomic operation
  • Sử dụng read-write mutex cho trường hợp đọc nhiều, ghi ít
  • Sử dụng sync/atomic package cho các phép toán đơn giản:
  • Sử dụng channels để đồng bộ hóa

Tham khảo

Group discord 2k+ mems: chém gió về lập trình và làm pet project cùng nhau


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí