目录
为什么要使用并发,因为并发能大大提高效率,以下案例可以通过执行时间直观的感受到差别
例如以下方法
1、未使用goroutine时
var students = map[int64]student{
1: {
id: 1,
name: "zs",
},
2: {
id: 2,
name: "ls",
},
}
type student struct {
id int
name string
}
func getStudent(id int64) student {
time.Sleep(1 * time.Second)
return students[id]
}
func one() {
start := time.Now()
ids := []int64{1, 2}
for _, id := range ids {
fmt.Println(getStudent(id))
}
end := time.Since(start)
fmt.Println(end)
}
func main() {
one()
}
通过时间上来看肯定是大于2秒的,timeSleep模拟处理业务需要花费的时间
2、使用goroutine时
新增函数two
func two() {
start := time.Now()
ids := []int64{1, 2}
for _, id := range ids {
go func() {
s := getStudent(id)
fmt.Println(s)
}()
}
end := time.Since(start)
fmt.Println(end)
time.Sleep(1 * time.Minute)
}
结果:
19.5µs
从结果上来看不用再顺序执行代码了,大大增加了程序的性能
建议标准写法,将id值作为参数传入并发方法体中
3、使用WaitGroup等待并发结束
上述代码中为了查看到最终结果,使用的是time.Sleep(1 * time.Minute )但是在正常的程序中肯定不会这么去使用的,所以我们要使用waitGroup来进行等待并发结束
var (
eg sync.WaitGroup
)
func two() {
//表示每add一次记录一个并发(协程)在执行,完成一个-1
start := time.Now()
ids := []int64{1, 2}
for _, id := range ids {
eg.Add(1)
go func(id int64) {
s := getStudent(id)
fmt.Println(s)
//表示这个并发(协程)完成了
eg.Done()
}(id)
}
//当记录的并发(协程)数为0,则全部并发结束了
eg.Wait()
end := time.Since(start)
fmt.Println(end)
}
结果:
{2 ls}
{1 zs}
1.001023916s
解释:
在外面我创建了一个sync.WaitGroup ,使用eg.add相当于记数每执行一次并发记录一次,执行完成一个减去一次,eg.done函数记录并发次数-1,最后调用eg.Wait()发现计数为0后则表示这几个并发完成了
4、利用defer方法体再优化
在实际开发中,当我们对函数的返回添加error后,如果这里我不是设错误为nil,如果我创建一个errors.new("查询错误"),同时如果我们继续把eg.done放到最后那么久没有办法去执行到eg.done,所以我们使用defer方法体去进行再次的优化
func getStudent(id int64) (student, error) {
time.Sleep(1 * time.Second)
return students[id], nil
}
func two() {
//表示每add一次记录一个并发(协程)在执行,
start := time.Now()
ids := []int64{1, 2}
for _, id := range ids {
eg.Add(1)
go func(id int64) {
defer func() {
eg.Done()
}()
s, err := getStudent(id)
if err != nil {
return
}
fmt.Println(s)
}(id)
}
//当记录的并发(协程)数为0,则全部并发结束了
eg.Wait()
end := time.Since(start)
fmt.Println(end)
}