MST

星途 面试题库

面试题:Go defer语句在复杂资源管理场景的应用

假设你正在开发一个数据库连接池,连接池中的每个连接都需要在使用后归还给连接池(类似资源回收)。请阐述如何使用defer语句来确保连接正确归还,同时说明在并发环境下可能遇到的问题及解决方案,并编写相应的代码框架。
38.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 使用defer语句确保连接正确归还 在Go语言中,可以使用defer语句在函数结束时执行特定的操作。对于数据库连接池,在获取连接后,使用defer语句确保连接在函数结束时归还到连接池。
package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database_name")
    if err != nil {
        panic(err.Error())
    }
    defer db.Close()

    // 获取连接
    conn, err := db.Conn(nil)
    if err != nil {
        panic(err.Error())
    }
    defer func() {
        // 归还连接
        err := conn.Close()
        if err != nil {
            fmt.Println("Error closing connection:", err)
        }
    }()

    // 使用连接执行数据库操作
    _, err = conn.Exec("SELECT * FROM some_table")
    if err != nil {
        fmt.Println("Error executing query:", err)
    }
}
  1. 并发环境下可能遇到的问题及解决方案

问题

  • 连接竞争:多个协程同时获取和归还连接,可能导致连接池状态不一致。
  • 资源耗尽:如果连接池中的连接数量有限,高并发情况下可能会出现所有连接都被占用,新的请求无法获取连接的情况。

解决方案

  • 互斥锁:使用sync.Mutex来保护连接池的状态,确保在同一时间只有一个协程可以修改连接池的状态(如获取或归还连接)。
  • 信号量:可以使用sync.Semaphorechannel来限制并发获取连接的数量,避免资源耗尽。例如,使用带缓冲的channel来表示连接池中的可用连接,channel的缓冲大小就是连接池的最大连接数。
package main

import (
    "database/sql"
    "fmt"
    "sync"
    _ "github.com/go-sql-driver/mysql"
)

type ConnectionPool struct {
    db      *sql.DB
    maxConn int
    sem     chan struct{}
    mu      sync.Mutex
    inUse   int
}

func NewConnectionPool(dsn string, maxConn int) (*ConnectionPool, error) {
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err
    }
    pool := &ConnectionPool{
        db:      db,
        maxConn: maxConn,
        sem:     make(chan struct{}, maxConn),
    }
    return pool, nil
}

func (p *ConnectionPool) GetConnection() (*sql.Conn, error) {
    p.sem <- struct{}{} // 获取信号量,如果没有可用信号量则阻塞
    p.mu.Lock()
    p.inUse++
    p.mu.Unlock()

    conn, err := p.db.Conn(nil)
    if err != nil {
        p.mu.Lock()
        p.inUse--
        p.mu.Unlock()
        <-p.sem // 释放信号量
        return nil, err
    }
    return conn, nil
}

func (p *ConnectionPool) ReturnConnection(conn *sql.Conn) {
    err := conn.Close()
    if err != nil {
        fmt.Println("Error closing connection:", err)
    }
    p.mu.Lock()
    p.inUse--
    p.mu.Unlock()
    <-p.sem // 释放信号量
}

在上述代码中,ConnectionPool结构体包含了数据库连接对象、最大连接数、信号量sem和互斥锁muGetConnection方法使用信号量来限制并发获取连接的数量,并使用互斥锁保护连接池的状态。ReturnConnection方法在归还连接时,同样使用互斥锁保护状态并释放信号量。