1. 运用并发和并行策略优化性能
- 使用goroutine处理任务:将I/O操作和计算任务分别封装成不同的goroutine。对于I/O操作,利用goroutine的轻量级特性,在I/O等待时让出CPU,使其他goroutine可以执行,从而提高系统的并发度。例如:
func ioTask() {
// 模拟I/O操作
time.Sleep(time.Second)
}
func computeTask() {
// 模拟复杂计算
for i := 0; i < 1000000000; i++ {
_ = i * i
}
}
func main() {
go ioTask()
go computeTask()
// 等待任务完成
time.Sleep(2 * time.Second)
}
- 使用channel进行任务同步和数据传递:由于任务存在依赖关系,channel可用于在不同goroutine之间传递数据和同步操作。比如,一个计算任务需要等待I/O操作完成后的数据,就可以通过channel传递I/O操作的结果。
func ioTask(ch chan<- string) {
// 模拟I/O操作
time.Sleep(time.Second)
ch <- "I/O result"
close(ch)
}
func computeTask(ch <-chan string) {
result := <-ch
// 使用I/O结果进行计算
fmt.Println("Compute with result:", result)
}
func main() {
ch := make(chan string)
go ioTask(ch)
go computeTask(ch)
// 等待任务完成
time.Sleep(2 * time.Second)
}
- 使用WaitGroup等待所有任务完成:在主goroutine中,使用
sync.WaitGroup
来等待所有相关的goroutine完成,确保程序不会提前退出。
func ioTask(wg *sync.WaitGroup) {
defer wg.Done()
// 模拟I/O操作
time.Sleep(time.Second)
}
func computeTask(wg *sync.WaitGroup) {
defer wg.Done()
// 模拟复杂计算
for i := 0; i < 1000000000; i++ {
_ = i * i
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go ioTask(&wg)
go computeTask(&wg)
wg.Wait()
}
2. 避免资源竞争和死锁问题
- 资源竞争:
- 使用互斥锁(Mutex):当多个goroutine需要访问共享资源时,使用
sync.Mutex
来保护共享资源。例如,若有多个goroutine需要修改同一个全局变量:
var mu sync.Mutex
var sharedData int
func updateSharedData() {
mu.Lock()
sharedData++
mu.Unlock()
}
- **使用读写锁(RWMutex)**:如果共享资源读操作远多于写操作,可以使用`sync.RWMutex`。读操作时可以多个goroutine同时进行,写操作时则独占资源。
var mu sync.RWMutex
var sharedData int
func readSharedData() int {
mu.RLock()
defer mu.RUnlock()
return sharedData
}
func writeSharedData() {
mu.Lock()
sharedData++
mu.Unlock()
}
- 死锁:
- 合理安排锁的获取顺序:确保所有goroutine按照相同的顺序获取锁,避免出现循环等待的情况。例如,若有两个资源A和B,所有goroutine都先获取A的锁,再获取B的锁。
- 避免嵌套锁:尽量减少锁的嵌套使用,若必须使用,要仔细检查获取和释放锁的逻辑,防止死锁。
- 设置合理的超时:在使用channel进行同步时,设置合理的超时,避免因channel阻塞导致死锁。例如:
ch := make(chan int)
select {
case data := <-ch:
// 处理数据
fmt.Println("Received data:", data)
case <-time.After(time.Second):
// 超时处理
fmt.Println("Timeout")
}
3. 对并发和并行性能产生影响的关键因素
- CPU核心数:Go运行时会将goroutine调度到不同的CPU核心上并行执行。更多的CPU核心意味着可以同时执行更多的任务,提高并行度。如果CPU核心数较少,过多的goroutine可能导致频繁的上下文切换,降低性能。
- 任务粒度:任务划分的粒度大小会影响性能。如果任务粒度太小,创建和调度goroutine的开销可能大于任务执行本身的开销;如果任务粒度太大,又可能无法充分利用并发和并行的优势。例如,将一个大的计算任务划分成适当大小的子任务,既能充分利用多核优势,又不会引入过多调度开销。
- I/O操作延迟:I/O操作通常比CPU计算操作慢得多。如果I/O操作延迟高,在等待I/O完成时,CPU资源可能闲置。合理的并发策略可以在I/O等待时执行其他任务,提高整体性能。
- 资源竞争程度:共享资源的竞争越激烈,使用锁等同步机制的频率就越高,这会导致性能下降。减少共享资源的使用,或者优化同步机制,可以提高并发性能。
- 网络延迟:如果涉及网络I/O,网络延迟会显著影响性能。优化网络请求,例如使用连接池、减少不必要的网络交互等,可以提高并发和并行性能。