📝 TỔNG HỢP 100 CÂU HỎI PHỎNG VẤN GOLANG THƯỜNG GẶP 🐹
100 Câu hỏi thường gặp khi phỏng vấn Golang
1. Golang là gì?
Go, hay Golang, là một ngôn ngữ lập trình mã nguồn mở được phát triển bởi Google. Nó là ngôn ngữ biên dịch, kiểu tĩnh và được thiết kế để xây dựng các ứng dụng mở rộng và hiệu suất cao.
2. Các tính năng chính của Go là gì?
- Hỗ trợ concurrency bằng cách sử dụng Goroutines.
- Garbage collection.
- Statically typed và dynamic behavior.
- Cú pháp đơn giản.
- Biên dịch nhanh.
3. Goroutines là gì, làm thế nào để tạo goroutine?
Goroutines là các thread nhẹ được quản lý bởi runtime của Go. Chúng là các function hoặc method chạy đồng thời với các function hoặc method khác.
Sử dụng từ khóa go
trước một function:
go myFunction()
// With anonymous function
go func(){
}()
4. Channel trong Go là gì, làm thế nào để khai báo một channel?
Channels là cách để các Goroutines giao tiếp với nhau và đồng bộ hóa việc thực thi. Chúng cho phép gửi và nhận các giá trị.
ch := make(chan int)
5.Buffered channel là gì?
Buffered channel có một dung lượng xác định và cho phép gửi các giá trị (mà không block bên gửi) cho đến khi buffer đầy. Nó không yêu cầu phải luôn có một receiver sẵn sàng để nhận và xử lý dữ liệu.
func main(){
ch1 := make(chan int,1)
ch1<-1 //this will not block, thanks to buffer
ch2 := make(chan int)
ch2 <- 1 // this will block main because no other goroutine will read it
}
6. Làm thế nào để đóng một channel?
Sử dụng hàm close()
(thường được gọi ở phía gửi msg vào channel, hoặc đối tượng quản lý channel đó) :
close(ch)
Và khi muốn kiểm tra xem channel đã close hay chưa
//Case 1
value, ok := <-ch
if !ok {
// Channel is closed
}
//Case 2
for value := range ch {
// Process value
}
// Channel is closed when the loop exits
7. Struct trong Go là gì, thế nào để định nghĩa một struct?
Struct là một kiểu dữ liệu do người dùng định nghĩa cho phép nhóm các trường có kiểu dữ liệu khác nhau vào một thực thể duy nhất.
type Person struct {
Name string
Age int
}
//or
func someFunc(){
arr := []struct{ UserId int }{{UserId: 200}}
var m map[string]struct {
UserId string
}
fmt.Println("Hello ", arr, m)
}
8. Interface trong Go là gì, làm thế nào để triển khai một interface?
Interface trong Go là một kiểu dữ liệu định nghĩa tập hợp các method signatures. Nó tạo tính đa hình polymorphism bằng cách cho phép đa dạng hóa việc định nghĩa các hành vi.
Một kiểu dữ liệu (type struct) triển khai một interface bằng cách triển khai (implement) tất cả các method của nó:
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
9. Những ảnh hưởng đến performance khi sử dụng các struct
lớn làm tham số cho hàm trong Go là như thế nào? Làm thế nào để tối ưu hóa (optimize)?
Việc truyền các struct
lớn theo giá trị (value) dẫn đến việc tạo ra một bản sao (copy), điều này có thể tốn kém về mặt bộ nhớ và hiệu suất. Để tối ưu hóa, nên truyền các con trỏ đến struct(*Struct)
thay vì sao chép toàn bộ struct
. Điều này giúp tránh việc nhân bản dữ liệu và giảm tải việc sao chép một lượng lớn bộ nhớ.
10. interface{}
trong Go là gì, và nó liên quan đến empty interfaces
như thế nào?
interface{}
là một empty interface
, có nghĩa là nó có thể chứa các giá trị của (any) bất kỳ kiểu nào vì mọi kiểu đều triển khai (implement) zero methods. Nó thường được sử dụng cho các cấu trúc dữ liệu generic hoặc khi làm việc với các function có thể chấp nhận bất kỳ kiểu nào (any). Sử dụng type assertions (value.(type))
và type switches cho phép bạn xác định dynamic type của một giá trị được lưu trữ trong interface{}
.
11. Một số chiến lược để giảm tải GC overhead là gì?
- Sử dụng object pooling để tái sử dụng bộ nhớ.
- Giảm việc tạo ra các đối tượng có tuổi thọ ngắn.
- Dự trữ trước bộ nhớ cho slices hoặc structs để tránh việc cấp phát thường xuyên.
12. Bạn sẽ implement việc xử lý lỗi và logging trong một dự án Go lớn như thế nào?
- Đối với xử lý lỗi (error handling), sử dụng các custom error types để thêm ngữ cảnh vào lỗi, và bọc lỗi bằng
fmt.Errorf
để có message lỗi với đầy đủ thông tin hơn. - Logging một cách có cấu trúc bằng cách sử dụng các thư viện như
zap
hoặclogrus
giúp ghi lại log với chi tiết với các trường dữ liệu liên quan gây ra lỗi để dễ dàng tìm kiếm và phân tích về sau. - Sử dụng
defer
để đóng các tài nguyên vàrecover
trong các phần quan trọng để xử lý các panics không mong muốn một cách gracefully. - Centralize error handling cho các thao tác thông thường như truy vấn cơ sở dữ liệu để đảm bảo tính nhất quán (consistency).
13. Làm thế nào để tránh các circular dependencies trong Go packages?
- Refactor các chức năng chung (commonly shared) vào một package riêng (pkg) để tránh sự phụ thuộc chồng chéo.
- Sử dụng
interfaces
để tách rời (decoupling) các dependencies, cho phép một package chỉ phụ thuộc vào interface thay vì implementation. - Tổ chức lại code để giảm số lượng dependencies giúp đảm bảo đồ thị phụ thuộc (dependency) không bị vòng lặp (circular dependencies).
14. Từ khóa defer
là gì, và nó hoạt động như thế nào?
defer
được sử dụng để hoãn việc thực thi một hàm cho đến khi surrounding function trả về.
Các hàm defer
được thực thi theo thứ tự LIFO (Last In, First Out):
defer fmt.Println("world")
defer fmt.Println("hi")
fmt.Println("hello")
// Output: hello hi world
15.How do you implement graceful shutdown of a Go HTTP server?
Answer: To implement a graceful shutdown, use a context
with http.Server
's Shutdown
method to stop accepting new connections while allowing in-flight requests to complete. You can use a signal.Notify
to listen for interrupt signals (e.g., SIGINT
) and call Shutdown
when the signal is received. This ensures the server shuts down cleanly without interrupting active requests.
srv := &http.Server{Addr: ":8080"}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
srv.Shutdown(context.Background())
16. Pointer trong Go là gì, và khai báo thế nào?
Pointer giữ địa chỉ bộ nhớ (memory address) của một giá trị. Nó được sử dụng để truyền tham chiếu (references) thay vì sao chép giá trị (copying values).
x := 5
var p *int
p = &x // & sign generates a pointer to x, assign it to p
*p // get the value that pointer p pointing to
17. Sự khác biệt giữa new
và make
là gì?
new
cấp phát bộ nhớ và trả về con trỏ đến đó.user := new(User)// equivalent to &User{} p := new(int) // p is *int, points to a zero-initialized int (0)
make
cấp phát và khởi tạo bộ nhớ cho slices, maps, và channels (trả về giá trị chứ không phải con trỏ).m := make(map[string]int) // m is a map[string]int, initialized and ready for use s := make([]int, 5) // s is a slice of int with length 5 and zeroed values ch := make(chan int) // ch is a channel for int
18. Slice trong Go là gì?
Slice là một mảng có kích thước động cho phép làm việc linh hoạt hơn với các chuỗi phần tử (sequences of elements).
19. Làm thế nào để tạo một slice?
s := make([]int, 0) // len 0 cap 0 value []
s2 := make([]int, 3,5) // len 3 cap 5 value [0 0 0]
s3 := []int{} // len 0 cap 0 value []
var s4 []int = []int{} // len 0 cap 0 value []
20. Map trong Go là gì?
Map là một tập hợp (collection) các cặp key-value.
21. Làm thế nào để tạo và kiểm tra phần tử trong map?
m := make(map[string]int)
m2 := map[string]int{}
m["hi"]=1
if val,ok:=m["hello"];ok {
}
22. Câu lệnh select
là gì?
select
cho phép một Goroutine đợi (wait) trên nhiều hoạt động giao tiếp (communication operations).
23. Làm thế nào để sử dụng select
?
select {
case msg := <-ch:
fmt.Println(msg)
case <-time.After(time.Millisecond*500):
fmt.Println("do something after a period amount of time")
default:
fmt.Println("No message received")
}
24. Channel nil
là gì?
Channel nil
sẽ chặn cả việc gửi và nhận. Hữu dụng khi muốn disable một channel nào đó, nhưng cẩn trọng khi dùng vì có thể gây deadlock
func main() {
var ch chan int // nil channel
select {
case ch <- 1: // This will block because ch is nil
fmt.Println("Sent to channel")
case <-time.After(1 * time.Second): // This ensures that after 1 second, it times out
fmt.Println("Timed out")
}
}
25. Hàm init
là gì?
init
là một hàm đặc biệt để khởi tạo các biến cấp package (package level). Nó được thực thi trước hàm main
.
26. Có thể có nhiều hàm init
không?
Có thể, nhưng chúng sẽ được thực thi theo thứ tự xuất hiện.
27. Struct rỗng {}
là gì?
Struct rỗng tiêu thụ (consumes) 0 byte bộ nhớ (storage). Thường dùng cho signaling, synchronization, hay dùng như 1 placeholder.
//{} can be used as values in a map when you only care about the existence of a key.
visited := map[string]struct{}{}
visited["example"] = struct{}{}
//Used to signal events without sending actual data.
done := make(chan struct{})
go func() {
// Do some work...
close(done) // Signal completion
}()
28. Làm thế nào để xử lý lỗi trong Go?
Bằng cách trả về kiểu error
và kiểm tra nó:
if err != nil {
return err
}
29. Type assertion là gì?
Type assertion được sử dụng để lấy giá trị thực sự của một interface:
value, ok := x.(string)
30. Lệnh go fmt
là gì?
go fmt
format mã nguồn Go theo standard style.
31. Mục đích của các lệnh go mod
, go mod tidy
, go clean -modcache
là gì?
-
go mod
quản lý các module dependencies trong các dự án Go (Cách này linh hoạt hơn việc dùngGo Path
trong các dự án cũ trước đây). -
go mod tidy
: Dọn dẹp các filego.mod
vàgo.sum
bằng cách loại bỏ các dependencies không sử dụng và thêm vào những cái còn thiếu mà module cần. Nó đảm bảo rằng các dependencies của module đồng bộ với mã thực tế. -
go clean -modcache
: Xóa nội dung của module cache, nơi lưu trữ các dependencies đã được tải về. Điều này hữu ích khi bạn muốn làm sạch cache để đảm bảo tải lại toàn bộ dependencies.
32. Làm thế nào để tạo một module?
go mod init module-name
33. Package trong Go là gì?
Package là cách nhóm các file Go liên quan lại với nhau.
34. Làm thế nào để import một package?
import "fmt"
35. Quy tắc hiển thị (visibility rules) của các hàm, biến trong Go thế nào?
- Các identifier được export bắt đầu bằng chữ cái viết hoa.
- Các identifier không được export (private) bắt đầu bằng chữ cái viết thường.
36. Sự khác biệt giữa var
và :=
là gì?
var
dùng để khai báo biến với kiểu rõ ràng (explicit types).:=
dùng để khai báo biến một cách ngắn gọn với kiểu ngầm định (inferred types).
37. panic
trong Go là gì?
panic
được dùng để kết thúc chương trình ngay lập tức khi gặp lỗi.
38. recover
là gì?
recover
được dùng để lấy lại quyền kiểm soát sau khi xảy ra panic
.
39. Làm thế nào để sử dụng recover
?
Nó được sử dụng trong hàm defer
:
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered:", r)
}
}()
40. Constant trong Go là gì?
Constants là các giá trị không thể thay đổi, được khai báo bằng từ khóa const
.
41. Làm thế nào để khai báo một constant?
const Pi = 3.14
42. iota trong Go là gì?
iota
là một lệnh giúp kích hoạt các constant sau đó tự động tăng thêm 1. (Thường dùng trong việc khai báo dữ liệu dạng Enum)
const (
Type1 = iota + 5 // set iota to start with 5, so Type1 = 5
Type2 // Type2 = 6
)
43. go test
là gì?
go test
được sử dụng để chạy các unit test được viết trong Go.
go test -v
go test -bench=. //test with Benchmark
go test -cover // analye test coverage
go test -race //test with race condition check
44. Làm thế nào để viết một hàm test?
Các hàm test phải bắt đầu tên hàm bằng Test
nằm trong file dạng _test.go
, và có thể sử dụng với built in package testing
:
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("expected 5, got %d", result)
}
}
45. Benchmarking trong Go là gì?
Benchmarking được sử dụng để đo lường hiệu suất của một hàm khi sử dụng go test -bench=.
.
46. Làm thế nào để viết một hàm benchmark?
Các hàm benchmark phải bắt đầu tên hàm bằng Benchmark
:
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}
47. Build constraint là gì?
Build constraints được sử dụng để bao gồm (include) hoặc loại bỏ (exclude) các file khỏi quá trình build (build process) dựa trên các điều kiện nhất định.
48. Làm thế nào để thiết lập một build constraint?
Đặt constraint trong một comment ở đầu file:
// +build linux
Lệnh trên chỉ include file được chỉ định trong trường hợp target build là Linux, hữu ích với platform-specific code, ví dụ OS-specific APIs.
49. Slices được backed bởi arrays nghĩa là gì?
Về bản chất slice được xây dựng trên underlying array và cung cấp một giao diện linh hoạt hơn khi làm việc với các phần tử bên dưới.
50. Garbage Collection trong Go là gì?
Go tự động quản lý bộ nhớ bằng cách sử dụng garbage collection (GC), giúp giải phóng bộ nhớ không còn được sử dụng.
51. Mục đích của package context
trong Go là gì?
Package context
được sử dụng để quản lý thời hạn (deadlines), tín hiệu hủy bỏ (cancellation signals), và các giá trị theo phạm vi yêu cầu (request-scoped values). Nó giúp kiểm soát luồng của các Goroutine và tài nguyên. (thường dùng các method context.WithTimeout
, context.WithCancel
hỗ trợ đóng goroutines một cách hiệu quả)
Ngoài ra context còn được dùng để share data (ex: userID) giữa các functions mà không cần dùng đến các biến global varible.
52. Làm thế nào để sử dụng context
trong Go?
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
Lúc này goroutines có thể được đóng với channel context.Done()
khi cancel()
được gọi.
go func(c context.Context){
for{
select {
case msg := <- someChannel:
fmt.Println("received msg: ",msg)
case <-c.Done():
return
}
}
}(ctx)
Trường hợp context
được dùng để share data như sau
// Define a key type to avoid collisions in context
type contextKey string
const requestIDKey = contextKey("requestID")
// Function that accepts a context and retrieves the request ID
func processRequest(ctx context.Context) {
requestID := ctx.Value(requestIDKey)
fmt.Println("Processing request with ID:", requestID)
}
func main() {
// Create a context with a value (e.g., request ID)
ctx := context.WithValue(context.Background(), requestIDKey, "12345")
// Pass the context with the request ID to another function
processRequest(ctx)
}
53. sync.WaitGroup
là gì?
sync.WaitGroup
được sử dụng để chờ một tập hợp các Goroutine hoàn thành việc thực thi.
54. Làm thế nào để sử dụngsync.WaitGroup
?
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// Do some work
}()
wg.Wait()
55. Mục đích của sync.Mutex
là gì??
sync.Mutex
cung cấp cơ chế khóa để bảo vệ tài nguyên chia sẻ (shared resouces) khỏi truy cập đồng thời (race condition).
56. Làm thế nào để sử dụng sync.Mutex
?
var mu sync.Mutex
mu.Lock()
// critical section
mu.Unlock()
57. select
được sử dụng thế nào với channels?
select
được sử dụng để xử lý nhiều hoạt động trên channel, đồng bộ hóa việc kết nối của goroutine trên nhiều channels một lúc (communication operations).
58. go generate
là gì??
go generate
là một lệnh để tạo mã nguồn. Nó đọc các comment đặc biệt trong mã nguồn để thực thi các lệnh cần thiết.
59. Method receiver trong Go là gì?
Method receiver xác định kiểu (type) mà method được liên kết đến, đó có thể là giá trị (value) hoặc con trỏ (pointer):
func (p *Person) GetName() string {
return p.Name
}
60. Sự khác biệt giữa value receiver và pointer receiver là gì?
- Value receiver nhận một bản sao của giá trị gốc.
- Pointer receiver nhận một tham chiếu (reference) đến giá trị gốc, cho phép thay đổi giá trị gốc (original value). (thường dùng trong trường hợp cần thay đổi giá trị gốc hoặc giảm tải việc copy giá trị gốc)
61. Variadic function là gì?
Variadic function chấp nhận một số lượng đối số tùy ý không cố định:
func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}
62. rune
trong Go là gì?
Rune
là một alias cho int32
và đại diện cho một mã Unicode.
63. select
block không có default
case hoạt động thế nào?
Select
block không có default
sẽ chờ cho đến khi một trong các case của nó có thể được thực thi. (cẩn trọng khi dùng default
case khi select
đặt trong for
vì có thể dẫn đến constant default running và unnessary resource consumption)
64. Mục đích Ticker
trong Go là gì?
A ticker
gửi các sự kiện theo khoảng thời gian đều đặn:
ticker := time.NewTicker(time.Second)
go func(){
for {
select {
case <-ticker.C:
fmt.Println("Tick...")
}
}
}()
65. Làm thế nào để xử lý JSON trong Go?
Sử dụng package encoding/json
để marshal và unmarshal JSON:
jsonData, _ := json.Marshal(structure)
json.Unmarshal(jsonData, &structure)
66. go vet
là gì?
go vet
kiểm tra mã nguồn Go và báo cáo các lỗi tiềm ẩn, tập trung vào các vấn đề không được trình biên dịch (compiler) phát hiện.
67. Anonymous function trong Go là gì?
Anonymous function là một hàm không có tên và có thể được định nghĩa trực tiếp:
go func() {
fmt.Println("Hello")
}()
68. Sự khác biệt giữa ==
và reflect.DeepEqual()
là gì?
==
kiểm tra tính bằng nhau cho các kiểu dữ liệu nguyên thủy (primitive type).reflect.DeepEqual()
so sánh tính bằng nhau sâu của các kiểu dữ liệu phức tạp như slices, maps, và structs.
69. time.Duration
trong Go là gì?
time.Duration
đại diện cho thời gian đã trôi qua và là một kiểu của int64
.
70. Làm thế nào để xử lý timeout với context
?
Sử dụng context.WithTimeout
để đặt một thời gian chờ:
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
71. Pipeline trong Go là gì?
Pipeline là một chuỗi các giai đoạn kết nối bằng channels, trong đó mỗi giai đoạn là một tập hợp các Goroutine nhận giá trị từ upstream và gửi giá trị đến downstream.
// Generator function produces integers.
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
// Square function reads from the input channel, squares the number, and sends it to the output channel.
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Create a pipeline: generator -> square.
numbers := generator(2, 3, 4)
squares := square(numbers)
// Consume the output from the last stage.
for sq := range squares {
fmt.Println(sq)
}
}
72.Quy ước thư mục pkg
trong Go là gì?
pkg
là một thư mục được sử dụng để đặt các package có thể tái sử dụng. Đây là một quy ước phổ biến nhưng không bắt buộc bởi Go.
73. Làm thế nào để debug mã Go?
Sử dụng các công cụ như dlv
(Delve), các câu lệnh print, hoặc package log
.
74. type
alias trong Go là gì?
type
aliasing cho phép bạn tạo một tên mới dựa trên một kiểu dữ liệu có sẵn (primitive type)
type MyInt = int
type MyInt2 int
75. Sự khác biệt giữa append
và copy
trong slices là gì?
append
thêm các phần tử vào một slice và trả về một slice mới tham chiếu đến cùng underlying array, hoặc array mới trong trường hợp việc append làm vượt quá capacity của underlying array.copy
sao chép các phần tử từ một slice này sang một slice khác.
slice1 := []int{1, 2}
slice2 := []int{3, 4}
copy(slice2, slice1) // [1, 2]
Nói thêm về một vài loại copy trong golang
//Primitive Types (int, float, bool, etc.): Use direct assignment
a := 10
b := a // b is a copy of a
//Structs: Use direct assignment
type Person struct {
Name string
Age int
}
p1 := Person{Name: "Alice", Age: 30}
p2 := p1 // p2 is a copy of p1
//Slices: Use the copy function
a := []int{1, 2, 3}
b := make([]int, len(a))
copy(b, a) // copies elements of a into b
//Maps: require a manual copy as assigning maps only copies the reference, not the contents.
original := map[string]int{"a": 1, "b": 2}
copyMap := make(map[string]int)
for k, v := range original {
copyMap[k] = v
}
//Pointers:Assigning a pointer to another only copies the reference, not the underlying value. For a deep copy, you must manually create a new value and copy the data.
Cần phân biệt rõ Shallow Copy (chỉ copy top level data của struct và reference đến slice, map, pointer chứ không copy chúng) và Deep Copy (full copy nhưng phải thực hiện thủ công, hoặc dùng json.Marshal/Unmarshal
hay external library như mitchellh/copystructure
, deepcopier
)
76. Mục đích của go doc
là gì?
go doc
được sử dụng để hiển thị tài liệu hướng dẫn (document) cho một package, function, hoặc biến trong Go.
77. Làm thế nào để xử lý panics trong production code?
Sử dụng recover
để xử lý panics một cách đúng đắn và ghi log
chúng để debugging:
defer func() {
if r := recover(); r != nil {
log.Println("Recovered from:", r)
}
}()
78. Sự khác biệt giữa map
và struct
là gì?
map
là một cấu trúc dữ liệu động với các cặp key-value.struct
là một cấu trúc dữ liệu tĩnh với các trường cố định.
79. Package unsafe
dùng làm gì?
Package unsafe cho phép thao tác low-level memory . Không được khuyến nghị sử dụng thường xuyên.
80. Làm thế nào để thực hiện dependency injection trong Go?
Sử dụng interfaces
và các hàm constructor để truyền các dependencies, giúp dễ dàng mock
và test
.
type HttpClient interface{}
func NewService(client HttpClient) *Service {
return &Service{client: client}
}
81. Goroutine khác gì với một thread?
Một Goroutine là một thread nhẹ được quản lý bởi Go runtime. Nó khác với các OS threads vì sử dụng stack khởi đầu nhỏ hơn (2KB) và được ánh xạ vào nhiều OS threads. Điều này làm cho Goroutines hiệu quả hơn trong việc xử lý concurrency.
82. Go scheduler hoạt động như thế nào?
Go scheduler sử dụng một thuật toán work-stealing với M:N scheduling, trong đó M đại diện cho OS threads và N đại diện cho Goroutines. Nó lên lịch cho các Goroutines trên các OS threads và CPUs sẵn có, nhằm cân bằng khối lượng công việc để đạt hiệu suất tối ưu.
83. Memory leak là gì và làm thế nào để ngăn chặn nó trong Go?
Memory leak xảy ra khi bộ nhớ đã cấp phát không được giải phóng. Trong Go, điều này có thể xảy ra nếu các Goroutines không được kết thúc hoặc các tham chiếu (references) đến các đối tượng được giữ lại không cần thiết. Sử dụng defer
để dọn dẹp và dừng các Goroutines đúng cách sẽ giúp ngăn chặn memory leak.
84. Garbage collection hoạt động như thế nào trong Go?
Go sử dụng garbage collector dạng concurrent, mark-and-sweep. Nó xác định các đối tượng có thể truy cập được trong giai đoạn mark và thu thập các đối tượng không thể truy cập trong giai đoạn sweep, cho phép các Goroutines khác tiếp tục chạy trong quá trình thu gom.
85. Giải thích sự khác nhau giữa sync.Mutex
và sync.RWMutex
.
sync.Mutex
được sử dụng để cung cấp quyền truy cập độc quyền vào một tài nguyên được chia sẻ.sync.RWMutex
cho phép nhiều readers hoặc một writer tại một thời điểm, cung cấp hiệu suất tốt hơn cho các workloads nặng về đọc.
86. Race condition là gì và làm thế nào để phát hiện chúng trong Go?
Race condition xảy ra khi nhiều Goroutines truy cập một biến chia sẻ đồng thời mà không có sự đồng bộ hóa hợp lý. Sử dụng go run -race
hoặc go test -race
để phát hiện các race condition trong chương trình Go.
87. Struct tag là gì và được sử dụng như thế nào?
Struct tags cung cấp metadata cho các trường (fields) trong struct, thường được sử dụng cho việc serialize JSON:
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
88. Làm thế nào để tạo một custom error trong Go?
Tạo một custom error bằng cách implement interface error
:
type MyError struct {
Msg string
}
func (e *MyError) Error() string {
return e.Msg
}
89. Nil pointer dereference là gì và làm thế nào để tránh nó?
Nil pointer dereference
xảy ra khi bạn cố gắng truy cập giá trị mà một con trỏ nil
trỏ đến. Tránh điều này bằng cách kiểm tra nil
trước khi sử dụng các con trỏ.
90. Explain the difference between sync.Pool
and garbage collection
.
sync.Pool
được sử dụng để tái sử dụng các objects và giảm áp lực lên GC. Nó cung cấp một cách để lưu trữ các objects có thể tái sử dụng, khác với GC tự động giải phóng bộ nhớ không sử dụng.
91. Làm thế nào để implement một worker pool trong Go?
Sử dụng channels
để phân phối các tasks và quản lý các Goroutines worker:
jobs := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs)
}
92. reflect
trong Go là gì?
Package reflect
cho phép kiểm tra runtime của các loại và giá trị. Nó được sử dụng cho các thao tác động (dynamic) như kiểm tra các trường của struct hoặc các phương thức. (Chỉ dùng trong một số trường hợp cần thiết vì reflect slow performance so với compiled type thông thường)
93. Sự khác biệt giữa buffered
và unbuffered
channels là gì?
- Một
buffered
channel có dung lượng, cho phép các Goroutines gửi dữ liệu mà không bị chặn (block or lock) cho đến khi buffer đầy. - Một
unbuffered
channel không có dung lượng và sẽ bị chặn cho đến khi receiver sẵn sàng.
94. Làm thế nào để tránh Goroutine leaks?
Đảm bảo các Goroutines được kết thúc bằng cách sử dụng context
để hủy hoặc sử dụng timeouts với channels.
95. Sự khác nhau chính giữa panic
và error
là gì?
error
được sử dụng để xử lý các tình huống dự đoán được và có thể trả về.panic
được sử dụng cho các tình huống không mong muốn và dừng luồng thực thi đột ngột.
96. Giải thích các interface io.Reader
và io.Writer
.
io.Reader
có phương thức Read
để đọc dữ liệu, trong khi io.Writer
có phương thức Write
để ghi dữ liệu. Chúng tạo cơ sở cho các trừu tượng I/O của Go.
97. nil value interface
là gì và tại sao nó có thể gây lỗi?
nil value interface
là một interface có giá trị mà nó tham chiếu đến là nil
. Nó có thể gây ra lỗi không mong muốn check nil
, vì một interface với giá trị cơ bản nil
bản thân nó lại không bằng nil
.
type MyInterface interface{}
var i MyInterface
var m map[string]int
i = m // This case, m is nil but i not nil
Để handle trường hợp trên ta có thể dùng interface assertion
như sau.
if v, ok := i.(map[string]int); ok && v != nil {
fmt.Printf("value not nil: %v\n", v)
}
98. Làm thế nào để ngăn chặn deadlock
trong các chương trình Go khi sử dụng goroutines?
Để ngăn chặn deadlock, hãy đảm bảo rằng:
- Các Lock luôn được acquired theo cùng một thứ tự trên tất cả các Goroutines.
- Sử dụng
defer
để giải phóng khóa (release locks). - Tránh holding lock trong khi gọi đến một hàm khác mà có tham chiếu đến cùng lock đó.
- Hạn chế việc sử dụng channels trong vùng tranh chấp đang được khóa (locked sections).
99. Làm thế nào để tối ưu hóa hiệu suất encode/decode JSON trong Go?
- Sử dụng các thư viện
jsoniter
hoặceasyjson
để encode/decode nhanh hơn so vớiencoding/json
chuẩn. - Định nghĩa trước các trường của struct bằng cách sử dụng
json:"field_name"
tags để tránh chi phí reflection. - Sử dụng
sync.Pool
để tái sử dụng các instances củajson.Encoder
hoặcjson.Decoder
khi encode/decode dữ liệu JSON lớn lặp đi lặp lại.
100. Sự khác biệt giữa GOMAXPROCS
và runtime.Gosched()
là gì?
GOMAXPROCS
điều khiển số lượng tối đa của các OS threads có thể thực thi Goroutines đồng thời. Nó cho phép điều chỉnh mức độ parallelism.runtime.Gosched()
nhường quyền xử lý cho các Goroutines khác. Nó không treo Goroutine hiện tại mà thay vào đó cho phép Go scheduler có cơ hội chạy các Goroutines khác.
Nếu bạn thấy bài viết này hữu ích, hãy cho mình biết bằng cách để lại 👍 hoặc bình luận!, hoặc nếu bạn nghĩ bài viết này có thể giúp ích cho ai đó, hãy thoải mái chia sẻ! Cảm ơn bạn rất nhiều! 😃
All rights reserved