3

I am using the following simple polling mechanism:

    func poll() {

        for {
            if a {
                device1()
                time.Sleep(time.Second * 10)
            } else {
                sensor1()
                time.Sleep(time.Second * 10)
            }
        }
    }

I need to poll the device1 only if "a" is true otherwise poll the sensor1. Now here "a" will be set to true by clicking the button on UI which will be a random act.

But because of time.Sleep, delay get introduced while checking the condition.

Is there any way to immediately stop time.Sleep when I get the value for "a"? what are the possible ways to implement such interrupts while polling in golang?

Jeet
  • 75
  • 1
  • 12
  • The first part of your question is too broad. There are many ways to interrupt polling or a loop. Please show your actual code, so a proper, appropriate answer can be provided. For the second question, I don't know what you mean by "instantaneous values". Again, please provide your code, and describe exactly what you want. – Jonathan Hall May 14 '18 at 08:57
  • You can try https://github.com/jwells131313/goethe which has timers that go off at regular intervals. You have a choice with the timers to be fixed or to restart the timer once the timer job completes – jwells131313 May 14 '18 at 19:20

1 Answers1

4

You cannot interrupt a time.Sleep().

Instead if you need to listen to other "events" while waiting for something, you should use channels and the select statements. A select is similar to a switch, but with the cases all referring to communication operations.

The good thing of select is that you may list multiple cases, all refering to comm. ops (e.g. channel receives or sends), and whichever operation can proceed will be chosen (choosing one randomly if multiple can proceed). If none can proceed, it will block (wait) until one of the comm. ops can proceed (unless there's a default).

A "timeout" event may be realized using time.After(), or a series of "continuous" timeout events (ticks or heartbeats) using time.Ticker.

You should change your button handler to send a value (the button state) on a channel, so receiving from this channel can be added to the select inside poll(), and processed "immediately" (without waiting for a timeout or the next tick event).

See this example:

func poll(buttonCh <-chan bool) {
    isDevice := false

    read := func() {
        if isDevice {
            device1()
        } else {
            sensor1()
        }
    }

    ticker := time.NewTicker(1 * time.Second)
    defer func() { ticker.Stop() }()

    for {
        select {
        case isDevice = <-buttonCh:
        case <-ticker.C:
            read()
        }
    }
}

func device1() { log.Println("reading device1") }
func sensor1() { log.Println("reading sensor1") }

Testing it:

buttonCh := make(chan bool)

// simulate a click after 2.5 seconds:
go func() {
    time.Sleep(2500 * time.Millisecond)
    buttonCh <- true
}()

go poll(buttonCh)
time.Sleep(5500 * time.Millisecond)

Output (try it on the Go Playground):

2009/11/10 23:00:01 reading sensor1
2009/11/10 23:00:02 reading sensor1
2009/11/10 23:00:03 reading device1
2009/11/10 23:00:04 reading device1
2009/11/10 23:00:05 reading device1

This solution makes it easy to add any number of event sources without changing anything. For example if you want to add a "shutdown" event and a "read-now" event to the above solution, this is how it would look like:

for {
    select {
    case isDevice = <-buttonCh:
    case <-ticker.C:
        read()
    case <-readNowCh:
        read()
    case <-shutdownCh:
        return
    }
}

Where shutdownCh is a channel with any element type, and to signal shutdown, you do that by closing it:

var shutdownCh = make(chan struct{})

// Somewhere in your app:
close(shutdownCh)

Closing the channel has the advantage that you may have any number of goroutines "monitoring" it, all will receive the shutdown (this is like broadcasting). Receive from a closed channel can proceed immediately, yielding the zero-value of the channel's element type.

To issue an "immediate read" action, you would send a value on readNowCh.

icza
  • 389,944
  • 63
  • 907
  • 827
  • This is exactly what I was looking for! Thanks a lot for the detailed answer. – Jeet May 14 '18 at 10:24
  • What will be the condition if I have more than one devices to poll each for 1 second or so. eg. device1 then sleep(s) then device2 then sleep(s)..like that. Do I need to create a channel for each device? – Jeet May 14 '18 at 13:10
  • @Logan You want to poll 2 devices, _alternating_? If so, just introduce a bool flag, indicating which to poll when the next tick arrives. And when it does, poll the device, and flip the flag. – icza May 14 '18 at 13:14
  • Not just 2 devices, I am talking about 10 devices. Polling would be device1, 2, 3....10 in an infinite for loop and I wanted 5 second delay(sleep) after each device poll. – Jeet May 15 '18 at 03:49
  • 1
    @Logan If you have `n` devices, you may have an index telling which device to poll when the next tick comes in. After polling that device, increment the index, and zero it if it goes above the index of the last device. Still no need to have distinct tickers for each device if you want to poll them one after the other. – icza May 15 '18 at 07:14
  • Got it, but I want some delay like 10 seconds in between each device poll so I have to sleep for 10 seconds and again this will block my condition. – Jeet May 17 '18 at 14:09
  • @Logan No, there is no sleep involved. Just like in the solution above, there is no sleep call. There is only a `Ticker` which delivers a value on its channel periodically. Same would be with 10 devices. What changes is the `read()` function. You could have your `n` devices listed in a slice, and an index (which to read). Once the device at that index is read, increment the index. – icza May 17 '18 at 14:12
  • Finally got it! – Jeet May 18 '18 at 06:21