英文:
Golang Goroutines Handle Concurrently Multiple Request
问题
用户可以通过我的项目购买产品。但是,购买请求中的产品库存数量必须等于或大于请求的数量。如果满足条件,购买记录将保存在表中。然后,在另一个表中更新该产品的库存数量。因此,有3个操作,如果我从另一个 Golang 应用程序发送 20 个并发请求来购买具有 100 个库存数量的 A 产品,它会出现错误。它在数据库中打开了 20 个购买记录,并将库存数量更新为 60。通常,在处理 5 个请求后,它应该因为产品库存不足而抛出错误。我无法调整这个方法来实现这个目的。我该怎么办?
这是该方法,它调用了存储库方法 3 次,但在那里没有特殊的操作,只有 gorm 的保存和更新操作:
var wg sync.WaitGroup
wg.Add(1)
errs := make(chan error, 1)
go func(ctx context.Context, request *dto.CreateTicketPurchaseRequest) {
defer wg.Done()
productStockCount, err := t.stockRepo.GetTicketStockCount(productId) // 首先检查可用库存数量
if err != nil {
errs <- err
return
}
if productStockCount.StockCount < payload.Quantity {
err = fmt.Errorf("没有足够的产品")
errs <- err
return
}
u := new(entity.TicketPurchases)
u.Quantity = payload.Quantity
u.UserId = payload.UserId
u.StockId = productId
err = t.purchaseRepo.PurchaseTicket(u) // 如果库存数量正常,则购买产品
if err != nil {
errs <- err
return
}
productStockCount.StockCount = productStockCount.StockCount - u.Quantity
err = t.stockRepo.RemoveStockCount(productStockCount) // 更新产品的库存数量
if err != nil {
errs <- err
return
}
close(errs)
}(ctx, payload)
wg.Wait()
if err = <-errs; err != nil {
return c.JSON(http.StatusBadRequest, nil)
}
英文:
User can purchase products with my project. However, product stock count must be equal or greater then requested quantity in purchase request. If It is ok, then a purchase record is saved on the table. After then, stock count is updated for this product in the another table. So there are 3 operations and if I send 20 concurrently requests to purchase 20 of product A which has 100 stock count in the db from another golang app, it works wrongly. It opens 20 purchase records in db and stock count updated as 60. Normally, after 5 requests operated, it must throws error because of stock count of product. I could not tweak method for this purpose. How Can I do?
This is the method, It calls repository methods 3 times but there is no special operations there only gorm save and update operations:
var wg sync.WaitGroup
wg.Add(1)
errs := make(chan error, 1)
go func(ctx context.Context, request *dto.CreateTicketPurchaseRequest) {
defer wg.Done()
productStockCount, err := t.stockRepo.GetTicketStockCount(productId) // first check available stock count
if err != nil {
errs <- err
return
}
if productStockCount.StockCount < payload.Quantity {
err = fmt.Errorf("no enough products")
errs <- err
return
}
u := new(entity.TicketPurchases)
u.Quantity = payload.Quantity
u.UserId = payload.UserId
u.StockId = productId
err = t.purchaseRepo.PurchaseTicket(u) // if stock count is ok, then purchase product
if err != nil {
errs <- err
return
}
productStockCount.StockCount = productStockCount.StockCount - u.Quantity
err = t.stockRepo.RemoveStockCount(productStockCount) // update stock count for product
if err != nil {
errs <- err
return
}
close(errs)
}(ctx, payload)
wg.Wait()
if err = <-errs; err != nil {
return c.JSON(http.StatusBadRequest, nil)
}
答案1
得分: 1
我假设多线程性能不是关键问题,而且你只在同一个数据库上运行一个应用程序实例。
你可以使用 sync.Mutex 来锁定整个 stockrepo。在进行任何操作之前锁定互斥锁,并在事务完成之前不要解锁。建议使用 defer
和 Unlock
。
示例代码:
var mu sync.Mutex
func doStuff() {
mu.Lock()
defer mu.Unlock()
// 获取票数、检查、更新等操作
}
一个更高效但更复杂的解决方案是按 productID 进行锁定,但这可能超出了你的问题范围。
英文:
I am assuming multi-threaded performance is not critical and you are only running one instance of the app on the same database.
You could use a sync.Mutex to Lock the entire stockrepo. Lock the mutex before doing anything, and don't unlock until the transaction is done. It is recommended to use defer
with Unlock
.
example:
var mu sync.Mutex
func doStuff() {
mu.Lock()
defer mu.Unlock()
// get ticket count, check, update, etc.
}
a more efficient but more complex solution is to lock per productID, but that's a bit more complex and probably out of scope for your questions.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论