csga5000
csga5000

Reputation: 4141

Using a loop to run my program in a new Thread - Don't use sleep?

I just have a program that used to do this in it's own thread:

public void run(){
    long lastTime = System.nanoTime();
    float lastSleep = 0;
    //Everything is in seconds.
    while(running){
        float delta = (System.nanoTime()-lastTime)/1000000000f;
        lastTime = System.nanoTime();
        manager.update(delta);
        panel.repaint();
        lastSleep = Math.max(maxTicSpeed-(delta-lastSleep),5/1000f);
        try{
            Thread.sleep((long) Math.round(lastSleep*1000));
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

Basically I had always been taught to sleep when looping like this, so I did, my program sleeps for at least 5 milliseconds, or the most time it can sleep without passing the limit( 1/30th of a second). But I was reading around, and sleep doesn't sound like it's very good.

http://msmvps.com/blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-designed-program.aspx

From what he says it sounds like my program wouldn't even sleep if it got too close to the lower sleeping limit, ect. When system.printing the change in time the change ranges approximately from .31508 - .03475, which is really good enough for me, as my program accounts for inaccuracy.

That being said, what can I do instead? I was considering adding something like this instead of the try{Sleep}:

long waitTill = (long) (System.nanoTime()+lastTime/1000000000f), 
    now = System.nanoTime();
while(now < waitTill){
    now = System.nanoTime();
}

But wouldn't my thread still be taking up the same amount of processor time? I thought the point was to stop our thread from taking up more of the processor than it actually needs..

So, should I use sleep(With a larger minimum sleep time?), should I use my alternative, should I use another alternative, or should I just let my program loop at an unrestrained speed? Am I programming poorly even if I account for sleep inaccuracy?

Thanks for the help!

EDIT: So, Timers have been recommened, however I understand that if my task didn't finish before the Timer called again then I would run into issues. That is a definate concern with my program. I feel like I have dealt with the problem of Thread.sleep() by using a delta, so, would Thread.sleep() as before be better?

Upvotes: 2

Views: 744

Answers (5)

mmirwaldt
mmirwaldt

Reputation: 883

As the question was raised how a timer behaves if the timer tasks take longer than the interval.

I programmed a silly sample program to watch how the the timer behaves:

public class FlawedTimerTask extends TimerTask {
    final int taskId;
    final long sleepTime;
    int counter = 0;

    public FlawedTimerTask(int taskId, long sleepTime) {
        super();
        this.taskId = taskId;
        this.sleepTime = sleepTime;
    }

    @Override
    public void run() {
        long beginTimeInNs = System.nanoTime();
        System.out.println("taskId=" + taskId + ", run=" + counter + ", beginning at " + (beginTimeInNs-beginOfExperiment));
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTimeInNs = System.nanoTime();
        System.out.println("taskId=" + taskId + ", run=" + counter + ", ending at " + (endTimeInNs-beginOfExperiment));
        counter++;
    }

    static long beginOfExperiment;

    public static void main(String[] args) {
        beginOfExperiment = System.nanoTime();
        Timer timer = new Timer();
        timer.schedule(new FlawedTimerTask(1, 800), 500, 500);
        timer.schedule(new FlawedTimerTask(2, 1000), 500, 2500);
    }
}

The output was:

taskId=1, run=0, beginning at 491988762
taskId=1, run=0, ending at 1291944877
taskId=2, run=0, beginning at 1292056514
taskId=2, run=0, ending at 2293928680
taskId=1, run=1, beginning at 2294036467
taskId=1, run=1, ending at 3094967160
taskId=1, run=2, beginning at 3095097404
taskId=1, run=2, ending at 3894902745
taskId=1, run=3, beginning at 3895045820
taskId=1, run=3, ending at 4695902088
taskId=2, run=1, beginning at 4696095849
taskId=2, run=1, ending at 5695887973
taskId=1, run=4, beginning at 5695991911
taskId=1, run=4, ending at 6496896941
taskId=1, run=5, beginning at 6497002803
taskId=1, run=5, ending at 7297814161
taskId=1, run=6, beginning at 7297998297
taskId=1, run=6, ending at 8098803239
taskId=2, run=2, beginning at 8098922575
taskId=2, run=2, ending at 9098814787
taskId=1, run=7, beginning at 9098971977
taskId=1, run=7, ending at 9899803866
taskId=1, run=8, beginning at 9899970038
taskId=1, run=8, ending at 10699807458
taskId=1, run=9, beginning at 10699912038
taskId=1, run=9, ending at 11500693882
taskId=2, run=3, beginning at 11500815143
taskId=2, run=3, ending at 12501656270
taskId=1, run=10, beginning at 12501781380
taskId=1, run=10, ending at 13302714640
taskId=1, run=11, beginning at 13302888511
taskId=1, run=11, ending at 14102727215
taskId=1, run=12, beginning at 14102929958
taskId=1, run=12, ending at 14903695762
taskId=2, run=4, beginning at 14903878616
taskId=2, run=4, ending at 15903607223
taskId=1, run=13, beginning at 15903775961
taskId=1, run=13, ending at 16705705613
taskId=1, run=14, beginning at 16705798644
taskId=1, run=14, ending at 17505650180
taskId=1, run=15, beginning at 17505881795
taskId=1, run=15, ending at 18306578307
taskId=2, run=5, beginning at 18306718815
taskId=2, run=5, ending at 19306666847
taskId=1, run=16, beginning at 19306757953
taskId=1, run=16, ending at 20107480129
taskId=1, run=17, beginning at 20107580217
taskId=1, run=17, ending at 20907534407
taskId=1, run=18, beginning at 20907640911
taskId=1, run=18, ending at 21709616117
taskId=2, run=6, beginning at 21709784855
taskId=2, run=6, ending at 22709563506
taskId=1, run=19, beginning at 22709664236
taskId=1, run=19, ending at 23510559642
taskId=1, run=20, beginning at 23510653956
taskId=1, run=20, ending at 24310465713
taskId=1, run=21, beginning at 24310572217
taskId=1, run=21, ending at 25111451583
taskId=2, run=7, beginning at 25111549105
taskId=2, run=7, ending at 26111453508
taskId=1, run=22, beginning at 26111544614
taskId=1, run=22, ending at 26913489022
taskId=1, run=23, beginning at 26913629531
taskId=1, run=23, ending at 27713421398
taskId=1, run=24, beginning at 27713577305
taskId=1, run=24, ending at 28514443839
taskId=2, run=8, beginning at 28514550985
taskId=2, run=8, ending at 29514349525
taskId=1, run=25, beginning at 29514496450
taskId=1, run=25, ending at 30315367475
taskId=1, run=26, beginning at 30315469488
taskId=1, run=26, ending at 31115349896
taskId=1, run=27, beginning at 31115475648
taskId=1, run=27, ending at 31917465609
taskId=2, run=9, beginning at 31917563773
taskId=2, run=9, ending at 32917368087
taskId=1, run=28, beginning at 32917524636
taskId=1, run=28, ending at 33718337276
taskId=1, run=29, beginning at 33718481634
taskId=1, run=29, ending at 34518366533
taskId=1, run=30, beginning at 34518459564
taskId=1, run=30, ending at 35319336363
taskId=2, run=10, beginning at 35319516009
taskId=2, run=10, ending at 36319338930
taskId=1, run=31, beginning at 36319440301
taskId=1, run=31, ending at 37121299378
taskId=1, run=32, beginning at 37121403957
taskId=1, run=32, ending at 37921223413
taskId=1, run=33, beginning at 37921324785
taskId=1, run=33, ending at 38722168863
taskId=2, run=11, beginning at 38722270877
taskId=2, run=11, ending at 39722259328

You can observe that

  • the timer runs in the intervalls of the timer tasks (800ms, 1000ms) and not in its interval 500ms
  • no interleaving between the timer tasks occur (there is a blocking call in the main loop in the implementation of the Timer which is single-threaded)

Upvotes: 0

pundit
pundit

Reputation: 312

Instead of sleep(), it would be better to use wait() and notify().

When using sleep() the thread goes out of the running queue and to run the thread again the OS have do some extra work which is a pure overhead.

But when the thread is waiting it does not goes out of the running queue and so no overhead.

Upvotes: 0

Alan
Alan

Reputation: 46873

In order to work around this problem, you will need to rethink your design. Essentially what you are doing is doing work on a regularly scheduled interval. The Run/While/Sleep pattern works, but as your research has uncovered isn't the optimal solution.

Modern languages have a "task-run" pattern which allows the programming environment/OS to better manage the execution of your tasks.

In Java there is java.util.timer along with java.util.timertask. With the task-run pattern, you create your task, and schedule it to run on a certain interval.

Timers also give you a cleaner way to stop your executing loop, by canceling the timer, instead of setting a boolean flag.

From the Comments:

There are a couple issues that one should be aware of. If your task is one where the task might run longer than the scheduled interval, it's possible to have another task run while the previous task is still executing. A few solutions:

  1. Use a job queue, to queue up work to be done. If no work is in the queue, the task returns.
  2. Another approach, common in javascript, is to schedule the task to execute one time, and at the conclusion of the task, re-schedule that task to execute a single-time.
  3. A less elegant approach, using a flag to indicate a specific task is being executed. This works, but requires you to properly manage the state of the flag, which is prone to error.

Another common problem is that scheduled timers are often implemented as best-effort. That is, the OS/Framework tries to run the task as scheduled, but offers no guarentee that a task will execute exactly on the interval specified. So if your task requires hard, deterministic scheduling, you will likely need a closer-to-os/hardware solution.

Upvotes: 5

csga5000
csga5000

Reputation: 4141

As Alan, and Pragmateek both said, Timers are typically preferred over using Thread.sleep(), however, as Martin James said "Sleep() can certainly be misused, and often is, but that does not make it intrinsically incorrect or some sort of anti-pattern".

My conclusion is that, since I use a delta, I handle Thread.sleep()'s inaccuracy. That being said there is no problem with using it. A timer would be a little more difficult to implement as my task would need to finish before the timer called again, which with my program would be extremely hard to guarantee.

So, in this application, and the way I used it, Thread.sleep() is a perfectly viable option.

Upvotes: 0

Pragmateek
Pragmateek

Reputation: 13374

You should absolutely avoid pure spinning (your second example) (except for some specific use-cases like synchronization primitives) because it consumes CPU for doing nothing more than waiting and can easily lead to dead-locks!

It's why you often use some delaying with sleep to consume less CPU and gives more chance to the other threads to work, reducing the risk of dead-locks.

So there is nothing wrong with your first loop as long as you know what you're doing.

But you should prefer components dedicated to these kind of use-cases: timers.

Upvotes: 1

Related Questions