Using channel or sync.Cond to wait for condition

2020-06-25 04:35发布

I am trying to wait for a specific condition, and I would like advice as to how this is done best. I have a struct that looks like this (simplified):

type view struct {
    timeFrameReached bool
    Rows []*sitRow
}

In a goroutine, I am updating a file, which is read into the view variable. The number of rows increases, and timeFrameReached will ultimately be true.

Elsewhere, I want to wait for the following condition to be true:

view.timeFrameReached == true || len(view.Rows) >= numRows

I am trying to learn channels and how Go's condition variables work, and I would like to know what is the best solution here. Theoretically, I could do something trivial like this:

for {
    view = getView()
    if view.timeFrameReached == true || len(view.Rows) >= numRows {
        break
    }
}

but that is obviously a naive solution. The value of numRows comes from an HTTP request, so the condition method seems challenging. The goroutine would not know when to broadcast the condition because it wouldn't know the number of rows it is looking for.

2条回答
地球回转人心会变
2楼-- · 2020-06-25 04:50

I think I would do this with a condition variable. The concept is not that a Signal should only be done when the condition the waiter wants to check is true, but when it might be true (i.e., the things being checked have changed).

The normal pattern for doing this is:

mutex.Lock()
for {
    view = getView()
    if view.timeFrameReached == true || len(view.Rows) >= numRows {
        break
    }
    cond.Wait(&mutex)
}
// Do stuff with view
mutex.Unlock()

Then, in the code where view is changed:

mutex.Lock()
// Change view
cond.Signal() // or cond.Broadcast()
mutex.Unlock()

Obviously, I've written this without knowing how your program works, so you may have to make some changes.

You could do something similar with a channel by sending on the channel to signal and trying to receive from the channel to wait, but that seems more complicated to me. (Also, if you have more than one goroutine waiting, you can signal all of them to wake up using cond.Broadcast.)

查看更多
家丑人穷心不美
3楼-- · 2020-06-25 05:04

One idea I have involves communicating the needed number of rows via a channel, and the goroutine that is building the view will do a non-blocking receive to see if the main thread is requesting a certain number of rows. If so, it will send a message back to indicate that the condition is met.

Here the main function requests a number of rows:

if numRows > len(viewFile.View.Rows) && !viewFile.View.TimeFrameReached {
    // Send the required number of rows
    rows <- numRows
    // Wait for the prefetch loop to signal that the view file is ready
    <-rows // Discard the response value and move on
    view = getView()
}

Here the goroutine checks if a certain number of rows are required. If so, it responds with an affirmative signal when ready. The value of that signal is inconsequential.

select {
    case numRows := <-rows:
        if len(viewFile.View.Rows) >= numRows || viewFile.View.TimeFrameReached {
            rows <- 1
        }
    default:
}
查看更多
登录 后发表回答