Running a loop precisely once per second
Clash Royale CLAN TAG#URR8PPP
up vote
32
down vote
favorite
I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.
Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))
while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done
bash timestamps sleep
add a comment |Â
up vote
32
down vote
favorite
I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.
Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))
while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done
bash timestamps sleep
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
26
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into thesched(7)
API (POSIX: see<sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.
â Kevin
Aug 6 at 23:57
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42
add a comment |Â
up vote
32
down vote
favorite
up vote
32
down vote
favorite
I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.
Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))
while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done
bash timestamps sleep
I'm running this loop to check and print some things every second. However, because the calculations take maybe a few hundred milliseconds, the printed time sometimes skip a second.
Is there any way to write such a loop that I am guaranteed to get a printout every second? (Provided, of course, that the calculations in the loop take less than a second :))
while true; do
TIME=$(date +%H:%M:%S)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' $TIME $FOO $BAR
sleep 1
done
bash timestamps sleep
edited Aug 7 at 11:04
Jeff Schaller
31.1k846105
31.1k846105
asked Aug 6 at 14:57
forthrin
800821
800821
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
26
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into thesched(7)
API (POSIX: see<sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.
â Kevin
Aug 6 at 23:57
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42
add a comment |Â
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
26
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into thesched(7)
API (POSIX: see<sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.
â Kevin
Aug 6 at 23:57
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
26
26
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the
sched(7)
API (POSIX: see <sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.â Kevin
Aug 6 at 23:57
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the
sched(7)
API (POSIX: see <sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.â Kevin
Aug 6 at 23:57
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42
add a comment |Â
5 Answers
5
active
oldest
votes
up vote
62
down vote
accepted
To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations ofsleep
handle fractional seconds?
â jcaron
Aug 9 at 8:15
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback likesleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.
â frostschutz
Aug 9 at 8:34
@frostschutz I'd expectsleep 0.9
to be interpreted assleep 0
by naïve implementations (given that's whatatoi
would do). Not sure if that would actually result in an error.
â jcaron
Aug 9 at 10:18
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and usegdate
on macOS to makedate +%N
work.)
â forthrin
Aug 9 at 12:16
 |Â
show 1 more comment
up vote
30
down vote
If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch
and its precise
option.
You can see the effect with watch -n 1 sleep 0.5
- it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5
will output twice per second, every second, and you won't see any skips.
add a comment |Â
up vote
10
down vote
Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep
.
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.
If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.
add a comment |Â
up vote
8
down vote
Another alternative (if you can't use, e.g., watch -p
as Maelstrom suggests) is sleepenh
[manpage], which is designed for this.
Example:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
Note the sleep 0.2
in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) â it happens once per second:
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system â but still no drift over time. I.e., you won't lose a second.
add a comment |Â
up vote
7
down vote
With zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
If your sleep doesn't support floating point seconds, you can use zsh
's zselect
instead (after a zmodload zsh/zselect
):
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
Those should not drift as long as the commands in the loop take less than one second to run.
add a comment |Â
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
62
down vote
accepted
To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations ofsleep
handle fractional seconds?
â jcaron
Aug 9 at 8:15
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback likesleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.
â frostschutz
Aug 9 at 8:34
@frostschutz I'd expectsleep 0.9
to be interpreted assleep 0
by naïve implementations (given that's whatatoi
would do). Not sure if that would actually result in an error.
â jcaron
Aug 9 at 10:18
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and usegdate
on macOS to makedate +%N
work.)
â forthrin
Aug 9 at 12:16
 |Â
show 1 more comment
up vote
62
down vote
accepted
To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations ofsleep
handle fractional seconds?
â jcaron
Aug 9 at 8:15
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback likesleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.
â frostschutz
Aug 9 at 8:34
@frostschutz I'd expectsleep 0.9
to be interpreted assleep 0
by naïve implementations (given that's whatatoi
would do). Not sure if that would actually result in an error.
â jcaron
Aug 9 at 10:18
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and usegdate
on macOS to makedate +%N
work.)
â forthrin
Aug 9 at 12:16
 |Â
show 1 more comment
up vote
62
down vote
accepted
up vote
62
down vote
accepted
To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
To stay a bit closer to the original code, what I do is:
while true; do
sleep 1 &
...your stuff here...
wait # for sleep
done
This changes the semantics a little: if your stuff took less than a second, it will simply wait for the full second to pass. However, if your stuff takes longer than a second for any reason, it won't keep spawning even more subprocesses with never any end to it.
So your stuff never runs in parallel, and not in the background, so variables work as expected too.
Note that if you do start additional background tasks as well, you'll have to change the wait
instruction to only wait for the sleep
process specifically.
If you need it to be even more accurate, you'll probably just have to sync it to the system clock and sleep ms instead of full seconds.
How to sync to system clock? No idea really, stupid attempt:
Default:
while sleep 1
do
date +%N
done
Output: 003511461 010510925 016081282 021643477 028504349 03... (keeps growing)
Synced:
while sleep 0.$((1999999999 - 1$(date +%N)))
do
date +%N
done
Output: 002648691 001098397 002514348 001293023 001679137 00... (stays same)
edited Aug 6 at 15:34
answered Aug 6 at 15:20
frostschutz
24.1k14571
24.1k14571
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations ofsleep
handle fractional seconds?
â jcaron
Aug 9 at 8:15
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback likesleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.
â frostschutz
Aug 9 at 8:34
@frostschutz I'd expectsleep 0.9
to be interpreted assleep 0
by naïve implementations (given that's whatatoi
would do). Not sure if that would actually result in an error.
â jcaron
Aug 9 at 10:18
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and usegdate
on macOS to makedate +%N
work.)
â forthrin
Aug 9 at 12:16
 |Â
show 1 more comment
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations ofsleep
handle fractional seconds?
â jcaron
Aug 9 at 8:15
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback likesleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.
â frostschutz
Aug 9 at 8:34
@frostschutz I'd expectsleep 0.9
to be interpreted assleep 0
by naïve implementations (given that's whatatoi
would do). Not sure if that would actually result in an error.
â jcaron
Aug 9 at 10:18
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and usegdate
on macOS to makedate +%N
work.)
â forthrin
Aug 9 at 12:16
8
8
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
This sleep/wait trick is really clever !
â philfr
Aug 8 at 7:20
I'm wondering if all implementations of
sleep
handle fractional seconds?â jcaron
Aug 9 at 8:15
I'm wondering if all implementations of
sleep
handle fractional seconds?â jcaron
Aug 9 at 8:15
1
1
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like
sleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.â frostschutz
Aug 9 at 8:34
@jcaron not all of them. but it works for gnu sleep and busybox sleep so it's not exotic. You could probably do a simple fallback like
sleep 0.9 || sleep 1
as invalid parameter is pretty much the only reason for sleep to ever fail.â frostschutz
Aug 9 at 8:34
@frostschutz I'd expect
sleep 0.9
to be interpreted as sleep 0
by naïve implementations (given that's what atoi
would do). Not sure if that would actually result in an error.â jcaron
Aug 9 at 10:18
@frostschutz I'd expect
sleep 0.9
to be interpreted as sleep 0
by naïve implementations (given that's what atoi
would do). Not sure if that would actually result in an error.â jcaron
Aug 9 at 10:18
1
1
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use
gdate
on macOS to make date +%N
work.)â forthrin
Aug 9 at 12:16
I'm happy to see this question sparked a lot of interest. Your suggestion and answer are very good. Not only does it keep within the second, it also sticks as close to the whole second as possible. Impressive! (PS! On a side note, one must install GNU Coreutils and use
gdate
on macOS to make date +%N
work.)â forthrin
Aug 9 at 12:16
 |Â
show 1 more comment
up vote
30
down vote
If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch
and its precise
option.
You can see the effect with watch -n 1 sleep 0.5
- it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5
will output twice per second, every second, and you won't see any skips.
add a comment |Â
up vote
30
down vote
If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch
and its precise
option.
You can see the effect with watch -n 1 sleep 0.5
- it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5
will output twice per second, every second, and you won't see any skips.
add a comment |Â
up vote
30
down vote
up vote
30
down vote
If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch
and its precise
option.
You can see the effect with watch -n 1 sleep 0.5
- it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5
will output twice per second, every second, and you won't see any skips.
If you can restructure your loop into a script / oneliner then the simplest way to do this is with watch
and its precise
option.
You can see the effect with watch -n 1 sleep 0.5
- it will show seconds counting up, but will occasionally skip over a second. Running it as watch -n 1 -p sleep 0.5
will output twice per second, every second, and you won't see any skips.
answered Aug 7 at 0:10
Maelstrom
40113
40113
add a comment |Â
add a comment |Â
up vote
10
down vote
Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep
.
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.
If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.
add a comment |Â
up vote
10
down vote
Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep
.
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.
If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.
add a comment |Â
up vote
10
down vote
up vote
10
down vote
Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep
.
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.
If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.
Running the operations in a subshell that runs as a background job would make them not interfere so much with the sleep
.
while true; do
(
TIME=$(date +%T)
# some calculations which take a few hundred milliseconds
FOO=...
BAR=...
printf '%s %s %sn' "$TIME" "$FOO" "$BAR"
) &
sleep 1
done
The only time "stolen" from the one second would be the time taken to launch the subshell, so it would eventually skip a second, but hopefully less often than the original code.
If the code in the subshell ends up using more than a second, the loop would start to accumulate background jobs and eventually run out of resources.
edited Aug 6 at 15:11
answered Aug 6 at 15:02
Kusalananda
102k13199315
102k13199315
add a comment |Â
add a comment |Â
up vote
8
down vote
Another alternative (if you can't use, e.g., watch -p
as Maelstrom suggests) is sleepenh
[manpage], which is designed for this.
Example:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
Note the sleep 0.2
in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) â it happens once per second:
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system â but still no drift over time. I.e., you won't lose a second.
add a comment |Â
up vote
8
down vote
Another alternative (if you can't use, e.g., watch -p
as Maelstrom suggests) is sleepenh
[manpage], which is designed for this.
Example:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
Note the sleep 0.2
in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) â it happens once per second:
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system â but still no drift over time. I.e., you won't lose a second.
add a comment |Â
up vote
8
down vote
up vote
8
down vote
Another alternative (if you can't use, e.g., watch -p
as Maelstrom suggests) is sleepenh
[manpage], which is designed for this.
Example:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
Note the sleep 0.2
in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) â it happens once per second:
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system â but still no drift over time. I.e., you won't lose a second.
Another alternative (if you can't use, e.g., watch -p
as Maelstrom suggests) is sleepenh
[manpage], which is designed for this.
Example:
#!/bin/sh
t=$(sleepenh 0)
while true; do
date +'sec=%s ns=%N'
sleep 0.2
t=$(sleepenh $t 1)
done
Note the sleep 0.2
in there the simulate doing some time-consuming task eating around 200ms. Despite that, the nanoseconds output remain stable (well, by non-realtime OS standards) â it happens once per second:
sec=1533663406 ns=840039402
sec=1533663407 ns=840105387
sec=1533663408 ns=840380678
sec=1533663409 ns=840175397
sec=1533663410 ns=840132883
sec=1533663411 ns=840263150
sec=1533663412 ns=840246082
sec=1533663413 ns=840259567
sec=1533663414 ns=840066687
That's under 1ms different, and no trend. That's quite good; you should expect bounces of at least 10ms if there is any load on the system â but still no drift over time. I.e., you won't lose a second.
answered Aug 7 at 17:41
derobert
68.3k8147203
68.3k8147203
add a comment |Â
add a comment |Â
up vote
7
down vote
With zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
If your sleep doesn't support floating point seconds, you can use zsh
's zselect
instead (after a zmodload zsh/zselect
):
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
Those should not drift as long as the commands in the loop take less than one second to run.
add a comment |Â
up vote
7
down vote
With zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
If your sleep doesn't support floating point seconds, you can use zsh
's zselect
instead (after a zmodload zsh/zselect
):
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
Those should not drift as long as the commands in the loop take less than one second to run.
add a comment |Â
up vote
7
down vote
up vote
7
down vote
With zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
If your sleep doesn't support floating point seconds, you can use zsh
's zselect
instead (after a zmodload zsh/zselect
):
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
Those should not drift as long as the commands in the loop take less than one second to run.
With zsh
:
n=0
typeset -F SECONDS=0
while true; do
date '+%FT%T.%2N%z'
((++n > SECONDS)) && sleep $((n - SECONDS))
done
If your sleep doesn't support floating point seconds, you can use zsh
's zselect
instead (after a zmodload zsh/zselect
):
zmodload zsh/zselect
n=0
typeset -F SECONDS=0
while true; do
date '+%FZ%T.%2N%z'
((++n > SECONDS)) && zselect -t $((((n - SECONDS) * 100) | 0))
done
Those should not drift as long as the commands in the loop take less than one second to run.
edited Aug 7 at 12:02
answered Aug 7 at 6:42
Stéphane Chazelas
279k53513845
279k53513845
add a comment |Â
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f460836%2frunning-a-loop-precisely-once-per-second%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Possibly helpful: unix.stackexchange.com/q/60767/117549
â Jeff Schaller
Aug 6 at 15:18
26
Note that "precisely once per second" is not literally possible in most cases because you are (usually) running in userspace atop a preemptively multitasking kernel which will schedule your code as it sees fit (so that you might not regain control immediately after a sleep ends, for example). Unless you are writing C code which calls into the
sched(7)
API (POSIX: see<sched.h>
and pages linked from there), you basically cannot have real-time guarantees of this form.â Kevin
Aug 6 at 23:57
Just to back up what @Kevin said, using sleep() to try and get any sort of precise timing is doomed to failure, it only guarantees AT LEAST 1sec of sleep. If you really need precise timings you need to look at the system clock (see CLOCK_MONOTONIC) and trigger things based on time-since-last-event + 1s, and make sure you don't trip yourself up by taking >1sec to run, calculating the next time after some operation, etc.
â John U
Aug 7 at 12:07
just going to leave this here falsehoodsabouttime.com
â alo Malbarez
Aug 7 at 15:36
Precisely once a second = use a VCXO. A software-only solution will only get you to "good enough", but not precise.
â Ian MacDonald
Aug 8 at 14:42