如果在Golang中使用mutex.lock()锁定函数,如何发回响应?
我有这个功能。
func (s *eS) Post(param *errorlogs.Q) (*errorlogs.Error, *errors.RestErr) {
//sub := q.Get("sub")
s.mu.Lock()
utime := int32(time.Now().Unix())
// Open our jsonFile
jsonFile, errFile := getlist(param.Id)
// if we os.Open returns an error then handle it
if errFile != nil {
return nil, errFile
}
jsonFile, err := os.Open(dir + "/File.json")
// if we os.Open returns an error then handle it
if err != nil {
return nil, errors.NewNotFoundError("Bad File request")
}
// read our opened jsonFile as a byte array.
byteValue, _ := ioutil.ReadAll(jsonFile)
// we initialize our model
var errorFile errorlogs.Error_File
// we unmarshal our byteArray which contains our
// jsonFile's content into '' which we defined above
json.Unmarshal(byteValue, &errorFile)
// defer the closing of our jsonFile so that we can parse it later on
defer jsonFile.Close()
// An object to copy the required data from the response
var id int32
if len(errorFile.Error) == 0 {
id = 0
} else {
id = errorFile.Error[len(errorFile.Error)-1].ID
}
newValue := &errorlogs.Error{
ID: id + 1,
Utime: utime,
}
errorFile.Error = append(errorFile.Error, *newValue)
file, err := json.Marshal(errorFile)
if err != nil {
return nil, errors.NewInternalServerError("Unable to json marshal file")
}
err = ioutil.WriteFile(dir+"/File.json", file, 0644)
if err != nil {
return nil, errors.NewInternalServerError("Unable to write file")
}
s.mu.Unlock()
return newValue, nil
}
在这里,我从并发请求中锁定了这个函数,如果某个客户端已经在写入文件,它不会让另一个客户端同时写入文件。但是现在我很困惑,这个 mutex.Lock() 在被锁定时对所有其他请求做了什么?它让另一个客户等待吗?或者它只是忽略所有其他客户?我们有没有办法用某种响应发回客户端?还是让其他客户端等待,然后允许他们访问此功能?
回答
当互斥锁被锁定时,所有其他调用都Mutex.Lock()将阻塞,直到Mutex.Unlock()首先被调用。
因此,当您的处理程序正在运行(并持有互斥锁)时,所有其他请求都将在Lock()调用时被阻止。
注意:如果您的处理程序由于您提前返回(使用return语句)而没有正常完成,或者它发生恐慌,您的互斥锁将保持锁定状态,因此所有进一步的请求都将被阻塞。
一个好的做法是在defer锁定后立即使用解锁互斥锁:
s.mu.Lock()
defer s.mu.Unlock()
这确保Unlock()无论您的函数如何结束(可能正常结束,返回或恐慌)都会被调用。
尽量保持锁的时间尽可能短,以尽量减少其他请求的阻塞时间。虽然在您进入处理程序时锁定并仅在返回前解锁可能很方便,但如果您在处理程序的“生命周期”内不使用受保护的资源,则仅在使用共享资源时锁定和解锁。例如,如果您想保护对文件的并发访问,请锁定互斥锁,读/写文件,一旦完成,就解锁互斥锁。您如何处理读取的数据以及如何组合和发送响应不应阻止其他请求。当然,当使用defer解锁时,它可能不会像它应该的那样早运行(当您完成共享资源时)。所以在某些情况下可以不使用defer,或者访问共享资源的代码可能会移动到命名或未命名(匿名)函数,以便仍然能够使用defer.
sync.Mutex不支持“窥视”状态,也不支持“尝试锁定”操作。这意味着使用sync.Mutex您不能通知客户端它必须等待,因为处理请求正在等待另一个请求完成。如果你需要这样的功能,你可以使用channels。容量为 1 的缓冲通道可以实现此功能:“锁定”操作是在通道上发送一个值,“解锁”操作是从通道接收一个值。到现在为止还挺好。“try-lock”操作可能是一个“条件”发送操作:使用select带有defaultcase的语句,您可以检测到您现在无法锁定,因为它已经被锁定,并且您可以替代或同时做其他事情,并且稍后重试锁定。
下面是它的外观示例:
var lock = make(chan struct{}, 1)
func handler(w http.ResponseWriter, r *http.Request) {
// Try locking:
select {
case lock <- struct{}{}:
// Success: proceed
defer func() { <-lock }() // Unlock deferred
default:
// Another handler would block us, send back an "error"
http.Error(w, "Try again later", http.StatusTooManyRequests)
return
}
time.Sleep(time.Second * 2) // Simulate long computation
io.WriteString(w, "Done")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
如果另一个请求持有锁,上面的简单示例立即返回错误。你可以选择在这里做不同的事情:你可以把它放在一个循环中并重试几次,然后放弃并返回错误(在迭代之间休眠一点)。您可以在尝试锁定时使用超时,并且只有在一段时间内无法获得锁定时才接受“失败”(参见time.After()和context.WithTimeout())。当然,如果我们使用某种超时,则default必须删除default案例(如果没有其他案例可以立即进行,则立即选择案例)。
虽然我们正在处理它(超时),因为我们已经在使用select,我们可以合并监视请求的上下文是一个好处:如果它被取消,我们应该终止并提前返回。我们可以通过添加一个从上下文的完成通道接收的案例来做到这一点,比如case <-r.Context().Done():.
这是一个示例,如何简单地使用一个来完成超时和上下文监控select:
var lock = make(chan struct{}, 1)
func handler(w http.ResponseWriter, r *http.Request) {
// Wait 1 sec at most:
ctx, cancel := context.WithTimeout(r.Context(), time.Second)
defer cancel()
// Try locking:
select {
case lock <- struct{}{}:
// Success: proceed
defer func() { <-lock }() // Unlock deferred
case <-ctx.Done():
// Timeout or context cancelled
http.Error(w, "Try again later", http.StatusTooManyRequests)
return
}
time.Sleep(time.Second * 2) // Simulate long computation
io.WriteString(w, "Done")
}