面试题答案
一键面试1. 限制并发数
- 策略:通过使用
sync.WaitGroup
和channel
来控制并发数,避免过多的 goroutine 同时运行导致资源耗尽。 - 代码示例:
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
result := j * 2
fmt.Printf("Worker %d finished job %d, result: %d\n", id, j, result)
results <- result
}
}
func main() {
const numJobs = 10
const maxWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for i := 0; i < maxWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
for j := 0; j < numJobs; j++ {
jobs <- j
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Printf("Result: %d\n", r)
}
}
在上述代码中,maxWorkers
控制了同时运行的 goroutine 数量,避免了资源过度消耗。
2. 负载均衡
- 策略:采用更智能的任务分配方式,例如轮询或加权轮询,确保每个 goroutine 处理的任务量相对均衡。
- 代码示例:
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
result := j * 2
fmt.Printf("Worker %d finished job %d, result: %d\n", id, j, result)
results <- result
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make([]chan int, numWorkers)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
jobs[i] = make(chan int)
wg.Add(1)
go worker(i, jobs[i], results, &wg)
}
for j := 0; j < numJobs; j++ {
jobs[j%numWorkers] <- j
}
for i := 0; i < numWorkers; i++ {
close(jobs[i])
}
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Printf("Result: %d\n", r)
}
}
这里通过 jobs[j%numWorkers] <- j
实现了简单的轮询负载均衡,让每个 worker 处理的任务相对均衡。
3. 资源复用
- 策略:对于一些昂贵的资源(如数据库连接、网络连接等),可以使用连接池来复用,减少资源创建和销毁的开销。
- 代码示例:
package main
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go-sql-driver/mysql"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup, db *sql.DB) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
// 复用数据库连接进行操作
row := db.QueryRow("SELECT some_column FROM some_table WHERE id =?", j)
var result int
row.Scan(&result)
fmt.Printf("Worker %d finished job %d, result: %d\n", id, j, result)
results <- result
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// 初始化数据库连接池
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database_name")
if err != nil {
panic(err.Error())
}
defer db.Close()
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg, db)
}
for j := 0; j < numJobs; j++ {
jobs <- j
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Printf("Result: %d\n", r)
}
}
此代码示例中使用了数据库连接池,每个 worker 复用连接池中的数据库连接,减少了连接创建和销毁的开销,提高了性能和稳定性。