golang 串行与并行(并发)对比

施德运
2023-12-01

golang对比其它语言最大的优势就是一个go关键字就能实现并发,工作中经常遇到并发的场景, 具体实现并发的方式有多种,今天就来做个小实验来对比说明串行和并行执行任务中并发的优势,好了直接上代码

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

//定义任务数量
var jobsCount int = 10

func main() {
    timer("serial", serial)
    timer("parallel", parallel)
    timer("parallel2", parallel2)
}

//串行实现任务执行
func serial() {
    for i := 0; i < jobsCount; i++ {
        fmt.Printf("hello  task %d \n", i)
        a := int(rand.Float64() * 1000.0)
        a = 500 //可注释掉该行使用随机数
        time.Sleep(time.Duration(a) * time.Millisecond)
    }
    fmt.Println("serial task done success ")
}

//使用waitgroup 来实现等待并发完成
func parallel() {
    wg := sync.WaitGroup{}
    for i := 0; i < jobsCount; i++ {
        wg.Add(1)
        go func(idx int) {
            fmt.Printf("hello  task %d \n", idx)
            a := int(rand.Float64() * 1000.0)
            a = 500 //可注释掉该行使用随机数
            time.Sleep(time.Duration(a) * time.Millisecond)
            wg.Done()
        }(i)
    }
    wg.Wait()
    fmt.Println("parallel task done success ")
}

//使用chanel 来实现并发
func parallel2() {
    ch := make(chan int)
    //done := make(chan int)
    for i := 0; i < jobsCount; i++ {
        go DoTask(i, ch)
    }

    for i := 0; i < jobsCount; i++ {
        fmt.Printf(" task %d done \n", <-ch)
    }
}

//我是完成耗时工作的
func DoTask(taskid int, ch chan int) {
    fmt.Printf("task %d doing !!!!!!!!!!!!!\n", taskid)
    a := int(rand.Float64() * 1000.0)
    a = 500
    time.Sleep(time.Duration(a) * time.Millisecond)
    ch <- taskid
}

//测量函数执行时间
func timer(funcName string, f func()) {
    t1 := time.Now()
    f()
    fmt.Printf("%s  fun cost  %s \n", funcName, time.Now().Sub(t1))
}

这里假设要完成十个耗时的任务,每个任务需要500ms或者随机时间,分别用串行和两个并行函数来完成; 

串行执行的顺序就是每个任务执行完才能执行下个任务,并行执行就是十个任务同时执行不分先后(可以认为同时开始),

使用计时器对每个函数执行时间进行度量, 

main 启动后,查看控制台上结果的输出:

hello  task 0 
hello  task 1 
hello  task 2 
hello  task 3 
hello  task 4 
hello  task 5 
hello  task 6 
hello  task 7 
hello  task 8 
hello  task 9 
serial task done success 
serial  fun cost  5.0042001s 
hello  task 9 
hello  task 4 
hello  task 5 
hello  task 6 
hello  task 7 
hello  task 8 
hello  task 2 
hello  task 0 
hello  task 3 
hello  task 1 
parallel task done success 
parallel  fun cost  500.0504ms 
task 9 doing !!!!!!!!!!!!!
task 4 doing !!!!!!!!!!!!!
task 3 doing !!!!!!!!!!!!!
task 6 doing !!!!!!!!!!!!!
task 7 doing !!!!!!!!!!!!!
task 8 doing !!!!!!!!!!!!!
task 1 doing !!!!!!!!!!!!!
task 0 doing !!!!!!!!!!!!!
task 2 doing !!!!!!!!!!!!!
task 5 doing !!!!!!!!!!!!!
 task 3 done 
 task 4 done 
 task 1 done 
 task 9 done 
 task 2 done 
 task 0 done 
 task 7 done 
 task 8 done 
 task 6 done 
 task 5 done 
parallel2  fun cost  500.3297ms 

Process finished with exit code 0

从控制台上我们可以看到串行执行的话,每个任务500ms ,总任务执行了5s ,

并行执行的话,两个并行并没有顺序,每个任务500ms,总任务也可以认为是500ms,

串行的代码10行,并行的代码分别为 15行和20行 ,相比之下时间上后者是前者的1/10,这就是并行的巨大优势,因此我们要多审视自己的代码,避免偷懒,面对高并发场景下才能实现高效完成任务; 

 新增循环中使用管道chanel的形式

//使用chan 接收等待任务完成(缺点:增加开销)
func paralle3() {
    //ch := make(chan int, jobsCount)
    ch := make(chan int)
    for i := 0; i < jobsCount; i++ {
        go func(idx int) {
            fmt.Printf("hello  task %d \n", idx)
            a := int(rand.Float64() * 1000.0)
            a = 500 //可注释掉该行使用随机数
            time.Sleep(time.Duration(a) * time.Millisecond)
            ch <- idx
        }(i)
    }
    for i := 0; i < jobsCount; i++ {
        fmt.Printf(" task %d done \n", <-ch)
    }
    fmt.Println("paralle3 task done success ")
}

 

 类似资料: