MST

星途 面试题库

面试题:Go并发基础在简单Web服务场景的适配策略

假设你正在开发一个简单的Web服务,使用Go语言。该服务需要处理多个并发的HTTP请求,每个请求可能涉及到数据库查询等I/O操作。请阐述你会如何运用Go的并发基础来设计这个服务,以提高性能和资源利用率。比如,如何使用goroutine和channel来管理并发请求与数据传递?
21.6万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试
  1. 使用goroutine处理HTTP请求
    • 在Go语言中,net/http包提供了方便的HTTP服务器实现。当接收到一个HTTP请求时,可以使用go关键字启动一个新的goroutine来处理该请求。这样,主HTTP服务器的goroutine可以继续监听新的请求,而不会被单个请求的I/O操作阻塞。
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func handleRequest(w http.ResponseWriter, r *http.Request) {
        // 处理请求逻辑,可能包含数据库查询等I/O操作
        fmt.Fprintf(w, "处理请求")
    }
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            go handleRequest(w, r)
        })
        fmt.Println("服务器在:8080 启动")
        http.ListenAndServe(":8080", nil)
    }
    
  2. 使用channel管理数据传递与同步
    • 传递请求数据:如果处理请求的goroutine需要从其他goroutine获取数据(例如从数据库查询结果),可以使用channel。例如,假设有一个数据库查询函数返回查询结果,我们可以通过channel将结果传递给处理HTTP响应的goroutine
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func queryDatabase(query string) string {
        // 模拟数据库查询
        return "查询结果"
    }
    
    func handleRequest(w http.ResponseWriter, r *http.Request) {
        resultCh := make(chan string)
        go func() {
            result := queryDatabase("一些查询语句")
            resultCh <- result
            close(resultCh)
        }()
    
        for result := range resultCh {
            fmt.Fprintf(w, "数据库查询结果: %s", result)
        }
    }
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            go handleRequest(w, r)
        })
        fmt.Println("服务器在:8080 启动")
        http.ListenAndServe(":8080", nil)
    }
    
    • 同步与控制并发channel还可以用于控制并发数量。比如,可以创建一个带缓冲的channel作为信号量,用来限制同时进行的数据库查询数量,避免过多的I/O操作耗尽资源。
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    const maxConcurrentQueries = 5
    
    func queryDatabase(query string, semaphore chan struct{}) string {
        semaphore <- struct{}{}
        defer func() { <-semaphore }()
        // 模拟数据库查询
        return "查询结果"
    }
    
    func handleRequest(w http.ResponseWriter, r *http.Request) {
        semaphore := make(chan struct{}, maxConcurrentQueries)
        resultCh := make(chan string)
        go func() {
            result := queryDatabase("一些查询语句", semaphore)
            resultCh <- result
            close(resultCh)
        }()
    
        for result := range resultCh {
            fmt.Fprintf(w, "数据库查询结果: %s", result)
        }
    }
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            go handleRequest(w, r)
        })
        fmt.Println("服务器在:8080 启动")
        http.ListenAndServe(":8080", nil)
    }
    
  3. 错误处理与资源清理
    • 在并发处理中,需要妥善处理错误。如果在数据库查询等I/O操作中发生错误,可以通过channel传递错误信息,让处理HTTP响应的goroutine能够正确处理并返回合适的HTTP状态码。
    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    func queryDatabase(query string) (string, error) {
        // 模拟数据库查询错误
        return "", fmt.Errorf("数据库查询错误")
    }
    
    func handleRequest(w http.ResponseWriter, r *http.Request) {
        resultCh := make(chan string, 1)
        errCh := make(chan error, 1)
        go func() {
            result, err := queryDatabase("一些查询语句")
            if err!= nil {
                errCh <- err
                return
            }
            resultCh <- result
            close(resultCh)
        }()
    
        select {
        case result := <-resultCh:
            fmt.Fprintf(w, "数据库查询结果: %s", result)
        case err := <-errCh:
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            go handleRequest(w, r)
        })
        fmt.Println("服务器在:8080 启动")
        http.ListenAndServe(":8080", nil)
    }
    
    • 对于打开的资源(如数据库连接),要确保在goroutine结束时正确关闭,避免资源泄漏。可以使用defer语句来确保资源的清理。例如,如果使用了数据库连接池,在使用完连接后要将其归还到连接池。
    package main
    
    import (
        "database/sql"
        "fmt"
        "net/http"
        _ "github.com/go - sql - driver/mysql"
    )
    
    func queryDatabase(db *sql.DB, query string) (string, error) {
        row := db.QueryRow(query)
        var result string
        err := row.Scan(&result)
        if err!= nil {
            return "", err
        }
        return result, nil
    }
    
    func handleRequest(w http.ResponseWriter, r *http.Request) {
        db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database")
        if err!= nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        defer db.Close()
    
        resultCh := make(chan string, 1)
        errCh := make(chan error, 1)
        go func() {
            result, err := queryDatabase(db, "一些查询语句")
            if err!= nil {
                errCh <- err
                return
            }
            resultCh <- result
            close(resultCh)
        }()
    
        select {
        case result := <-resultCh:
            fmt.Fprintf(w, "数据库查询结果: %s", result)
        case err := <-errCh:
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    }
    
    func main() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            go handleRequest(w, r)
        })
        fmt.Println("服务器在:8080 启动")
        http.ListenAndServe(":8080", nil)
    }