Go服务中共享资源ActualOrders访问冲突:使用Mutex是否足够?
关于并发场景下使用Mutex保护共享切片的问题
我有一个包含两个请求的测试服务,请求均使用ActualOrders变量作为共享资源。当存在数百个并行查询时,ActualOrders变量可能出现数据冲突,尤其是在遍历数组的场景下。为避免该问题,如下示例中使用Mutex是否足够?
main.go:
package main import ( "encoding/json" "errors" "fmt" "net/http" "os" "time" ) type Order struct { Room string `json:"room"` UserEmail string `json:"email"` From time.Time `json:"from"` To time.Time `json:"to"` } var ActualOrders = []Order{} var mutex sync.Mutex func getOrders(responseWriter http.ResponseWriter, request *http.Request) { userEmail := request.URL.Query().Get("email") results := []Order{} mutex.Lock() for _, item := range ActualOrders { if item.UserEmail == userEmail { results = append(results, item) } } mutex.Unlock() bytes, err := json.Marshal(results) if err != nil { http.Error(responseWriter, err.Error(), http.StatusInternalServerError) return } responseWriter.Header().Set("Content-type", "application/json") responseWriter.WriteHeader(http.StatusOK) responseWriter.Write(bytes) } func createOrder(responseWriter http.ResponseWriter, request *http.Request) { var newOrder Order requestBody := request.Body defer request.Body.Close() err := json.NewDecoder(requestBody).Decode(&newOrder) if err != nil { http.Error(responseWriter, err.Error(), http.StatusBadRequest) return } mutex.Lock() for _, order := range ActualOrders { if !(newOrder.To.Before(order.From) || newOrder.From.After(order.To)) { http.Error(responseWriter, http.StatusText(http.StatusConflict), http.StatusConflict) return } } ActualOrders = append(ActualOrders, newOrder) mutex.Unlock() responseWriter.WriteHeader(http.StatusCreated) } func main() { mux := http.NewServeMux() mux.HandleFunc("/orders", getOrders) mux.HandleFunc("/order", createOrder) err := http.ListenAndServe(":8080", mux) if errors.Is(err, http.ErrServerClosed) { fmt.Printf("server closed\n") } else if err != nil { fmt.Printf("error starting server: %s\n", err) os.Exit(1) } }
解答
是的,使用sync.Mutex在这里足以避免并发数据冲突,但代码存在两处关键问题需要修正,同时可以做一些优化提升性能:
1. Mutex的有效性说明
sync.Mutex的Lock()和Unlock()方法确保了对共享资源ActualOrders的互斥访问:
- 同一时间只有一个goroutine能读取或修改该切片,彻底避免了并发遍历与修改时的竞态条件(比如遍历过程中切片扩容导致的索引越界、数据读取不一致等问题)。
- 无论是
getOrders中的读操作(遍历切片),还是createOrder中的读+写操作(冲突检查+添加新订单),都被锁正确保护,符合并发安全的要求。
2. 必须修正的代码问题
(1)缺失sync包导入
代码中使用了sync.Mutex但未导入sync包,会导致编译错误,需在import块中添加:
import ( // ... 原有导入 "sync" )
(2)createOrder中的死锁风险
createOrder函数中,当检测到订单冲突时直接返回,未调用mutex.Unlock(),会导致锁永远无法释放,后续所有请求都会阻塞死锁。解决方法是用defer确保锁一定会被释放:
修正后的createOrder函数:
func createOrder(responseWriter http.ResponseWriter, request *http.Request) { var newOrder Order requestBody := request.Body defer request.Body.Close() err := json.NewDecoder(requestBody).Decode(&newOrder) if err != nil { http.Error(responseWriter, err.Error(), http.StatusBadRequest) return } mutex.Lock() defer mutex.Unlock() // 用defer保证锁在函数退出时必被释放 for _, order := range ActualOrders { if !(newOrder.To.Before(order.From) || newOrder.From.After(order.To)) { http.Error(responseWriter, http.StatusText(http.StatusConflict), http.StatusConflict) return } } ActualOrders = append(ActualOrders, newOrder) responseWriter.WriteHeader(http.StatusCreated) }
3. 性能优化建议
如果你的服务中读请求(/orders)远多于写请求(/order),可以改用sync.RWMutex替代sync.Mutex:
RWMutex的RLock()/RUnlock()支持多个goroutine同时读取共享资源,只有写操作(Lock()/Unlock())会互斥,能大幅提升高并发下的读性能。
另外,当订单数量较大时,遍历整个切片做冲突检查或用户订单查询的效率较低,可以考虑维护辅助索引(比如按用户邮箱或房间号建立map),减少锁的持有时间,提升整体性能。
内容的提问来源于stack exchange,提问作者Nurzhan Nogerbek
相关产品推荐
相关产品推荐

