网络编程
位置:首页>> 网络编程>> Go语言>> GoLang抽奖系统简易实现流程

GoLang抽奖系统简易实现流程

作者:上后左爱  发布时间:2024-04-30 10:02:24 

标签:GoLang,Go,抽奖

业务难点

设计一个抽奖系统,这个系统并不是具体化,是抽象化,具有以下的几个难点:

1、抽奖业务需要 复杂多变

2、奖品类型和概率设置

3、公平的抽奖和安全的发奖

4、并发安全性问题 一个人不能枪多次

5、高效的抽奖和发奖,提供高并发和性能

6、 如何使用redies进行优化

技术选项

  • 高并发 Go 协程优先于 PHP多进程,Java的 多线程模型

  • 高性能编译后的二进制优先于PHP解释性和Java虚拟机

  • 高效的网络模型 epoll 模型优先于PHPBIO模型和Java NIO模型

抽奖活动

  • 年会抽奖,彩票刮奖,微信摇一摇,抽奖大转盘,集福卡等活动,本项目以 抽奖大转盘作为一种活动进行设计。

  • 项目实战内容: 框架/核心代码后台功能 ,合理设置奖品和发送奖品, mysql+优化-使用redies 发奖计划与奖品池, 压力测试和更多的运营策略(系统的性能有更好的了解,运营产品的策略有更多), 引入 thirft 框架(RPC 框架), 设计接口生成代码, 服务端接口和客户端程序

需求分析

1. go mod 配置

2. 配置国内代理: go env -w GOPROXY=https://goproxy.cn,https://goproxy.io,direct

3. go get -u -v github.com/kataras/iris 下载包在 GOPATH的PKG目录下

4. iris:功能: 安全认证,缓存 cookies 文件 MVC, 模板 丰富的示例代码

5. https://iris-go.com/v10/recipe

*年会抽奖程序

使用的是iris 这个web 框架 进行处理

/**
*  curl http://localhost:8080/
*  curl --data "users=123,567" http://localhost:8080/import
*  curl http://localhost:8080/lucky
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"strings"
"sync"
"time"
)
var userList []string // 共享变量读写前后 需要增加 锁的设定 简单方式添加互斥锁
var mu sync.Mutex
type lotteryController struct {
Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
userList = []string{}
mu = sync.Mutex{}
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error: %s\n", err))
return
}
}
func (c *lotteryController) Get() string {
count := len(userList)
return fmt.Sprintf("当前总共参与抽奖的用户数:%d\n", count)
}
// PostImport POST http://localhost:8090/import
// params : users
func (c *lotteryController) PostImport() string {
strUsers := c.Ctx.FormValue("users")
users := strings.Split(strUsers, ",")
// 批量线程导入时候 发现有多线程的问题 数据统计不正确
mu.Lock()
defer mu.Unlock()
count1 := len(userList)
for _, u := range users {
u = strings.TrimSpace(u)
if len(u) > 0 {
userList = append(userList, u)
}
}
count2 := len(userList)
return fmt.Sprintf("当前总共参与抽奖的用户数:%d, 成功导入的用户数:%d\n", count2, count2-count1)
}
// GetLucky GET http://localhost:8090/lucky
func (c *lotteryController) GetLucky() string {
// 抽奖地方进行锁的判断
mu.Lock()
defer mu.Unlock()
count := len(userList)
if count > 1 {
index := rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(int32(count))
user := userList[index]
// 需要 删除被挑选过的人 直接可以删除 当前index 下的数据 更好
userList = append(userList[0:index], userList[index+1:]...)
return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
} else if count == 1 {
user := userList[0]
return fmt.Sprintf("当前中奖用户:%s, 剩余用户数:%d\n", user, count-1)
} else {
return fmt.Sprintf("当前中奖完毕,没有用户参与中奖\n")
}
}

单元测试问题,对于 userList 的 多线程下发生数据竞争问题 :

package main
import (
"fmt"
"github.com/kataras/iris/v12/httptest"
"sync"
"testing"
)
func TestMVC(t *testing.T) {
app := newApp()
e := httptest.New(t, app)
// 使用同步等待锁
var wg sync.WaitGroup
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:0\n")
for i := 0; i < 100; i++ {
wg.Add(1)
// 不会出现协程并发性问题
go func(i int) {
defer wg.Done()
e.POST("/import").WithFormField("users", fmt.Sprintf("test_u%d", i)).Expect().Status(httptest.StatusOK)
}(i)
}
wg.Wait()
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:100\n")
e.GET("/lucky").Expect().Status(httptest.StatusOK)
e.GET("/").Expect().Status(httptest.StatusOK).Body().Equal("当前总共参与抽奖的用户数:99\n")
}

微信摇一摇得抽奖活动

/**
* 微信摇一摇得功能
* wrk -t10 -c10 -d5 http://localhost:8080/lucky 进行压力测试 查看代码是否有竞争异常问题和 接口请求量速度
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"log"
"math/rand"
"os"
"sync"
"time"
)
var mu sync.Mutex
const (
giftTypeCoin      = iota // 虚拟币
giftTypeCoupon           // 不同卷
giftTypeCouponFix        // 不同卷
giftTypeRealSmall        // 实物小奖
giftTypeRealLarge        // 十五大奖
)
type gift struct {
id       int
name     string
pic      string
link     string
gType    int
data     string // 奖品数据(特定得配置信息)
dataList []string
total    int
left     int
inuse    bool
rate     int // 万分之N
rateMin  int
rateMax  int
}
// 最大中奖号码
const rateMax = 1000
var logger *log.Logger
// 奖品类表
var giftList []*gift
type lotteryController struct {
Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func initLog() {
f, _ := os.Create("G:\\goLandProject\\lottery_demo.log")
logger = log.New(f, "", log.Ldate|log.Lmicroseconds)
}
func initGift() {
giftList = make([]*gift, 5)
g1 := gift{
id:       1,
name:     "手机大奖",
pic:      "",
link:     "",
gType:    giftTypeRealLarge,
data:     "",
dataList: nil,
total:    20000,
left:     20000,
inuse:    true,
rate:     10000,
rateMin:  0,
rateMax:  0,
}
g2 := gift{
id:       2,
name:     "充电器",
pic:      "",
link:     "",
gType:    giftTypeRealSmall,
data:     "",
dataList: nil,
total:    5,
left:     5,
inuse:    false,
rate:     10,
rateMin:  0,
rateMax:  0,
}
g3 := gift{
id:       3,
name:     "优惠卷满200减50",
pic:      "",
link:     "",
gType:    giftTypeCouponFix,
data:     "mall-coupon-2018",
dataList: nil,
total:    50,
left:     50,
inuse:    false,
rate:     500,
rateMin:  0,
rateMax:  0,
}
g4 := gift{
id:       4,
name:     "直降优惠卷",
pic:      "",
link:     "",
gType:    giftTypeCoupon,
data:     "",
dataList: []string{"c01", "c02", "c03", "c04", "c05"},
total:    50,
left:     50,
inuse:    false,
rate:     100,
rateMin:  0,
rateMax:  0,
}
g5 := gift{
id:       5,
name:     "金币",
pic:      "",
link:     "",
gType:    giftTypeCoin,
data:     "10金币",
dataList: nil,
total:    100,
left:     100,
inuse:    false,
rate:     5000,
rateMin:  0,
rateMax:  0,
}
giftList[0] = &g1
giftList[1] = &g2
giftList[2] = &g3
giftList[3] = &g4
giftList[4] = &g5
// s数据整理 中奖区间数据
rateStart := 0
for _, data := range giftList {
if !data.inuse {
continue
}
data.rateMin = rateStart
data.rateMax = rateStart + data.rate
if data.rateMax >= rateMax {
data.rateMax = rateMax
rateStart = 0
} else {
rateStart += data.rate
}
}
}
func main() {
initLog()
initGift()
mu = sync.Mutex{}
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) Get() string {
count := 0
total := 0
for _, data := range giftList {
if data.inuse && (data.total == 0 || (data.total > 0 && data.left > 0)) {
count++
total += data.left
}
}
return fmt.Sprintf("当前有效奖品种类数量:%d, 限量奖品总数量:%d\n", count, total)
}
// GetLucky  http://localhost:8080/lucky
func (c *lotteryController) GetLucky() map[string]interface{} {
mu.Lock()
defer mu.Unlock()
code := luckyCode()
ok := false
result := make(map[string]interface{})
result["success"] = ok
// 对 code 与 rateMin -rateMax 区间内进行对比 判断是否获奖
for _, data := range giftList {
if !data.inuse || (data.total > 0 && data.left <= 0) {
continue
}
if data.rateMin <= int(code) && data.rateMax > int(code) {
sendData := ""
switch data.gType {
case giftTypeCoin:
ok, sendData = sendCoin(data)
case giftTypeCoupon:
ok, sendData = sendCoupon(data)
case giftTypeCouponFix:
ok, sendData = sendCouponFix(data)
case giftTypeRealSmall:
ok, sendData = sendRealSmall(data)
case giftTypeRealLarge:
ok, sendData = sendRealLarge(data)
}
if ok {
// 中奖后得到奖品 生成中奖记录
saveLuckyData(code, data, sendData)
result["success"] = true
result["id"] = data.id
result["name"] = data.name
result["data"] = sendData
break
}
}
}
return result
}
func luckyCode() int32 {
seed := time.Now().UnixNano()
code := rand.New(rand.NewSource(seed)).Int31n(int32(rateMax))
return code
}
func sendCoin(data *gift) (bool, string) {
if data.total == 0 {
// 数量无数
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "奖品已经发完"
}
}
// 不同优惠卷
func sendCoupon(data *gift) (bool, string) {
if data.left > 0 {
left := data.left - 1
data.left = left
return true, data.dataList[left%5]
} else {
return false, "奖品已经发完"
}
}
func sendCouponFix(data *gift) (bool, string) {
if data.total == 0 {
// 数量无数
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "奖品已经发完"
}
}
func sendRealSmall(data *gift) (bool, string) {
if data.total == 0 {
// 数量无数
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "奖品已经发完"
}
}
func sendRealLarge(data *gift) (bool, string) {
if data.total == 0 {
// 数量无数
return true, data.data
} else if data.left > 0 {
data.left -= 1
return true, data.data
} else {
return false, "奖品已经发完"
}
}
func saveLuckyData(code int32, g *gift, data string) {
logger.Printf("lucky, code =%d ,id =%d, name =%d, data =%s, left=%d \n", code, g.id, g.name, data, g.left)
}

微博抢红包

  • 红包得集合,红包内红包数量读写都存在并发安全性问题

  • 第一种方式 使用 Sync.Map 互斥锁得方式

/**
* 微信抢红包 普通得 map 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
*
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"sync"
"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type lotteryController struct {
Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
// 返回当前全部得红包
rs := make(map[uint32][2]int)
packageList.Range(func(key, value interface{}) bool {
id := key.(uint32)
list := value.([]uint)
var money int
for _, v := range list {
money += int(v)
}
rs[id] = [2]int{len(list), money}
return true
})
return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
uid, errUid := c.Ctx.URLParamInt("uid")
moeny, errMoney := c.Ctx.URLParamFloat64("money")
num, errNum := c.Ctx.URLParamInt("num")
if errUid != nil || errNum != nil || errMoney != nil {
fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
}
moenyTotal := int(moeny * 100)
if uid < 1 || moenyTotal < num || num < 1 {
return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
}
// 金额分配算法
r := rand.New(rand.NewSource(time.Now().UnixNano()))
rMax := 0.55 // 随机分配最大值
if num > 1000 {
rMax = 0.01
} else if num < 10 {
rMax = 0.80
}
list := make([]uint, num)
leftMoney := moenyTotal
leftNum := num
for leftNum > 0 {
if leftNum == 1 {
list[num-1] = uint(leftMoney)
break
}
// 剩余钱数等于剩余红包数每个红包进行均分
if leftMoney == leftNum {
for i := num - leftNum; i < num; i++ {
list[i] = 1
break
}
}
// 随机分配最大值
rMoney := int(float64(leftMoney-leftNum) * rMax)
m := r.Intn(rMoney)
if m < 1 {
m = 1
}
list[num-leftNum] = uint(m)
leftMoney -= m
leftNum--
}
// 红包得UUID
id := r.Uint32()
packageList.Store(id, list)
return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
id, errid := c.Ctx.URLParamInt("id")
uid, errUid := c.Ctx.URLParamInt("uid")
if errUid != nil || errid != nil {
return fmt.Sprintf("")
}
if uid < 1 || id < 1 {
return fmt.Sprintf("")
}
listq, ok := packageList.Load(uint32(id))
list := listq.([]int)
if !ok || len(list) < 1 {
return fmt.Sprintf("红包不存在, id =%d \n", id)
}
// 分配随机数获取红包
r := rand.New(rand.NewSource(time.Now().UnixNano()))
i := r.Intn(len(list))
money := list[i]
// 更新红包中列表信息
if len(list) > 1 {
if i == len(list)-1 {
packageList.Store(uint32(id), list[:i])
} else if i == 0 {
packageList.Store(uint32(id), list[1:])
} else {
packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
}
} else {
packageList.Delete(uint32(id))
}
return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
}

第二种方式: chan 队列方式 解决线程安全

/**
* 微信抢红包 普通得 map 发生 竞争情况 所以需要使用 互斥 sync.Map
* 在大量得写 和 读得情况下会发生 竞争
*
* 单核任务 修改成 16 核心 进行抢红包
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"math/rand"
"sync"
"time"
)
// 红包列表
var packageList *sync.Map = new(sync.Map)
type task struct {
id       uint32
callback chan uint
}
const taskNum = 16 // 初始化队列数量
var chTaskList []chan task = make([]chan task, taskNum)
type lotteryController struct {
Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
// 启动多个子线程进行 红包抢
for i := 0; i < taskNum; i++ {
chTaskList[i] = make(chan task)
go fetchPackageListMoney(chTaskList[i])
}
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get http://localhost:8080
func (c *lotteryController) Get() map[uint32][2]int {
// 返回当前全部得红包
rs := make(map[uint32][2]int)
packageList.Range(func(key, value interface{}) bool {
id := key.(uint32)
list := value.([]uint)
var money int
for _, v := range list {
money += int(v)
}
rs[id] = [2]int{len(list), money}
return true
})
return rs
}
// GetSet   http://localhost:8080/set?uid=1&money=100&num=100
func (c *lotteryController) GetSet() string {
uid, errUid := c.Ctx.URLParamInt("uid")
moeny, errMoney := c.Ctx.URLParamFloat64("money")
num, errNum := c.Ctx.URLParamInt("num")
if errUid != nil || errNum != nil || errMoney != nil {
fmt.Sprintf("errUid=%d, errMoney=%d, errNum=%d \n", errUid, errMoney, errNum)
}
moenyTotal := int(moeny * 100)
if uid < 1 || moenyTotal < num || num < 1 {
return fmt.Sprintf("参数数值异常, uid=%d, money=%d, num=%d \n", uid, moeny, num)
}
// 金额分配算法
r := rand.New(rand.NewSource(time.Now().UnixNano()))
rMax := 0.55 // 随机分配最大值
if num > 1000 {
rMax = 0.01
} else if num < 10 {
rMax = 0.80
}
list := make([]uint, num)
leftMoney := moenyTotal
leftNum := num
for leftNum > 0 {
if leftNum == 1 {
list[num-1] = uint(leftMoney)
break
}
// 剩余钱数等于剩余红包数每个红包进行均分
if leftMoney == leftNum {
for i := num - leftNum; i < num; i++ {
list[i] = 1
break
}
}
// 随机分配最大值
rMoney := int(float64(leftMoney-leftNum) * rMax)
m := r.Intn(rMoney)
if m < 1 {
m = 1
}
list[num-leftNum] = uint(m)
leftMoney -= m
leftNum--
}
// 红包得UUID
id := r.Uint32()
packageList.Store(id, list)
return fmt.Sprintf("/get?id=%d&uid=%d&num=%d", id, uid, num)
}
// GetGet http://localhost:8080/get?id=1&uid=1
func (c *lotteryController) GetGet() string {
id, errid := c.Ctx.URLParamInt("id")
uid, errUid := c.Ctx.URLParamInt("uid")
if errUid != nil || errid != nil {
return fmt.Sprintf("")
}
if uid < 1 || id < 1 {
return fmt.Sprintf("")
}
listq, ok := packageList.Load(uint32(id))
list := listq.([]int)
if !ok || len(list) < 1 {
return fmt.Sprintf("红包不存在, id =%d \n", id)
}
// 构造一个任务
callback := make(chan uint)
t := task{id: uint32(id), callback: callback}
// 发送任务
chTasks := chTaskList[id%taskNum]
chTasks <- t
// 接受返回结果值
money := <-callback
if money <= 0 {
return "很遗憾,没有抢到红包\n"
} else {
return fmt.Sprintf("恭喜你抢到一个红包, 红包金额:%d \n", money)
}
}
// 使用队列方式, 需要不断从chan 通道中获取数据
func fetchPackageListMoney(chTasks chan task) {
for {
t := <-chTasks
id := t.id
l, ok := packageList.Load(id)
if ok && l != nil {
// 分配随机数获取红包
list := l.([]int)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
i := r.Intn(len(list))
money := list[i]
// 更新红包中列表信息
if len(list) > 1 {
if i == len(list)-1 {
packageList.Store(uint32(id), list[:i])
} else if i == 0 {
packageList.Store(uint32(id), list[1:])
} else {
packageList.Store(uint32(id), append(list[:i], list[i+1:]...))
}
} else {
packageList.Delete(uint32(id))
}
t.callback <- uint(money)
} else {
t.callback <- 0
}
}
}

抽奖大转盘

后端设置各个奖品得中奖概率和数量限制,更新库存时候发现并发安全性质问题 和微信摇一摇 类似

使用CAS进行安全代码进行修改,不在使用同步锁,CAS乐观锁比sync.mutSync 会快一些

/**
* 大转盘程序
* curl http://localhost:8080/
* curl http://localhost:8080/debug
* curl http://localhost:8080/prize
* 固定几个奖品,不同的中奖概率或者总数量限制
* 每一次转动抽奖,后端计算出这次抽奖的中奖情况,并返回对应的奖品信息
*
* 增加互斥锁,保证并发库存更新的正常
* 压力测试:
* wrk -t10 -c100 -d5 "http://localhost:8080/prize"
*/
package main
import (
"fmt"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc"
"log"
"math/rand"
"strings"
"sync/atomic"
"time"
)
// Prate 奖品中奖概率
type Prate struct {
Rate  int    // 万分之N的中奖概率
Total int    // 总数量限制,0 表示无限数量
CodeA int    // 中奖概率起始编码(包含)
CodeB int    // 中奖概率终止编码(包含)
Left  *int32 // 剩余数使用CAS乐观锁 进行修改
}
var left = int32(1000)
// 奖品列表
var prizeList []string = []string{
"一等奖,火星单程船票",
"二等奖,凉飕飕南极之旅",
"三等奖,iPhone一部",
"", // 没有中奖
}
// 奖品的中奖概率设置,与上面的 prizeList 对应的设置
var rateList []Prate = []Prate{
//Prate{1, 1, 0, 0, 1},
//Prate{2, 2, 1, 2, 2},
Prate{5, 1000, 0, 9999, &left},
//Prate{100,0, 0, 9999, 0},
}
type lotteryController struct {
Ctx iris.Context
}
// 启动一个 iris 应用
func newApp() *iris.Application {
app := iris.New()
mvc.New(app.Party("/")).Handle(&lotteryController{})
return app
}
func main() {
app := newApp()
err := app.Listen(":8080")
if err != nil {
panic(fmt.Sprintf("web server start error : %s\n", err))
}
}
// Get GET http://localhost:8080/
func (c *lotteryController) Get() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("大转盘奖品列表:<br/> %s", strings.Join(prizeList, "<br/>\n"))
}
// GetPrize GET http://localhost:8080/prize
func (c *lotteryController) GetPrize() string {
c.Ctx.Header("Content-Type", "text/html")
// 第一步,抽奖,根据随机数匹配奖品
seed := time.Now().UnixNano()
r := rand.New(rand.NewSource(seed))
// 得到个人的抽奖编码
code := r.Intn(10000)
//fmt.Println("GetPrize code=", code)
var myPrize string
var prizeRate *Prate
// 从奖品列表中匹配,是否中奖
for i, prize := range prizeList {
rate := &rateList[i]
if code >= rate.CodeA && code <= rate.CodeB {
// 满足中奖条件
myPrize = prize
prizeRate = rate
break
}
}
if myPrize == "" {
// 没有中奖
myPrize = "很遗憾,再来一次"
return myPrize
}
// 第二步,发奖,是否可以发奖
if prizeRate.Total == 0 {
// 无限奖品
fmt.Println("中奖: ", myPrize)
return myPrize
} else if *prizeRate.Left > 0 {
// 还有剩余奖品
left := atomic.AddInt32(prizeRate.Left, -1)
if left >= 0 {
log.Printf("奖品:%s", myPrize)
return myPrize
}
}
// 有限且没有剩余奖品,无法发奖
myPrize = "很遗憾,再来一次"
return myPrize
}
// GetDebug GET http://localhost:8080/debug
func (c *lotteryController) GetDebug() string {
c.Ctx.Header("Content-Type", "text/html")
return fmt.Sprintf("获奖概率: %v", rateList)
}

抽奖活动总结

  • 并发安全性质问题,互斥锁,队列, CAS递减方式

  • 优化,通过散列减小单个集合得大小

来源:https://blog.csdn.net/qq_27217017/article/details/125960025

0
投稿

猜你喜欢

手机版 网络编程 asp之家 www.aspxhome.com