美文网首页
测试一下Go语言goroutine的魅力

测试一下Go语言goroutine的魅力

作者: 黄刚刚 | 来源:发表于2021-06-28 11:46 被阅读0次

    本段测试代码的说明

    这里用go语言简单的写了一个创建订单的业务流程,目的是测试一下Go语言的goroutine的魅力,注意,只是测试目的,代码并不存在任何实用性.

    测试逻辑说明

    创建订单的业务逻辑,一般情况下都需要下面的几个步骤:
    1.检查订单商品ID是否有效
    2.检查订单商品ID的数量是否足够本次创建订单
    3.检查优惠券是否可用
    4.计算每个商品的订单价格(这里可能是查询商品的最新单价*数量)
    5.计算订单金额
    6.创建订单

    测试代码中,已经把上面的6个步骤,全部封装成了独立的函数,每个函数内都启用了延时1秒的操作来模拟真实
    情况下的一些时间的耗费;

    最终目的,是想要分别尝试一下,用goroutine方式和不用goroutine方式的区别,和性能差异!

    测试结果

    这里是测试代码的两次运行结果,从结果中可以看到,首先是程序的执行过程的不同,再就是程序耗费时间的不同,启用goroutine的程序耗费时间是3.0122762s,而没启用goroutine的程序耗费时间是9.003462s,这性能差距兼职令人惊喜啊

    D:\go\src\github.com\just5325\day01\04_channel>go run main.go
    请选择是否启用子协程处理任务(1:使用 0:不使用 直接回车默认为1):
    1
    正在计算订单金额
    正在检查商品是否有效 ID: 1
    正在计算每个商品的订单价格 ID: 1
    正在检查商品数量是否有效 ID:1 数量:1
    正在检查优惠券是否可用 ID:1
    正在计算每个商品的订单价格 ID: 2
    正在检查商品是否有效 ID: 2
    正在检查商品数量是否有效 ID:2 数量:1
    正在创建订单,订单金额: 100
    结束! [已启用子协程],程序耗费时间:3.0122762s
    
    D:\go\src\github.com\just5325\day01\04_channel>go run main.go
    请选择是否启用子协程处理任务(1:使用 0:不使用 直接回车默认为1):
    0
    正在检查商品是否有效 ID: 1
    正在检查商品是否有效 ID: 2
    正在检查商品数量是否有效 ID:1 数量:1
    正在检查商品数量是否有效 ID:2 数量:1
    正在检查优惠券是否可用 ID:1
    正在计算每个商品的订单价格 ID: 1
    正在计算每个商品的订单价格 ID: 2
    正在计算订单金额
    正在创建订单,订单金额: 100
    结束! [未启用子协程],程序耗费时间:9.003462s
    

    测试代码

    package main
    
    import (
        "fmt"
        "time"
    )
    
    // 是否启用子子协程 false:不启用(就像我们写PHP代码一样从上至下的顺序运行代码) true:启用(启用多个协程同时处理不同的业务逻辑,相关联的数据通过通道传输)
    var subGoroutine bool = false
    
    /**
    @title 检查商品是否有效
    @params productData []map[string]int 商品数据
    @params out chan<- bool 发送数据的通道,通道内传递的值为是否有效 true:有效 false:无效
    */
    func checkProductById(productData []map[string]int, out chan bool) {
        checkProductId := make(chan int, 10)
        funcRet := make(chan bool, 10)
        // 开启子协程,把商品ID逐个发送到通道中
        go func() {
            for _, value := range productData {
                checkProductId <- value["id"]
            }
            // 关闭通道
            close(checkProductId)
        }()
    
        // 开启子协程,遍历商品ID,检查通道中的每个商品是否有效
        go func() {
            // for range遍历通道,值为商品ID,通道关闭后会自动结束循环,所以这里只管遍历接收通道的数据并处理后发送数据到通道就OK了
            for value := range checkProductId {
                // 延时1秒,代表这里执行的各种耗费时间的任务
                fmt.Println("正在检查商品是否有效 ID:", value)
                time.Sleep(time.Second * 1)
    
                // 我们就当所有商品ID检查结果都为有效
                funcRet <- true
            }
            close(funcRet)
        }()
    
        // 遍历通道,发送数据
        for v := range funcRet {
            out <- v
        }
        close(out)
    }
    
    /*
    @title 检查商品数量是否有效
    @params productData []map[string]int 商品数据
    @params out chan<- bool 发送数据的通道,通道内传递的值为是否有效 true:有效 false:无效
    */
    func checkProductNumById(productData []map[string]int, out chan bool) {
        checkProductNum := make(chan map[string]int, 10)
        funcRet := make(chan bool, 10)
        // 开启子协程,把商品ID逐个发送到通道中
        go func() {
            for _, value := range productData {
                checkProductNum <- value
            }
            // 关闭通道
            close(checkProductNum)
        }()
    
        // 开启子协程,遍历商品信息,检查通道中的每个商品的数量是否有效
        go func() {
            // for range遍历通道,值为商品信息,通道关闭后会自动结束循环,所以这里只管遍历接收通道的数据并处理后发送数据到通道就OK了
            for value := range checkProductNum {
                // 延时1秒,代表这里执行的各种耗费时间的任务
                fmt.Printf("正在检查商品数量是否有效 ID:%d 数量:%d\n", value["id"], value["num"])
                time.Sleep(time.Second * 1)
    
                // 我们就当所有商品ID检查结果都为有效
                funcRet <- true
            }
            close(funcRet)
        }()
    
        // 遍历通道,发送数据
        for v := range funcRet {
            out <- v
        }
        close(out)
    }
    
    /*
    @title 检查优惠券是否可用
    @params couponId int 优惠券ID
    @params out chan<- bool 发送数据的通道,通道内传递的值为是否有效 true:有效 false:无效
    */
    func checkCoupon(couponId int, out chan int) {
        // 延时1秒,代表这里执行的各种耗费时间的任务
        fmt.Printf("正在检查优惠券是否可用 ID:%d\n", couponId)
        time.Sleep(time.Second * 1)
    
        // 发送数据到通道
        out <- 100
        close(out)
    }
    
    /**
    @title 计算每个商品的订单价格(这里可能是查询商品的最新单价*数量)
    @params productData []map[string]int 商品数据
    @params out chan<- int 发送数据的通道,通道内传递的值为是订单中商品的金额
    */
    func getOrderProductAmountById(productData []map[string]int, out chan int) {
        checkProductId := make(chan int, 10)
        funcRet := make(chan int, 10)
        // 开启子协程,把商品ID逐个发送到通道中
        go func() {
            for _, value := range productData {
                checkProductId <- value["id"]
            }
            // 关闭通道
            close(checkProductId)
        }()
    
        // 开启子协程,遍历商品ID,检查通道中的每个商品是否有效
        go func() {
            // for range遍历通道,值为商品ID,通道关闭后会自动结束循环,所以这里只管遍历接收通道的数据并处理后发送数据到通道就OK了
            for value := range checkProductId {
                // 延时1秒,代表这里执行的各种耗费时间的任务
                fmt.Println("正在计算每个商品的订单价格 ID:", value)
                time.Sleep(time.Second * 1)
    
                // 我们就当所有商品ID检查结果都为有效
                funcRet <- 100
            }
            close(funcRet)
        }()
    
        // 遍历通道,发送数据
        for v := range funcRet {
            out <- v
        }
        close(out)
    }
    
    /**
    @title 计算订单金额
    @params productAmount chan int 商品金额
    @params couponAmount chan int 优惠券金额
    @params out chan<- int 发送数据的通道,通道内传递的值为订单金额
    */
    func getOrderAmount(productAmount chan int, couponAmount chan int, out chan int) {
        // 延时1秒,代表这里执行的各种耗费时间的任务
        fmt.Println("正在计算订单金额")
        time.Sleep(time.Second * 1)
    
        // 订单总价
        var orderAmount int
        for v := range productAmount {
            orderAmount += v
        }
        // 不考虑满减,能用的优惠券就减
        for v := range couponAmount {
            orderAmount -= v
        }
        // 发送数据到通道
        out <- orderAmount
        close(out)
    }
    
    /**
    @title 创建订单
    @params orderAmountChannel chan int 订单金额
    */
    func create(orderAmountChannel chan int) {
        // 订单总价
        var orderAmount int
        for v := range orderAmountChannel {
            orderAmount = v
        }
    
        // 延时1秒,代表这里执行的各种耗费时间的任务
        fmt.Println("正在创建订单,订单金额:", orderAmount)
        time.Sleep(time.Second * 1)
    }
    
    /**
    @title 获取用户输入信息,配置是否使用子协程
    @params isSubGoroutine int 请选择是否启用子协程处理任务(1:使用 0:不使用 直接回车默认为1)
    */
    func inputIsSubGoroutine() bool {
        // 声明一个int类型的变量,接收用户输入的信息
        var isSubGoroutine int
        // 打印输入提示信息
        fmt.Println("请选择是否启用子协程处理任务(1:使用 0:不使用 直接回车默认为1):")
        // 预先声明一个error类型的变量,用作接收错误信息
        var err error
        // fmt.Scanln从标准输入os.Stdin读取文本,会在读取到换行时停止.
        _, err = fmt.Scanln(&isSubGoroutine)
        // 使用if语句,初始化定义一个变量为空,如果有错误信息,提示错误并退出程序
        if errorMsg := ""; err != nil {
            // 这里我们正好也练习一下switch的写法,实际上和php也是一样的
            switch err.Error() {
            case "unexpected newline":
                // 未输入参数时,不报错,默认为1,使用子协程
                isSubGoroutine = 1
            case "expected newline", "expected integer":
                errorMsg = "参数错误,只允许输入整型数字的 0 或者 1 "
            default:
                errorMsg = fmt.Sprintf("参数错误,Error: [%s]", err.Error())
            }
            fmt.Println(errorMsg)
        }
        if isSubGoroutine == 1 {
            subGoroutine = true
        } else {
            subGoroutine = false
        }
        return subGoroutine
    }
    
    func main() {
        // 获取用户输入信息,配置是否使用子协程
        inputIsSubGoroutine()
    
        // 程序开始运行时间
        startDate := time.Now()
        // 练习一下channel通道的使用,简单的写一下创建订单的业务流程吧
    
        // 声明一个变量productData,类型为切片,值为map类型,存储创建订单的商品信息
        var productData []map[string]int
        productData = append(productData, map[string]int{
            // id的表示商品id
            "id": 1,
            // num的表示购买数量
            "num": 1,
        })
        productData = append(productData, map[string]int{
            "id":  2,
            "num": 1,
        })
    
        // 声明一个变量couponId,表示订单所使用的优惠券ID
        couponId := 1
        // OK,到这里进入主业务流程了
        // 一般情况下都需要的几个步骤:
        // 1.检查订单商品ID是否有效
        // 2.检查订单商品ID的数量是否足够本次创建订单
        // 3.检查优惠券是否可用
        // 4.计算每个商品的订单价格(这里可能是查询商品的最新单价*数量)
        // 5.计算订单金额
        // 6.创建订单
    
        // 创建一个切片,存储所有的商品ID
        productIds := make([]int, 0)
        for _, value := range productData {
            productIds = append(productIds, value["id"])
        }
    
        // 1.检查订单商品ID是否有效 +++++++++++++++++++++++++++++++++++++++++ Start
        // 创建一个通道,传输检查错误信息
        checkProductByIdFuncRet := make(chan bool, 10)
        // 判断配置是否启用子协程
        if subGoroutine {
            go checkProductById(productData, checkProductByIdFuncRet)
        } else {
            checkProductById(productData, checkProductByIdFuncRet)
        }
        // 1.检查订单商品ID是否有效 +++++++++++++++++++++++++++++++++++++++++ End
    
        // 2.检查订单商品ID的数量是否足够本次创建订单 ++++++++++++++++++++++++++ Start
        // 创建一个通道,传输检查错误信息
        checkProductNumByIdFuncRet := make(chan bool, 10)
        // 判断配置是否启用子协程
        if subGoroutine {
            go checkProductNumById(productData, checkProductNumByIdFuncRet)
        } else {
            checkProductNumById(productData, checkProductNumByIdFuncRet)
        }
        // 2.检查订单商品ID的数量是否足够本次创建订单 ++++++++++++++++++++++++++ End
    
        // 3.检查优惠券是否可用 ++++++++++++++++++++++++++++++++++++++++++++ Start
        // 创建一个通道,传输检查错误信息
        checkCouponFuncRet := make(chan int, 10)
        // 判断配置是否启用子协程
        if subGoroutine {
            go checkCoupon(couponId, checkCouponFuncRet)
        } else {
            checkCoupon(couponId, checkCouponFuncRet)
        }
        // 3.检查优惠券是否可用 ++++++++++++++++++++++++++++++++++++++++++++ End
    
        // 计算每个商品的订单价格(这里可能是查询商品的最新单价*数量) ++++++++++++++ Start
        // 创建一个通道,传输每个商品的订单价格
        getOrderProductAmountByIdFuncRet := make(chan int, 10)
        // 判断配置是否启用子协程
        if subGoroutine {
            go getOrderProductAmountById(productData, getOrderProductAmountByIdFuncRet)
        } else {
            getOrderProductAmountById(productData, getOrderProductAmountByIdFuncRet)
        }
        // 计算每个商品的订单价格(这里可能是查询商品的最新单价*数量) ++++++++++++++ End
    
        // 计算订单金额 +++++++++++++++++++++++++++++++++++++++++++++++++++ Start
        // 创建一个通道,传输订单金额
        getOrderAmountFuncRet := make(chan int, 10)
        // 判断配置是否启用子协程
        if subGoroutine {
            go getOrderAmount(getOrderProductAmountByIdFuncRet, checkCouponFuncRet, getOrderAmountFuncRet)
        } else {
            getOrderAmount(getOrderProductAmountByIdFuncRet, checkCouponFuncRet, getOrderAmountFuncRet)
        }
        // 计算订单金额 +++++++++++++++++++++++++++++++++++++++++++++++++++ Start
    
        // 遍历通道数据,有false就输出错误信息
        for v := range checkProductByIdFuncRet {
            if !v {
                fmt.Println("有无效的商品,请重新下单")
                // 发送错误信息后,结束主goroutine,子goroutine也会自动结束
                return
            }
        }
        for v := range checkProductNumByIdFuncRet {
            if !v {
                fmt.Println("商品数量不足,请重新下单")
                // 发送错误信息后,结束主goroutine,子goroutine也会自动结束
                return
            }
        }
    
        // 代码执行到这里,说明错误检查都通过了,所有需要计算的业务逻辑也都计算完成了,
        // 该拿着计算好的数据直接写入数据库完成订单创建了,这一步就不需要启动子协程了
        create(getOrderAmountFuncRet)
    
        // 程序开始运行时间
        subtract := time.Now().Sub(startDate)
        var subGoroutineMsg string
        if subGoroutine {
            subGoroutineMsg = "已启用子协程"
        } else {
            subGoroutineMsg = "未启用子协程"
        }
        fmt.Printf("结束! [%v],程序耗费时间:%v\n", subGoroutineMsg, subtract)
    }
    

    相关文章

      网友评论

          本文标题:测试一下Go语言goroutine的魅力

          本文链接:https://www.haomeiwen.com/subject/sdihultx.html