MST

星途 面试题库

面试题:Go语言Goroutine任务调度策略的优化

在高并发场景下,Goroutine的任务调度策略可能面临性能瓶颈。假设你有一组CPU密集型的Goroutine任务和一组I/O密集型的Goroutine任务,如何优化调度策略以提高整体系统性能,请详细阐述思路和实现方法。
41.1万 热度难度
编程语言Go

知识考点

AI 面试

面试题答案

一键面试

优化思路

  1. CPU密集型任务
    • 由于CPU密集型任务长时间占用CPU资源,应尽量避免过多此类任务同时运行,以免导致CPU资源竞争激烈。可以根据CPU核心数,合理限制并发执行的CPU密集型Goroutine数量。
    • 优先将CPU资源分配给这些任务,因为它们对CPU计算能力要求高。
  2. I/O密集型任务
    • I/O密集型任务在等待I/O操作完成时会让出CPU,所以可以让更多的I/O密集型Goroutine并发运行。
    • 利用I/O操作等待时间,调度器可以切换到其他可运行的任务,包括CPU密集型任务,提高CPU利用率。

实现方法

  1. 限制CPU密集型任务并发数
    • 使用sync.WaitGroupchannel来控制并发数。例如:
package main

import (
    "fmt"
    "sync"
)

func cpuIntensiveTask(id int, wg *sync.WaitGroup, sem chan struct{}) {
    defer wg.Done()
    sem <- struct{}{}
    defer func() { <-sem }()
    // 模拟CPU密集型计算
    for i := 0; i < 1000000000; i++ {
        _ = i * i
    }
    fmt.Printf("CPU intensive task %d finished\n", id)
}

func main() {
    var wg sync.WaitGroup
    maxCPUConcurrency := 4
    sem := make(chan struct{}, maxCPUConcurrency)
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go cpuIntensiveTask(i, &wg, sem)
    }
    wg.Wait()
}
  • 在上述代码中,sem是一个带缓冲的通道,其缓冲大小为maxCPUConcurrency,控制同时运行的CPU密集型任务数量。
  1. I/O密集型任务调度
    • Go语言的调度器默认对I/O密集型任务有较好的支持。当I/O操作阻塞时,调度器会自动切换到其他可运行的Goroutine。
    • 例如对于网络I/O操作,可以使用标准库的net/http包等进行操作:
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "sync"
)

func ioIntensiveTask(url string, wg *sync.WaitGroup) {
    defer wg.Done()
    resp, err := http.Get(url)
    if err != nil {
        fmt.Printf("Error fetching %s: %v\n", url, err)
        return
    }
    defer resp.Body.Close()
    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Printf("Error reading %s: %v\n", url, err)
        return
    }
    fmt.Printf("I/O intensive task for %s finished\n", url)
}

func main() {
    var wg sync.WaitGroup
    urls := []string{
        "http://example.com",
        "http://google.com",
        // 更多URL
    }
    for _, url := range urls {
        wg.Add(1)
        go ioIntensiveTask(url, &wg)
    }
    wg.Wait()
}
  • 这里多个I/O密集型任务(HTTP请求)可以并发执行,在等待响应时调度器会调度其他Goroutine运行。
  1. 全局调度优化
    • 可以使用runtime.GOMAXPROCS函数设置Go程序能使用的CPU核心数,以匹配硬件资源,充分利用多核CPU性能。例如:
package main

import (
    "fmt"
    "runtime"
)

func main() {
    numCPU := runtime.NumCPU()
    runtime.GOMAXPROCS(numCPU)
    fmt.Printf("Using %d CPU cores\n", numCPU)
    // 启动任务等操作
}
  • 这样可以让CPU密集型任务更好地并行执行,同时配合I/O密集型任务的调度,提高整体系统性能。