Waiting for synchronization. End with a timeout

Is there some simple way to make the Java equivalent

wait(long timeMillis)

      

which waits on the monitor (mutex + cond, roughly) for a certain amount of time and returns if not signaled?

I can't find anything in the docs or googling on this, and while it is certainly possible to play some games with WaitGroup creation and have a pop timer goroutine, it seems tedious / annoying / inefficient to just get this simple functionality (which by the way, directly supported by any core threading library I've ever come across)

Edit: Yes, we've all read http://www.golang-book.com/10/index.htm as well as https://blog.golang.org/pipelines - again, making more threads is "bad "An (ineffective) solution for this, and channels are not a good fit for that either." Imagine using the typical parallel server Join () method to use ... (Please don't tell me to invert the control instead and use the Listener pattern. You don't always have the ability to change the API you are working with ...)

+4


source to share


5 answers


You can implement a conditional variable that only supports broadcast (no signal) with a channel. Here's a quick rundown of it: https://gist.github.com/zviadm/c234426882bfc8acba88f3503edaaa36#file-cond2-go

You can also just use this technique to replace the pipe and close the old one in your code. The code in the Gist uses unsafe.Pointer and atomic operations to call 'Broadcast' without getting the main sync.Locker. However, in your own code, more often than not, you still have to broadcast from within the capture lock, so you don't need to do anything unsafe / atomic.



While this method works, you can checkout as well: https://godoc.org/golang.org/x/sync/semaphore . If you create a weighted semaphore with a limit of 1, this will also give you all the abilities you need, and rightfully so.

+5


source


Not. There is no easy way to do this, and based on this thread, they are not going to add it. (although perhaps a discussion with them might lead you to something)

But there is always the hard way. 2 options:

  • Create your own Cond

    that has this capability. (see https://golang.org/src/sync/cond.go )
  • Use OS layer capabilities through a system call. (perhaps futex ?)


The challenge here - and the reason it isn't trivial - is that gorutas are not threads. Go has its own custom scheduler. Making your own Cond

will involve handling parts of the runtime that are not really meant to be processed. (but as I said, it is possible)

Sorry if this is a limitation. Most of the walking is pretty straightforward - you can often jump down to the bottom layer without too much trouble. But the scheduler is different. This is magic.

Magic works for most things, and they've added material in sync

to cover some of the known cases where it doesn't. If you think you've found someone else, perhaps you can convince them to add it. (but it's not just a matter of reproducing an API from another programming language or exposing the underlying API)

0


source


I sketched out a couple of possible alternatives in my talk at GopherCon this year (see https://drive.google.com/file/d/1nPdvhB0PutEJzdCq5ms6UI58dp50fcAN/view ). The "Conditional Variables" section starts on slide 37, but this particular template is described in more detail in the fallback slides (101-105).

As zviadm notes, one of the options ( https://play.golang.org/p/tWVvXOs87HX ) is to close the channel.

Another option ( https://play.golang.org/p/uRwV_i0v13T ) is for each waiter to allocate 1 buffered channel and for the broadcaster to send the token to the broadcast buffer.

If the event is a persistent condition such as "queue empty", the third option ( https://play.golang.org/p/uvx8vFSQ2f0 ) is to use a 1-buffered channel and each receiver must fill the buffer. as long as the state persists.

0


source


https://gitlab.com/jonas.jasas/condchan allows wait timeout. Please see an example:

package main

import (
    "fmt"
    "sync"
    "time"
    "gitlab.com/jonas.jasas/condchan"
)

func main() {
    cc := condchan.New(&sync.Mutex{})
    timeoutChan := time.After(time.Second)

    cc.L.Lock()
    // Passing func that gets channel c that signals when
    // Signal or Broadcast is called on CondChan
    cc.Select(func(c <-chan struct{}) { // Waiting with select
        select {
        case <-c: // Never ending wait
        case <-timeoutChan:
            fmt.Println("Hooray! Just escaped from eternal wait.")
        }
    })
    cc.L.Unlock()
}

      

0


source


I ran into the same problem and it turned out that using a pipe was quite easy to solve.

  • Signal is a transmission over a channel
  • Waiting is just waiting for a message on the channel.
  • Wait with a timeout is just a timer and message selection.
  • Broadcast is a loop that sends messages until no one is listening.

As with any condition variable, it must hold the mutex when you are waiting, and it is highly recommended to hold it when you signal.

I wrote in an implementation that follows the Cond protocol and adds a WaitOrTimeout. It returns true if successful, false if timed out.

Here's my code along with some test cases! DISCLAIMER: It seems to be working fine, but has not been thoroughly tested. Moreover, fairness is not guaranteed. Thread waits are reported in the order the scheduler sees fit and not necessarily first-in / first-out.

https://play.golang.org/p/K1veAOGbWZ

package main

import (
    "sync"
    "time"
    "fmt"
)

type TMOCond struct {
    L    sync.Locker
    ch      chan bool
}

func NewTMOCond(l sync.Locker) *TMOCond {
    return &TMOCond{ ch: make(chan bool), L: l }
}

func (t *TMOCond) Wait() {
    t.L.Unlock()
    <-t.ch
    t.L.Lock()
}

func (t *TMOCond) WaitOrTimeout(d time.Duration) bool {
    tmo := time.NewTimer(d)
    t.L.Unlock()
    var r bool
    select {
    case <-tmo.C:
    r = false
    case <-t.ch:
        r = true
    }
    if !tmo.Stop() {
        select {
        case <- tmo.C:
        default:
        }
    }
    t.L.Lock()
    return r
}

func (t *TMOCond) Signal() {
    t.signal()
}

func (t *TMOCond) Broadcast() {
    for {
        // Stop when we run out of waiters
        //
        if !t.signal() {
            return
        }
    }
}

func (t *TMOCond) signal() bool {
    select {
    case t.ch <- true:
        return true
    default:
        return false
    }
}

// **** TEST CASES ****
func lockAndSignal(t *TMOCond) {
    t.L.Lock()
    t.Signal()
    t.L.Unlock()
}

func waitAndPrint(t *TMOCond, i int) {
    t.L.Lock()
    fmt.Println("Goroutine", i, "waiting...")
    ok := t.WaitOrTimeout(10 * time.Second)
    t.L.Unlock()
    fmt.Println("This is goroutine", i, "ok:", ok)
}

func main() {
    var m sync.Mutex
    t := NewTMOCond(&m)

    // Simple wait
    //
    t.L.Lock()
    go lockAndSignal(t)
    t.Wait()
    t.L.Unlock()
    fmt.Println("Simple wait finished.")

    // Wait that times out
    //
    t.L.Lock()
    ok := t.WaitOrTimeout(100 * time.Millisecond)
    t.L.Unlock()
    fmt.Println("Timeout wait finished. Timeout:", !ok)


    // Broadcast. All threads should finish.
    //
    for i := 0; i < 10; i++ {
        go waitAndPrint(t, i)
    }
    time.Sleep(1 * time.Second) 
    t.L.Lock()
    fmt.Println("About to signal")
    t.Broadcast()
    t.L.Unlock()
    time.Sleep(10 * time.Second)
}

      

-2


source







All Articles