如何与 time.After 进行惯用同步?

How to do idiomatic synchronization with time.After?

我正在编写一个对传入请求进行排队的应用程序。如果一个请求在队列中的时间超过了一定的时间,我想抛出一个超时。我正在使用 time.After:

timeoutCh := time.After(5 * time.Second)
select {
    case <-timeoutCh:
         //throw timeout 504
    case <-processing:
         //process request
}

处理通道(连同请求)被放入队列,当一个请求被取出来进行处理时,我向通道发送一个信号以命中 case 语句:

processing <- true

这样做的问题是,如果已经选择了timeoutCh,处理通道会阻塞,所以我需要一些方法来检查请求是否超时。

我考虑过使用共享原子布尔值,但如果我这样做:

case <-timeoutCh:
     requestTimedOut = true

然后在发送到处理通道之前检查布尔值,仍然存在竞争条件,因为可能已选择 timeoutCh 情况,但布尔值尚未设置为真!

在 Go 中有处理这种同步问题的惯用方法吗?

使用数据和超时的互斥坐标处理。

定义一个类型来保存互斥锁、输入、结果、一个表示工作完成的通道和一个指示工作(如果有)已完成的标志。

type work struct {
    sync.Mutex
    input    InputType
    result   ResultType
    signal   chan struct {}
    done     bool
}

请求处理程序创建工作项并将其排入队列,并等待超时或来自队列处理器的信号。无论哪种方式,请求处理程序都会检查队列处理器是否完成了工作并做出适当的响应。

func handler(resp http.ResponseWriter, req *http.Request) {
    w := &queueElement{
        input: computeInputFromRequest(req)
        signal:  make(chan struct{})
    }
    enqueue(w)

    // Wait for timeout or for queue processor to signal that the work is complete.
    select {
    case <-time.After(5 * time.Second):
    case <-w.signal:
    }

    w.Lock()
    done := w.done  // Record state of the work item.
    w.done = true   // Mark the work item as complete.
    w.Unlock()

    if !done {
        http.Error(w, "Timeout", http.StatusGatewayTimeout)
    }  else {
        respondWithResult(resp, w.result)
    }
}

队列处理器看起来像这样:

 for {
   w := dequeue()
   w.Lock()
   if !w.done {
      w.done = true
      w.result = computeResultFromInput(w.input)
      close(w.signal)
   }
   w.Unlock()
}

为确保请求处理程序等待结果,队列处理器在处理工作项时持有锁。