面试题答案
一键面试确保共享资源正确管理和释放的方法
- 使用互斥锁(Mutex):
- 当多个goroutine需要访问共享资源时,通过互斥锁来保证同一时间只有一个goroutine能访问该资源,防止数据竞争。
- 例如,对一个全局变量的读写操作:
package main
import (
"fmt"
"sync"
)
var (
counter int
mu sync.Mutex
)
func increment(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock()
counter++
mu.Unlock()
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Final counter value:", counter)
}
- 读写锁(RWMutex):
- 如果共享资源读操作远多于写操作,可以使用读写锁。多个goroutine可以同时进行读操作,但写操作时会独占资源,防止其他读写操作。
- 示例:
package main
import (
"fmt"
"sync"
)
var (
data = make(map[string]int)
rwmu sync.RWMutex
)
func read(key string, wg *sync.WaitGroup) {
defer wg.Done()
rwmu.RLock()
value := data[key]
fmt.Printf("Read key %s, value %d\n", key, value)
rwmu.RUnlock()
}
func write(key string, value int, wg *sync.WaitGroup) {
defer wg.Done()
rwmu.Lock()
data[key] = value
fmt.Printf("Write key %s, value %d\n", key, value)
rwmu.Unlock()
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go write("test", 100, &wg)
go read("test", &wg)
wg.Wait()
}
- 使用sync.WaitGroup:
- 用于等待一组goroutine完成。在启动每个goroutine前调用
wg.Add(1)
,在goroutine结束时调用wg.Done()
,主goroutine通过wg.Wait()
等待所有goroutine完成。 - 如上面的例子中,通过
WaitGroup
确保所有increment
和read
/write
操作完成后程序才结束。
- 用于等待一组goroutine完成。在启动每个goroutine前调用
- 使用sync.Cond:
- 当需要在共享资源的状态发生变化时通知其他goroutine时使用。例如,一个生产者 - 消费者模型中,当缓冲区满时生产者等待,当缓冲区有空间时消费者通知生产者。
- 示例:
package main
import (
"fmt"
"sync"
)
type Buffer struct {
data []int
count int
size int
mu sync.Mutex
cond sync.Cond
}
func NewBuffer(size int) *Buffer {
b := &Buffer{
data: make([]int, 0, size),
size: size,
}
b.cond.L = &b.mu
return b
}
func (b *Buffer) Produce(item int) {
b.mu.Lock()
for b.count == b.size {
b.cond.Wait()
}
b.data = append(b.data, item)
b.count++
fmt.Printf("Produced %d, buffer count %d\n", item, b.count)
b.cond.Broadcast()
b.mu.Unlock()
}
func (b *Buffer) Consume() int {
b.mu.Lock()
for b.count == 0 {
b.cond.Wait()
}
item := b.data[0]
b.data = b.data[1:]
b.count--
fmt.Printf("Consumed %d, buffer count %d\n", item, b.count)
b.cond.Broadcast()
b.mu.Unlock()
return item
}
func main() {
buffer := NewBuffer(2)
var wg sync.WaitGroup
wg.Add(3)
go func() {
buffer.Produce(1)
wg.Done()
}()
go func() {
buffer.Produce(2)
wg.Done()
}()
go func() {
buffer.Consume()
wg.Done()
}()
wg.Wait()
}
- 资源清理与defer:
- 在函数中使用
defer
关键字来确保资源在函数结束时正确释放。例如,对于文件句柄,在打开文件后使用defer file.Close()
确保文件在函数结束时关闭。
- 在函数中使用
package main
import (
"fmt"
"os"
)
func readFile() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 处理文件内容
}
- 使用sync.Once:
- 用于确保某个操作只执行一次。例如,对于数据库连接池的初始化,使用
sync.Once
可以保证在多个goroutine环境下连接池只初始化一次。
- 用于确保某个操作只执行一次。例如,对于数据库连接池的初始化,使用
package main
import (
"fmt"
"sync"
)
var (
dbOnce sync.Once
dbConn string
)
func getDBConnection() string {
dbOnce.Do(func() {
// 实际的数据库连接操作
dbConn = "connected to database"
fmt.Println("Database connection initialized")
})
return dbConn
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
conn := getDBConnection()
fmt.Println(conn)
}()
}
wg.Wait()
}
通过合理使用这些工具,可以在Go的并发程序中有效管理和释放共享资源,提高程序的健壮性。