美文网首页
测试一下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