Chạy một vòng lặp chính xác một lần mỗi giây


33

Tôi đang chạy vòng lặp này để kiểm tra và in một số thứ mỗi giây. Tuy nhiên, vì các tính toán có thể mất vài trăm mili giây, thời gian in đôi khi bỏ qua một giây.

Có cách nào để viết một vòng lặp như vậy mà tôi được đảm bảo để có được một bản in mỗi giây không? (Tất nhiên, với điều kiện là các tính toán trong vòng lặp mất ít hơn một giây :))

while true; do
  TIME=$(date +%H:%M:%S)
  # some calculations which take a few hundred milliseconds
  FOO=...
  BAR=...
  printf '%s  %s  %s\n' $TIME $FOO $BAR
  sleep 1
done


26
Lưu ý rằng " chính xác một lần mỗi giây" không thực sự có thể xảy ra trong hầu hết các trường hợp bởi vì bạn (thường) đang chạy trong không gian người dùng trên một hạt nhân đa nhiệm được ưu tiên sẽ lên lịch cho mã của bạn khi nó thấy phù hợp (để bạn không thể lấy lại quyền kiểm soát ngay sau khi ngủ kết thúc, ví dụ). Trừ khi bạn đang viết mã C gọi sched(7)API (POSIX: see <sched.h>và các trang được liên kết từ đó), về cơ bản bạn không thể có các đảm bảo theo thời gian thực của biểu mẫu này.
Kevin

Chỉ cần sao lưu những gì @Kevin đã nói, sử dụng chế độ ngủ () để thử và nhận bất kỳ loại thời gian chính xác nào sẽ bị thất bại, nó chỉ đảm bảo cho giấc ngủ tối thiểu 1 giây. Nếu bạn thực sự cần thời gian chính xác, bạn cần xem đồng hồ hệ thống (xem CLOCK_MONOTONIC) và kích hoạt mọi thứ dựa trên thời gian kể từ lần cuối cùng của sự kiện + 1 và đảm bảo bạn không tự vấp ngã bằng cách chạy> 1 giây để chạy, tính toán lần sau sau một số thao tác, v.v.
John U

sẽ rời khỏi đây tại đây falsehoodsabouttime.com
alo Malbarez

Chính xác một lần một giây = sử dụng VCXO. Một giải pháp chỉ dành cho phần mềm sẽ chỉ đưa bạn đến "đủ tốt", nhưng không chính xác.
Ian MacDonald

Câu trả lời:


65

Để gần hơn một chút với mã gốc, điều tôi làm là:

while true; do
  sleep 1 &
  ...your stuff here...
  wait # for sleep
done

Điều này thay đổi ngữ nghĩa một chút: nếu công cụ của bạn mất ít hơn một giây, nó sẽ chỉ chờ cho đến khi toàn bộ giây trôi qua. Tuy nhiên, nếu công cụ của bạn mất nhiều thời gian hơn một giây vì bất kỳ lý do gì, nó sẽ không tiếp tục sinh ra nhiều quy trình con hơn nữa mà không bao giờ có kết thúc.

Vì vậy, công cụ của bạn không bao giờ chạy song song và không ở chế độ nền, vì vậy các biến cũng hoạt động như mong đợi.

Lưu ý rằng nếu bạn cũng bắt đầu các tác vụ nền bổ sung, bạn sẽ phải thay đổi waithướng dẫn để chỉ chờ sleepquá trình cụ thể.

Nếu bạn cần nó chính xác hơn nữa, có lẽ bạn sẽ phải đồng bộ hóa nó với đồng hồ hệ thống và ngủ ms thay vì toàn bộ giây.


Làm thế nào để đồng bộ với đồng hồ hệ thống? Không có ý tưởng thực sự, nỗ lực ngu ngốc:

Mặc định:

while sleep 1
do
    date +%N
done

Đầu ra: 003511461 010510925 016081282 021643477 028504349 03 ... (tiếp tục phát triển)

Đã đồng bộ hóa:

 while sleep 0.$((1999999999 - 1$(date +%N)))
 do
     date +%N
 done

Đầu ra: 002648691 001098397 002514348 001293023 001679137 00 ... (giữ nguyên)


9
Bí quyết ngủ / chờ này thực sự rất thông minh!
philfr

Tôi đang tự hỏi nếu tất cả các triển khai sleepxử lý giây phân đoạn?
jcaron

1
@jcaron không phải tất cả. nhưng nó hoạt động cho giấc ngủ gnu và ngủ hộp bận rộn vì vậy nó không phải là kỳ lạ. Bạn có thể có thể thực hiện một dự phòng đơn giản như sleep 0.9 || sleep 1tham số không hợp lệ là lý do duy nhất khiến giấc ngủ không bao giờ thất bại.
frostschutz

@frostschutz Tôi mong muốn sleep 0.9được giải thích sleep 0bằng cách triển khai ngây thơ (cho rằng đó là những gì atoisẽ làm). Không chắc chắn nếu điều đó thực sự sẽ dẫn đến một lỗi.
jcaron

1
Tôi rất vui khi thấy câu hỏi này gây ra rất nhiều sự quan tâm. Đề nghị và câu trả lời của bạn là rất tốt. Nó không chỉ giữ trong vòng thứ hai, nó còn bám sát càng gần toàn bộ giây càng tốt. Ấn tượng! (! PS Trên một mặt lưu ý, người ta phải cài đặt GNU coreutils và sử dụng gdatetrên hệ điều hành MacOS để làm date +%Nviệc.)
forthrin

30

Nếu bạn có thể cấu trúc lại vòng lặp của mình thành tập lệnh / oneliner thì cách đơn giản nhất để thực hiện điều này là watchprecisetùy chọn của nó .

Bạn có thể thấy hiệu ứng với watch -n 1 sleep 0.5- nó sẽ hiển thị giây đếm ngược, nhưng đôi khi sẽ bỏ qua hơn một giây. Chạy nó như watch -n 1 -p sleep 0.5sẽ xuất ra hai lần mỗi giây, mỗi giây và bạn sẽ không thấy bất kỳ sự bỏ qua nào.


11

Chạy các hoạt động trong một lớp con chạy như một công việc nền sẽ khiến chúng không can thiệp quá nhiều vào sleep.

while true; do
  (
    TIME=$(date +%T)
    # some calculations which take a few hundred milliseconds
    FOO=...
    BAR=...
    printf '%s  %s  %s\n' "$TIME" "$FOO" "$BAR"
  ) &
  sleep 1
done

Lần duy nhất "bị đánh cắp" từ một giây sẽ là thời gian để khởi chạy subshell, vì vậy cuối cùng nó sẽ bỏ qua một giây, nhưng hy vọng ít thường xuyên hơn mã gốc.

Nếu mã trong lớp con kết thúc bằng cách sử dụng nhiều hơn một giây, vòng lặp sẽ bắt đầu tích lũy các công việc nền và cuối cùng hết tài nguyên.


9

Một cách khác (nếu bạn không thể sử dụng, ví dụ, watch -pnhư Maelstrom gợi ý) là sleepenh[ manpage ], được thiết kế cho việc này.

Thí dụ:

#!/bin/sh

t=$(sleepenh 0)
while true; do
        date +'sec=%s ns=%N'
        sleep 0.2
        t=$(sleepenh $t 1)
done

Lưu ý rằng sleep 0.2trong đó mô phỏng thực hiện một số nhiệm vụ tốn thời gian ăn khoảng 200ms. Mặc dù vậy, đầu ra nano giây vẫn ổn định (tốt, theo tiêu chuẩn hệ điều hành không theo thời gian thực) - nó xảy ra một lần mỗi giây:

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

Đó là dưới 1ms khác nhau, và không có xu hướng. Điều đó khá tốt; bạn nên mong đợi số lần thoát tối thiểu 10ms nếu có bất kỳ tải nào trên hệ thống - nhưng vẫn không bị trôi theo thời gian. Tức là bạn sẽ không mất một giây.


7

Với zsh:

n=0
typeset -F SECONDS=0
while true; do
  date '+%FT%T.%2N%z'
  ((++n > SECONDS)) && sleep $((n - SECONDS))
done

Nếu giấc ngủ của bạn không hỗ trợ điểm nổi giây, bạn có thể sử dụng zshzselectthay vì (sau một 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

Những thứ đó không nên trôi miễn là các lệnh trong vòng lặp mất ít hơn một giây để chạy.


0

Tôi đã có cùng một yêu cầu chính xác cho tập lệnh shell POSIX, trong đó tất cả các trình trợ giúp (ngủ, GNUs ngủ, ngủenh, ...) đều không có sẵn.

xem: https://stackoverflow.com/a/54494216

#!/bin/sh

get_up()
{
        read -r UP REST </proc/uptime
        export UP=${UP%.*}${UP#*.}
}

wait_till_1sec_is_full()
{
    while true; do
        get_up
        test $((UP-START)) -ge 100 && break
    done
}

while true; do
    get_up; START=$UP

    your_code

    wait_till_1sec_is_full
done
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.