Mô phỏng GC trong Go



  • GC là gì?

    GC - Garbage Collection (sự thu gom gác) hay Garbage Collector (bộ thu gom rác). Rác ở đây chính là những object, memory... sinh ra từ việc cấp phát động (trên heap). Sau khi kết thúc một chương trình (process) thì OS sẽ dọn cho bạn. Tuy nhiên trong nhiều trượng hợp bị lỗi, crash hay dump... thì sẽ không thể dọn hết, dẫn đến leak memory khiến vùng nhớ đó không thể truy cập được.

    Đối với các ngôn ngữ hệ thống thì bạn phải tuân thủ đúng quy định chơi xong phải dọn, không vứt bừa bãi 😂 và tất nhiên bạn sẽ nhận ngay nghề mới là lao công không công, nhưng nó không xấu vì chứng tỏ bạn là người văn minh 😂

    Chính vì lẽ đó mà GC ra đời nhằm mục đích giải quyết vấn đề lười-bẩn, ăn xong mà chẳng dọn, lại còn có thể gây nguy hiểm của anh em dev nói riêng hay người sử dụng ngôn ngữ nói chung 😂

    GC được phát minh bởi John McCarthy từ những 1959 trên ngôn ngữ Lisp. Nói về lịch sử của GC thì phải nhắc đến Lua, một trong những thằng tiên phong mở lối trong việc tích hợp luôn vào ngôn ngữ (v2.2 1995). Sau đó đến giải thuật Garbage-first collector của tụi Java. Còn Go thì được tích hợp ngay từ các phiên bản đầu tiên (2009-2010)...

    Về chức năng thì đúng theo nghĩa đen của nó, dồn hết rác lại một chỗ, khi nào không dùng thì dọn thôi. Một số khác như Lua, Go thì khi gán một object bằng nil, nó sẽ dọn ngay lặp tức hoặc sau khi xong một scope/block. Trong C++ có một khái niệm là Reference counting dùng để điếm các tham chiếu, có thể dọn hàng loạt những cái không dùng tới khi nó không tham gia đếm nữa 😂

    Mô phỏng

    Đầu tiên, mình sẽ tạo một struct dạng node như sau

    type Mem struct {
        ptr  interface{} 
        next *Mem
    }
    
    • ptr là một con trỏ, sẽ trỏ đến vị trí cấp phát bằng new
    • next sẽ trỏ đến một con trỏ tương tự, xoay vòng như vậy nên nó mới được gọi là node

    Tiếp tục tạo một biến global là một con trỏ Mem và gán bằng nil, sử dụng nó để trữ các object.

    var Obj *Mem= nil
    
    func addMem(ptr interface{}) {
        var mem = new(Mem)
        mem.ptr = ptr
        mem.next = Obj
        Obj = mem
    }
    

    Hàm addMem giúp thêm từng object mới vào bộ thu gom. Bên trong mình tạo mới một con trỏ Mem để nó trỏ đến một object cấp phát từ tham số #1. Sau đó gán next đến Obj hiện tại và tạo mới Obj bằng cách gán Mem vừa mới tạo.

    Ngoài ra, bạn có thể dùng unsafe để tạo một hàm giúp allocate object mới sau đó add vào bộ thu gom bằng hàm addMem và trả về con trỏ đến object 🙂

    Tiếp tục là hàm dọn rác:

    func cleanMem() {
        var mem = Obj
        for mem != nil {
            var next = mem.next // giữ next lại
            mem.ptr = nil // dọn hàng thôi
            mem = nil
            mem = next // gán next trở lại mem
        }
        Obj = nil // ok, xong rồi thì reset Obj
    }
    

    nếu không dùng biến mem làm trung gian thì cả Obj sẽ ăn hành 😂

    Hàm cleanMem sử dụng để dọn hết object đã thu gom, do Go không cho delete hay xóa bất cứ thứ gì đã cấp phát nên mình sẽ gán chúng bằng nil (Go sẽ dọn nó ngay). Ngoài ra bạn có thể sử dụng unsafe kèm CGo để có thể dùng malloc hoặc calloc như C thuần.

    Để tiện cho việc theo dõi nó có thực sự hoạt động hay không, mình sẽ thêm output vào.

    func cleanMem() {
        var co = 0
        ...
        for ...
    
            co++
        }
        fmt.Printf("cleaned %d objects\n", co)
    }
    

    Test thử

    func main() {
        var z = new(int)
        var t = new(float32)
        addMem(z)
        addMem(t)
    
        cleanMem()
    }
    

    Và output ngoài mong đợi luôn 🙂

    $ go run main.go
    cleaned 2 objects!
    

    Nâng cấp máy dọn rác

    Để cải tiến máy dọn rác, mình sẽ tích hợp AI hàng fake vào để giúp nó có thể lựa chọn, phân biệt từng loại rác để mà xử lý 😂

    Đơn giản thôi, đầu tiên ta cần một số constant để xác định nó thuộc loại nào.

    const (
        TpInt = 1    // intptr
        TpF32 = 2    // floatptr
        TpXXX = 69
        ...
    )
    

    Trong struct Mem, mình sẽ thêm một member int để gán type cho nó.

    type Mem struct {
        t      int
        ...
    }
    
    func addMem(ptr interface{}, t int) {
        ...
        mem.t = t
        ...
    }
    

    Và cuối cùng đến hàm dọn rác, chỉ cần phân loại ra bằng switch-case là xong ngay.

    func cleanMem() {
        ...
        for mem != nil {
            var next = mem.next
            switch mem.t {
                // thằng này phải dọn như vậy
                case TpInt:
                    mem.ptr = nil
                    break
                // thằng này lại dọn khác :))
                case TXXX: {
                    var xxx = _t69(mem.ptr)
                    goBed(xxx)
                    break
                }
                ...
            }
            mem = nil
        }
    ...
    

    • Vậy là xong, thật đơn giản phải không nào!
    • Code trên bạn có thể xem qua tại gist này.

  • Trùm cuối

    @wuuyi Bài viết chất quá, mà để dành đọc sau, giờ đọc đau não quá man 😂

    alt text



Có thể bạn cũng quan tâm

.
DMCA.com Protection Status