如果在 Golang 中使用 mutex.lock() 锁定函数,如何发回响应?

How to send a response back if a function is locked using mutex.lock() in Golang?

我有这个功能

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() will block until Mutex.Unlock() 的调用将首先被调用。

因此,当您的处理程序 运行ning(并持有互斥体)时,所有其他请求将在 Lock() 调用时被阻止。

注意: 如果您的处理程序由于您 return 过早(使用 return 语句)而无法正常完成,或者它出现恐慌,您的互斥量将保持锁定状态,因此所有进一步的请求都将被阻止。

一个好的做法是使用 defer 解锁互斥体,在它被锁定后立即解锁:

s.mu.Lock()
defer s.mu.Unlock()

这确保无论您的函数如何结束(可能正常结束,return 或 panic)都会调用 Unlock()

尽量少持有锁,以尽量减少其他请求的阻塞时间。虽然在进入处理程序时锁定权利并仅在 return 之前解锁可能很方便,但如果您在处理程序的“生命周期”内不使用受保护资源,则仅在使用共享资源时锁定和解锁资源。例如,如果你想保护对文件的并发访问,锁定互斥锁,读/写文件,并在完成后立即解锁互斥锁。您对读取数据的处理方式以及您 assemble 和发送响应的方式不应阻止其他请求。当然,当使用 defer 解锁时,它可能不会像应该的那样早 运行 (当您完成共享资源时)。所以在某些情况下,不使用 defer 可能是可以的,或者访问共享资源的代码可能会被移动到命名或未命名(匿名)函数中,以便仍然能够使用 defer.

sync.Mutex does not support "peeking" the status, nor "try-lock" operation. This means using sync.Mutex you cannot signal the client that it has to wait because processing the request is waiting another request to complete. If you'd need such functionality, you could use channels. A buffered channel with a capacity of 1 could fulfil this functionality: the "lock" operation is sending a value on the channel, the "unlock" operation is receiving a value from the channel. So far so good. The "try-lock" operation could be a "conditional" send operation: using a select 语句和 default 情况下,您可以检测到您现在无法锁定,因为它已经被锁定,您可以改为或同时执行其他操作,并稍后重试锁定.

这是一个示例:

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

上面的简单示例 return 如果另一个请求持有锁,则立即出错。你可以选择在这里做不同的事情:你可以把它放在一个循环中并在放弃之前重试几次并 returning 一个错误(在迭代之间睡一会儿)。您可以在尝试锁定时使用超时,并且仅在一段时间内无法获得锁定时才接受“失败”(请参阅​​ time.After() and context.WithTimeout())。当然,如果我们使用某种超时,则必须删除 default 案例(如果 none 其他案例可以立即进行,则立即选择 default 案例)。

当我们在它(超时)时,因为我们已经在使用 select,这是一个额外的好处,我们可以合并监控请求的上下文:如果它被取消,我们应该终止并 return早。我们可以通过添加一个从上下文的完成通道接收的案例来做到这一点,比如 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")
}