如何解决共享资源的访问冲突问题?

huangapple go评论140阅读模式
英文:

How to solve the problem of access conflict to shared resources?

问题

有一个测试服务,其中包含2个请求。这些请求使用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)
	}
}
英文:

There is a test service with 2 requests. Those requests use a shared resource in the form of the ActualOrders variable. Suppose that hundreds of parallel queries are running, there is a chance that a data conflict will occur in the ActualOrders variable. Especially when I'm looping through an array. To prevent this, will it be enough to use a Mutex, as I did in the example below?

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)
}
}

答案1

得分: 1

你可以使用RWMutex来改进你的实现。在getOrders函数中使用读锁,而在createOrder函数中使用写锁。这样可以在写入ActualOrders变量时获得独占访问权限,但允许共享读取操作:

var mutex sync.RWMutex

func getOrders(responseWriter http.ResponseWriter, request *http.Request) {
    ...
    mutex.RLock()
    ... 
    mutex.RUnlock()
}

func createOrder(responseWriter http.ResponseWriter, request *http.Request) {
    ...
    mutex.Lock()
    for _, order := range ActualOrders {
       ... 
    }
    ActualOrders = append(ActualOrders, newOrder)
    mutex.Unlock()
}
英文:

Using a mutex as you did will protect from data races. Your implementation can be improved though.

You can use a RWMutex, use a read-lock for the getOrders function, and a lock for the createOrder function. This will allow exclusive access to the ActualOrders variable when you are writing to it, but shared reads will be allowed:

var mutex sync.RWMutex
func getOrders(responseWriter http.ResponseWriter, request *http.Request) {
...
mutex.RLock()
... 
mutex.RUnlock()
}
func createOrder(responseWriter http.ResponseWriter, request *http.Request) {
...
mutex.Lock()
for _, order := range ActualOrders {
... 
}
ActualOrders = append(ActualOrders, newOrder)
mutex.Unlock()
}
</details>

huangapple
  • 本文由 发表于 2023年1月15日 00:13:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/75119345.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定