Top 25 câu hỏi phỏng vấn Go: hướng dẫn dành cho nhà phát triển

Chinh phục buổi phỏng vấn Go với 25 câu hỏi được hỏi nhiều nhất. Goroutine, channel, interface và mẫu đồng thời kèm ví dụ mã.

Câu hỏi phỏng vấn Go - Hướng dẫn chuẩn bị toàn diện

Phỏng vấn kỹ thuật Go đánh giá khả năng nắm vững các khái niệm cốt lõi của ngôn ngữ: tính đồng thời, quản lý bộ nhớ và các mẫu thiết kế đặc trưng. Hướng dẫn này tập hợp 25 câu hỏi được hỏi nhiều nhất kèm câu trả lời chi tiết và ví dụ mã.

Lời khuyên cho buổi phỏng vấn

Go đề cao sự đơn giản và dễ đọc. Người phỏng vấn ưu tiên những câu trả lời ngắn gọn nhưng thể hiện hiểu biết sâu hơn là các giải pháp phức tạp quá mức.

Nền tảng ngôn ngữ Go

1. var:= khác nhau ở điểm nào?

Khai báo var cho phép chỉ định kiểu dữ liệu rõ ràng và hoạt động ở cấp package. Toán tử := tự suy luận kiểu nhưng chỉ dùng được bên trong các hàm.

declaration.gogo
package main

// Package level - var required
var globalConfig = "production"

func main() {
    // var with explicit type
    var count int = 10

    // var with type inference
    var name = "Alice"

    // Short declaration - functions only
    age := 25

    // Multiple declarations
    var (
        host = "localhost"
        port = 8080
    )
}

Trong hàm, khai báo ngắn := được ưa chuộng nhờ tính cô đọng, còn var vẫn cần thiết cho các biến cấp package.

2. Hệ thống kiểu của Go hoạt động như thế nào?

Go dùng kiểu tĩnh kết hợp suy luận kiểu. Ngôn ngữ phân biệt kiểu giá trị (sao chép khi gán) với kiểu tham chiếu (chia sẻ cùng cấu trúc nền tảng).

types.gogo
package main

import "fmt"

func main() {
    // Value types - full copy
    a := [3]int{1, 2, 3}
    b := a          // Copies the array
    b[0] = 100      // Doesn't modify a
    fmt.Println(a)  // [1 2 3]

    // Reference types - share data
    slice1 := []int{1, 2, 3}
    slice2 := slice1    // Same underlying array
    slice2[0] = 100     // Also modifies slice1
    fmt.Println(slice1) // [100 2 3]

    // Maps are also references
    m1 := map[string]int{"a": 1}
    m2 := m1
    m2["a"] = 100
    fmt.Println(m1["a"]) // 100
}

Mảng là kiểu giá trị, còn slice, map và channel là kiểu tham chiếu.

3. Phân biệt giữa mảng và slice

Mảng có kích thước cố định được xác định lúc biên dịch. Slice là góc nhìn động trên mảng nền với ba thành phần: con trỏ, độ dài và sức chứa.

arrays_slices.gogo
package main

import "fmt"

func main() {
    // Array - fixed size, value type
    arr := [5]int{1, 2, 3, 4, 5}

    // Slice - view over the array
    slice := arr[1:4]  // [2 3 4]
    fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice))
    // len=3, cap=4

    // Modifications affect original array
    slice[0] = 20
    fmt.Println(arr) // [1 20 3 4 5]

    // Direct creation with make
    dynamic := make([]int, 3, 10)
    // len=3, cap=10

    // Append may reallocate
    dynamic = append(dynamic, 1, 2, 3, 4, 5)
}

Trong Go, slice là kiểu được ưa chuộng cho các tập hợp động.

4. Câu lệnh defer hoạt động ra sao?

defer lên lịch một lời gọi hàm để chạy ở cuối hàm bao quanh. Các lời gọi bị hoãn xếp chồng và được thực thi theo thứ tự LIFO (vào sau, ra trước).

defer.gogo
package main

import (
    "fmt"
    "os"
)

func main() {
    // LIFO order
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // Prints: 3, 2, 1
}

// Typical use case: resource cleanup
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close() // Always executes

    // Read file...
    return os.ReadFile(path)
}

// Caution: arguments are evaluated immediately
func deferArgs() {
    x := 10
    defer fmt.Println(x) // Captures 10
    x = 20
    // Prints: 10
}

defer đảm bảo việc thực thi ngay cả khi xảy ra panic, vì vậy rất phù hợp cho việc dọn dẹp tài nguyên.

5. Interface trong Go là gì?

Interface mô tả một tập hợp method. Bất kỳ kiểu nào triển khai các method đó đều mặc nhiên thỏa mãn interface, không cần khai báo tường minh.

interfaces.gogo
package main

import "fmt"

// Interface definition
type Writer interface {
    Write([]byte) (int, error)
}

// Type that implicitly implements Writer
type FileLogger struct {
    path string
}

func (f *FileLogger) Write(data []byte) (int, error) {
    // Write to file
    fmt.Println("Writing to", f.path)
    return len(data), nil
}

// Empty interface - accepts any type
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
func process(w Writer) {
    // Type check
    if fl, ok := w.(*FileLogger); ok {
        fmt.Println("FileLogger with path:", fl.path)
    }
}

Việc triển khai interface ngầm định giúp các package được tách rời mạnh mẽ.

Tính đồng thời và goroutine

6. Goroutine là gì và khác thread thế nào?

Goroutine là một thread nhẹ do runtime của Go quản lý. Nó chiếm vài KB stack (so với vài MB của thread hệ thống) và bộ lập lịch của Go ghép kênh hàng nghìn goroutine vào một số ít thread hệ thống.

goroutines.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup

    // Launch 1000 goroutines
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Goroutine %d finished\n", id)
        }(i) // Pass i by value
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
}
Cạm bẫy phổ biến

Biến lặp luôn nên được truyền theo giá trị tới goroutine. Nếu không, mọi goroutine có thể giữ lại cùng một giá trị cuối cùng.

7. Channel hoạt động ra sao?

Channel tạo ra cơ chế giao tiếp và đồng bộ giữa các goroutine. Chúng có thể có buffer (kèm sức chứa) hoặc không buffer (đồng bộ).

channels.gogo
package main

import "fmt"

func main() {
    // Unbuffered channel - blocks until received
    ch := make(chan int)

    go func() {
        ch <- 42 // Blocks until read
    }()

    value := <-ch // Receives value
    fmt.Println(value)

    // Buffered channel - doesn't block until full
    buffered := make(chan string, 2)
    buffered <- "first"
    buffered <- "second"
    // buffered <- "third" // Would block

    fmt.Println(<-buffered) // "first"
    fmt.Println(<-buffered) // "second"
}

Channel không buffer đảm bảo đồng bộ, còn channel có buffer cho phép tách rời theo thời gian giữa bên gửi và bên nhận.

8. Cách dùng select với nhiều channel?

select chờ đồng thời nhiều thao tác trên channel. Thao tác sẵn sàng đầu tiên sẽ được thực hiện; nếu nhiều thao tác sẵn sàng cùng lúc, lựa chọn diễn ra ngẫu nhiên.

select.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait with timeout
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout")
        }
    }

    // Non-blocking select with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("No message available")
    }
}

select là công cụ cốt lõi để quản lý tính đồng thời trong Go một cách tinh tế.

9. Làm sao tránh race condition?

Race condition xảy ra khi nhiều goroutine truy cập dữ liệu chung mà không đồng bộ. Go cung cấp nhiều cơ chế bảo vệ.

race_conditions.gogo
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// Solution 1: Mutex
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

// Solution 2: RWMutex for read-heavy workloads
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // Multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Single writer
    defer c.mu.Unlock()
    c.data[key] = value
}

// Solution 3: atomic for simple counters
var atomicCounter int64

func incrementAtomic() {
    atomic.AddInt64(&atomicCounter, 1)
}

func main() {
    // Detection: go run -race main.go
    counter := SafeCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Count:", counter.count)
}

Cờ trình biên dịch -race phát hiện race condition lúc chạy.

10. Hãy giải thích mẫu worker pool

Mẫu worker pool giới hạn tính đồng thời bằng cách tạo một số lượng cố định goroutine xử lý các tác vụ từ một hàng đợi.

worker_pool.gogo
package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(100 * time.Millisecond) // Simulate work
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    var wg sync.WaitGroup

    // Start workers
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // Wait and close results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Collect results
    for result := range results {
        fmt.Println("Result:", result)
    }
}

Mẫu này giúp tránh tốn bộ nhớ và CPU do sinh ra quá nhiều goroutine.

Sẵn sàng chinh phục phỏng vấn Go?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Xử lý lỗi và panic/recover

11. Xử lý lỗi trong Go diễn ra thế nào?

Go dùng giá trị trả về tường minh cho lỗi, không có exception. Theo quy ước, error là tham số trả về cuối cùng.

errors.gogo
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors for comparison
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("access unauthorized")
)

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "must be positive",
        }
    }
    return nil
}

func main() {
    // Basic check
    if err := validateAge(-5); err != nil {
        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }
    }

    // Sentinel error comparison
    err := findUser("unknown")
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found")
    }
}

func findUser(id string) error {
    // Error wrapping with context
    return fmt.Errorf("findUser %s: %w", id, ErrNotFound)
}

Bao bọc với %w xâu chuỗi các lỗi mà vẫn cho phép kiểm tra lỗi gốc.

12. Khi nào dùng panic và recover?

panic chấm dứt luồng bình thường và mở stack ngược trở lại. recover bắt panic trong một defer và cho phép tiếp tục thực thi.

panic_recover.gogo
package main

import "fmt"

func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()

    riskyOperation()
    return nil
}

func riskyOperation() {
    // Simulates an operation that can panic
    panic("something went wrong")
}

// Legitimate use case: initialization validation
func MustCompileRegex(pattern string) *Regexp {
    r, err := regexp.Compile(pattern)
    if err != nil {
        panic(err) // Programming error
    }
    return r
}

func main() {
    err := safeOperation()
    if err != nil {
        fmt.Println("Recovered error:", err)
    }
    fmt.Println("Program continues")
}
Quy tắc vàng

Panic chỉ nên dành cho lỗi lập trình (vi phạm bất biến). Với lỗi đã lường trước (thiếu file, lỗi mạng), tốt nhất là luôn trả về error.

Struct, method và embedding

13. Receiver theo giá trị và theo con trỏ khác nhau ra sao?

Receiver theo giá trị nhận một bản sao của struct, còn receiver theo con trỏ nhận một tham chiếu và có thể thay đổi bản gốc.

receivers.gogo
package main

import "fmt"

type Counter struct {
    value int
}

// Value receiver - works on copy
func (c Counter) GetValue() int {
    return c.value
}

// Pointer receiver - modifies original
func (c *Counter) Increment() {
    c.value++
}

// Pointer receiver for large structs (avoids copy)
type LargeStruct struct {
    data [1000]int
}

func (l *LargeStruct) Process() {
    // Avoids copying 8000 bytes
}

func main() {
    c := Counter{value: 0}
    c.Increment() // Go automatically converts
    fmt.Println(c.GetValue()) // 1

    // Careful with interfaces
    var _ fmt.Stringer = &c // OK if method on *Counter
}

Quy tắc: nếu một method dùng receiver theo con trỏ, mọi method khác của kiểu cũng nên dùng receiver theo con trỏ để bảo đảm nhất quán.

14. Embedding trong Go hoạt động ra sao?

Embedding nhúng một kiểu vào kiểu khác và kế thừa method cùng field. Đây không phải kế thừa cổ điển mà là composition.

embedding.gogo
package main

import "fmt"

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

// Embedding Logger
type Service struct {
    *Logger // Pointer embedding
    name    string
}

func NewService(name string) *Service {
    return &Service{
        Logger: &Logger{prefix: name},
        name:   name,
    }
}

func main() {
    svc := NewService("API")

    // Promoted method - direct access
    svc.Log("Starting")

    // Explicit access also works
    svc.Logger.Log("Explicit")

    // Promoted field
    fmt.Println(svc.prefix) // "API"
}

Embedding mang lại sự linh hoạt khi tổ hợp các kiểu mà tránh được sự cứng nhắc của kế thừa.

15. Triển khai mẫu singleton trong Go thế nào?

Package sync cung cấp sync.Once để bảo đảm việc khởi tạo chỉ chạy một lần ngay cả với nhiều goroutine đồng thời.

singleton.gogo
package main

import (
    "fmt"
    "sync"
)

type Database struct {
    connectionString string
}

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        fmt.Println("Single initialization")
        instance = &Database{
            connectionString: "postgres://...",
        }
    })
    return instance
}

func main() {
    // Concurrent calls - single initialization
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            db := GetDatabase()
            fmt.Printf("Instance: %p\n", db)
        }()
    }
    wg.Wait()
}

sync.Once an toàn trong môi trường đa luồng và gọn gàng hơn so với mutex kết hợp double-check locking.

Context và hủy bỏ

16. Package context phục vụ gì?

Package context quản lý deadline, tín hiệu hủy và các giá trị gắn với một request xuyên suốt cây gọi hàm.

context.gogo
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Context with timeout
    ctx, cancel := context.WithTimeout(
        context.Background(),
        2*time.Second,
    )
    defer cancel() // Always call cancel

    result := make(chan string, 1)

    go func() {
        // Simulate long operation
        time.Sleep(3 * time.Second)
        result <- "completed"
    }()

    select {
    case res := <-result:
        fmt.Println(res)
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err())
    }
}

// Propagation through functions
func fetchData(ctx context.Context, url string) ([]byte, error) {
    // Early check
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // HTTP client respects context
    resp, err := http.DefaultClient.Do(req)
    // ...
}

Mọi hàm có thể chạy lâu nên nhận context.Context ở vị trí tham số đầu tiên.

17. Làm sao thực hiện graceful shutdown cho chương trình?

Các tín hiệu hệ thống như SIGINT và SIGTERM có thể được bắt để cho phép tắt chương trình một cách sạch sẽ.

graceful_shutdown.gogo
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // Context cancelled on signal
    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGINT,
        syscall.SIGTERM,
    )
    defer stop()

    // Start server
    server := startServer()

    // Wait for signal
    <-ctx.Done()
    fmt.Println("\nShutting down...")

    // Timeout for graceful shutdown
    shutdownCtx, cancel := context.WithTimeout(
        context.Background(),
        5*time.Second,
    )
    defer cancel()

    if err := server.Shutdown(shutdownCtx); err != nil {
        fmt.Println("Shutdown error:", err)
    }

    fmt.Println("Shutdown complete")
}

Mẫu này đảm bảo các kết nối đang hoạt động được hoàn tất đúng cách trước khi dừng hẳn.

Test và benchmark

18. Viết test trong Go bằng cách nào?

Package có sẵn testing cung cấp các chức năng cơ bản. Test nằm trong các file *_test.go.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

// Table-driven tests
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"mixed", -1, 5, 4},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Table-driven test là mẫu đặc trưng của Go để kiểm thử nhiều trường hợp cùng lúc.

19. Viết benchmark thế nào?

Benchmark dùng testing.B và chạy bằng go test -bench.

benchmark_test.gogo
package main

import (
    "strings"
    "testing"
)

func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("a")
        }
        _ = sb.String()
    }
}

// Typical results:
// BenchmarkStringConcat-8      50000    28000 ns/op
// BenchmarkStringBuilder-8   1000000     1200 ns/op

Benchmark phơi bày sự khác biệt về hiệu năng giữa các phương án.

Generics (Go 1.18+)

20. Cách dùng generics trong Go?

Go 1.18 giới thiệu tham số kiểu, cho phép viết mã tổng quát mà vẫn giữ tính an toàn về kiểu.

generics.gogo
package main

import "fmt"

// Generic function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Custom type constraint
type Number interface {
    int | int64 | float64
}

func Sum[T Number](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func main() {
    // Usage
    doubled := Map([]int{1, 2, 3}, func(n int) int {
        return n * 2
    })
    fmt.Println(doubled) // [2 4 6]

    fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15

    stack := &Stack[string]{}
    stack.Push("hello")
    stack.Push("world")
    val, _ := stack.Pop()
    fmt.Println(val) // "world"
}

Generics loại bỏ nhu cầu sao chép mã hoặc dùng interface{}.

Module và phụ thuộc

21. Hệ thống module Go vận hành thế nào?

Module Go quản lý phụ thuộc với phiên bản hóa theo ngữ nghĩa. Tệp go.mod định nghĩa module và các phụ thuộc của nó.

go.mod examplego
module github.com/user/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)

// Essential commands:
// go mod init github.com/user/project
// go mod tidy        - clean dependencies
// go get package@v1.2.3 - add/update
// go mod vendor      - copy locally
bash
# Updating dependencies
go get -u ./...           # All dependencies
go get -u=patch ./...     # Patches only

Tệp go.sum chứa checksum mật mã để bảo đảm tính toàn vẹn của phụ thuộc.

22. Cách tổ chức một dự án Go?

Cấu trúc chuẩn dựa trên quy ước cộng đồng và không áp đặt quy tắc cứng nhắc.

text
myproject/
├── cmd/
│   └── api/
│       └── main.go      # Entry point
├── internal/            # Private to module
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                 # Reusable external code
├── go.mod
├── go.sum
└── README.md

Thư mục internal là đặc biệt: nội dung của nó không thể import bởi các module khác.

Các câu hỏi nâng cao

23. Garbage collector trong Go vận hành ra sao?

Go dùng garbage collector dạng tricolor mark-and-sweep, chạy đồng thời và được tối ưu cho độ trễ thấp.

gc_optimization.gogo
package main

import "runtime"

func main() {
    // GC configuration
    // GOGC=100 (default) - triggers GC when heap doubles

    // Force GC
    runtime.GC()

    // Memory statistics
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)

    println("Alloc:", stats.Alloc)
    println("NumGC:", stats.NumGC)
    println("PauseTotalNs:", stats.PauseTotalNs)
}

// Optimization techniques
// 1. Reuse allocations with sync.Pool
// 2. Pre-allocate slices with make([]T, 0, cap)
// 3. Avoid repeated string/[]byte conversions
// 4. Use pointers for large structs

Biến môi trường GODEBUG=gctrace=1 hiển thị nhật ký GC.

24. Hãy giải thích bộ lập lịch của Go

Bộ lập lịch Go dùng mô hình M:N, ánh xạ N goroutine lên M thread hệ thống với ba thực thể: G (goroutine), M (thread) và P (bộ xử lý logic).

scheduler.gogo
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // Number of logical processors (P)
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))

    // Number of active goroutines
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())

    // Yield processor to other goroutines
    runtime.Gosched()

    // M:P:G model
    // - G: goroutine (lightweight stack ~2KB)
    // - M: OS thread (machine)
    // - P: logical processor (execution context)
    //
    // Each P has a local queue of Gs
    // Work stealing when queue is empty
}

Từ Go 1.14, bộ lập lịch hỗ trợ giành lại CPU (preemptive), tránh việc một goroutine chiếm trọn một P.

25. Cách tối ưu hiệu năng trong Go?

Việc tối ưu bắt đầu từ profiling để xác định nút thắt cổ chai.

profiling.gogo
package main

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPU profiling
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // Code to profile...

    // Memory profiling
    mf, _ := os.Create("mem.prof")
    defer mf.Close()
    pprof.WriteHeapProfile(mf)
}

// Analysis: go tool pprof cpu.prof

// Common optimization techniques:
// 1. Avoid allocations in hot loops
// 2. Use sync.Pool for reusable objects
// 3. Prefer []byte over string for mutations
// 4. Use bufio for I/O
// 5. Batch database operations
Quy tắc tối ưu

Hãy đo lường trước khi tối ưu. Profiling thường lộ ra những nút thắt cổ chai bất ngờ.

Kết luận

25 câu hỏi này bao quát các khái niệm cơ bản được đánh giá trong phỏng vấn Go:

Danh mục chuẩn bị:

  • ✅ Thành thạo goroutine và channel
  • ✅ Hiểu interface ngầm định
  • ✅ Xử lý lỗi đặc trưng của Go
  • ✅ Sử dụng context đúng cách
  • ✅ Mẫu đồng thời (mutex, worker pool)
  • ✅ Test và benchmark
  • ✅ Hiểu generics từ Go 1.18+

Chìa khóa thành công cho buổi phỏng vấn Go: thể hiện khả năng đánh đổi giữa sự đơn giản và hiệu năng, đồng thời biết khi nào nên dùng mẫu đồng thời nào.

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thẻ

#go
#golang
#interview
#concurrency
#goroutines

Chia sẻ

Bài viết liên quan