本段测试代码的说明
这里用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)
}
网友评论