package main
import (
"fmt"
"sync"
)
// Task 代表一个任务
type Task struct {
ID int
}
func main() {
// 模拟任务队列
tasks := []Task{
{ID: 1},
{ID: 2},
{ID: 3},
{ID: 4},
{ID: 5},
}
var wg sync.WaitGroup
var mu sync.Mutex
taskIndex := 0
// 启动3个goroutine来处理任务
numGoroutines := 3
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for {
mu.Lock()
if taskIndex >= len(tasks) {
mu.Unlock()
break
}
task := tasks[taskIndex]
taskIndex++
mu.Unlock()
// 处理任务
processTask(task)
}
}()
}
wg.Wait()
}
// processTask 处理单个任务
func processTask(task Task) {
fmt.Printf("Processing task %d\n", task.ID)
}
for循环中的策略
- 任务获取:在for循环内部,首先使用互斥锁
mu
来保护对共享任务队列索引taskIndex
的访问。每次获取任务前,锁定互斥锁,检查taskIndex
是否超过任务队列的长度。如果没有超过,则取出任务,并将taskIndex
递增,然后解锁互斥锁。
- 任务处理:获取任务后,在临界区(互斥锁保护的区域)之外处理任务,这样可以在处理任务时不占用锁,提高并发效率。
保证程序正确性和高效性的方式
- 正确性:
- 互斥锁:通过
sync.Mutex
来确保同一时间只有一个goroutine可以访问和修改共享的任务队列索引taskIndex
,从而避免竞态条件。
- 边界检查:在每次获取任务前,检查
taskIndex
是否超过任务队列的长度,确保不会越界访问。
- 高效性:
- 减少锁的持有时间:只在获取任务时持有锁,任务处理在锁之外进行,这样可以让其他goroutine有更多机会获取任务,提高并发度。
- 合理设置goroutine数量:根据实际情况(如CPU核心数、任务类型等)合理设置goroutine的数量,避免过多或过少的goroutine导致资源浪费或性能瓶颈。例如在上述代码中设置了3个goroutine来处理任务。