Làm thế nào để chạy một lệnh với tốc độ trung bình 5 lần mỗi giây?


21

Tôi có một tập lệnh dòng lệnh thực hiện lệnh gọi API và cập nhật cơ sở dữ liệu với kết quả.

Tôi có giới hạn 5 cuộc gọi API mỗi giây với nhà cung cấp API. Kịch bản mất hơn 0,2 giây để thực thi.

  • Nếu tôi chạy lệnh tuần tự, nó sẽ không chạy đủ nhanh và tôi sẽ chỉ thực hiện 1 hoặc 2 lệnh gọi API mỗi giây.
  • Nếu tôi chạy lệnh tuần tự, nhưng đồng thời từ một số thiết bị đầu cuối, tôi có thể vượt quá giới hạn 5 cuộc gọi / giây.

Nếu có cách sắp xếp các luồng để tập lệnh dòng lệnh của tôi được thực thi gần như chính xác 5 lần mỗi giây?

Ví dụ, một cái gì đó sẽ chạy với 5 hoặc 10 luồng và không có luồng nào sẽ thực thi tập lệnh nếu một luồng trước đó đã thực thi nó dưới 200ms trước.


Tất cả các câu trả lời phụ thuộc vào giả định rằng tập lệnh của bạn sẽ hoàn thành theo thứ tự nó được gọi. Có thể chấp nhận cho trường hợp sử dụng của bạn nếu chúng kết thúc không theo thứ tự?
Cody Gustafson

@CodyGustafson Hoàn toàn chấp nhận được nếu họ hoàn thành không theo thứ tự. Tôi không tin rằng có một giả định như vậy trong câu trả lời được chấp nhận, ít nhất?
Benjamin

Điều gì xảy ra nếu bạn vượt quá số lượng cuộc gọi mỗi giây? Nếu nhà cung cấp API điều chỉnh, bạn không cần bất kỳ cơ chế nào ở cuối ... phải không?
Floris

@Floris Họ sẽ trả về một thông báo lỗi sẽ dịch ngoại lệ trong SDK. Trước hết tôi nghi ngờ nhà cung cấp API sẽ rất vui nếu tôi tạo ra 50 thông điệp điều tiết mỗi giây (bạn phải hành động theo những thông báo tương ứng) và thứ hai là tôi đang sử dụng API cho các mục đích khác cùng một lúc, vì vậy tôi không muốn đạt đến giới hạn thực sự cao hơn một chút.
Benjamin

Câu trả lời:


25

Trên hệ thống GNU và nếu bạn có pv, bạn có thể làm:

cmd='
   that command | to execute &&
     as shell code'

yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh

Việc -P20thực hiện tối đa 20 $cmdcùng một lúc.

-L10 giới hạn tốc độ xuống còn 10 byte mỗi giây, vì vậy 5 dòng mỗi giây.

Nếu $cmds của bạn trở thành hai chậm và khiến giới hạn 20 đạt được, thì nó xargssẽ ngừng đọc cho đến khi một $cmdtrường hợp ít nhất trở lại. pvvẫn sẽ tiếp tục ghi vào đường ống với cùng tốc độ, cho đến khi đường ống đầy (mà trên Linux với kích thước đường ống mặc định là 64KiB sẽ mất gần 2 giờ).

Tại thời điểm đó, pvsẽ ngừng viết. Nhưng ngay cả khi đó, khi xargstiếp tục đọc,pv sẽ cố gắng bắt kịp và gửi tất cả các dòng mà nó nên gửi sớm nhất có thể để duy trì tổng thể trung bình 5 dòng trên giây.

Điều đó có nghĩa là miễn là có thể với 20 quy trình để đáp ứng 5 lần chạy mỗi giây theo yêu cầu trung bình, nó sẽ thực hiện được. Tuy nhiên, khi đạt đến giới hạn, tốc độ bắt đầu các quá trình mới sẽ không bị điều khiển bởi bộ đếm thời gian của pv mà bởi tốc độ mà các trường hợp cmd trước đó quay trở lại. Chẳng hạn, nếu 20 hiện đang chạy và đã được 10 giây và 10 trong số chúng quyết định hoàn thành tất cả cùng một lúc, thì 10 cái mới sẽ được bắt đầu cùng một lúc.

Thí dụ:

$ cmd='date +%T.%N; exec sleep 2'
$ yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh
09:49:23.347013486
09:49:23.527446830
09:49:23.707591664
09:49:23.888182485
09:49:24.068257018
09:49:24.338570865
09:49:24.518963491
09:49:24.699206647
09:49:24.879722328
09:49:25.149988152
09:49:25.330095169

Trung bình, nó sẽ là 5 lần mỗi giây ngay cả khi độ trễ giữa hai lần chạy không phải lúc nào cũng chính xác là 0,2 giây.

Với ksh93(hoặc với zshnếu sleeplệnh của bạn hỗ trợ giây phân đoạn):

typeset -F SECONDS=0
n=0; while true; do
  your-command &
  sleep "$((++n * 0.2 - SECONDS))"
done

Điều đó không đặt ràng buộc vào số lượng your-commands đồng thời mặc dù.


Sau một chút thử nghiệm, pvlệnh dường như chính xác là những gì tôi đang tìm kiếm, không thể hy vọng tốt hơn! Chỉ trên dòng này : yes | pv -qL10 | xargs -n1 -P20 sh -c "$cmd" sh, không phải là shdự phòng cuối cùng ?
Benjamin

1
@Benjamin Thứ hai đó shlà cho kịch bản $0của bạn $cmd. Nó cũng được sử dụng trong các thông báo lỗi bởi trình bao. Nếu không có nó, $0sẽ là ytừ yes, vì vậy bạn sẽ nhận được thông báo lỗi như y: cannot execute cmd... Bạn cũng có thể làmyes sh | pv -qL15 | xargs -n1 -P20 sh -c "$cmd"
Stéphane Chazelas

Tôi đang vật lộn để phân rã toàn bộ mọi thứ thành những mảnh dễ hiểu, TBH! Trong ví dụ của bạn, bạn đã loại bỏ cái cuối cùng này sh; và trong các thử nghiệm của tôi, khi tôi loại bỏ nó, tôi có thể thấy không có sự khác biệt!
Benjamin

@Benjamin. Nó không quan trọng. Nó sẽ chỉ làm cho khác đi nếu bạn $cmdsử dụng $0(tại sao nó?) Và cho các thông báo lỗi. Hãy thử ví dụ với cmd=/; không có lần thứ hai sh, bạn sẽ thấy một cái gì đó giống như y: 1: y: /: Permission deniedthay vìsh: 1: sh: /: Permission denied
Stéphane Chazelas

Tôi đang gặp vấn đề với giải pháp của bạn: nó hoạt động tốt trong vài giờ, sau đó đến một lúc nào đó nó chỉ thoát, không có bất kỳ lỗi nào. Điều này có thể liên quan đến đường ống đầy, có một số tác dụng phụ không mong muốn?
Benjamin

4

Đơn giản, nếu lệnh của bạn kéo dài dưới 1 giây, bạn có thể chỉ cần bắt đầu 5 lệnh mỗi giây. Rõ ràng, điều này là rất bùng nổ.

while sleep 1
do    for i in {1..5}
      do mycmd &
      done
done

Nếu lệnh của bạn có thể mất hơn 1 giây và bạn muốn trải ra các lệnh bạn có thể thử

while :
do    for i in {0..4}
      do  sleep .$((i*2))
          mycmd &
      done
      sleep 1 &
      wait
done

Ngoài ra, bạn có thể có 5 vòng riêng biệt chạy độc lập, tối thiểu 1 giây.

for i in {1..5}
do    while :
      do   sleep 1 &
           mycmd &
           wait
      done &
      sleep .2
done

Giải pháp khá tốt là tốt. Tôi thích thực tế là nó đơn giản và chính xác là 5 lần mỗi giây, nhưng nó có nhược điểm là bắt đầu 5 lệnh cùng lúc (thay vì cứ sau 200ms) và có thể thiếu sự bảo vệ của việc có hầu hết n luồng chạy cùng lúc !
Benjamin

@Benjamin Tôi đã thêm một giấc ngủ 200ms trong vòng lặp của phiên bản thứ hai. Phiên bản thứ hai này không thể có nhiều hơn 5 cmds chạy cùng một lúc vì chúng tôi chỉ bắt đầu 5 lần, sau đó chờ tất cả.
meuh

Vấn đề là, bạn không thể có nhiều hơn 5 mỗi giây bắt đầu; nếu tất cả các tập lệnh đột nhiên mất hơn 1 giây để thực thi, thì bạn sẽ không đạt đến giới hạn API. Ngoài ra, nếu bạn chờ đợi tất cả, một tập lệnh chặn duy nhất sẽ chặn tất cả các tập lệnh khác?
Benjamin

@Benjamin Vì vậy, bạn có thể chạy 5 vòng độc lập, mỗi vòng có thời gian ngủ tối thiểu là 1 giây, xem phiên bản thứ 3.
meuh

2

Với chương trình C,

Ví dụ, bạn có thể sử dụng một chuỗi ngủ trong 0,2 giây

#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

pthread_t tid;

void* doSomeThing() {
    While(1){
         //execute my command
         sleep(0.2)
     } 
}

int main(void)
{
    int i = 0;
    int err;


    err = pthread_create(&(tid), NULL, &doSomeThing, NULL);
    if (err != 0)
        printf("\ncan't create thread :[%s]", strerror(err));
    else
        printf("\n Thread created successfully\n");



    return 0;
}

sử dụng nó để biết cách tạo một chủ đề: tạo một chủ đề (đây là liên kết tôi đã sử dụng để dán mã này)


Cảm ơn câu trả lời của bạn, mặc dù tôi lý tưởng tìm kiếm thứ gì đó không liên quan đến lập trình C, mà chỉ sử dụng các công cụ Unix hiện có!
Benjamin

Vâng, câu trả lời stackoverflow might này ví dụ được sử dụng một cái xô thẻ chia sẻ giữa nhiều đề người lao động, nhưng yêu cầu trên Unix.SE gợi ý nhiều hơn một "Power user" chứ không phải là "lập trình viên" cách tiếp cận đang bị truy nã :-) Tuy nhiên, cclà một công cụ Unix hiện có và đây không phải là rất nhiều mã!
Steve Jessop

1

Sử dụng node.js, bạn có thể bắt đầu một chuỗi thực thi tập lệnh bash cứ sau 200 mili giây cho dù phản hồi mất bao lâu để quay lại vì phản hồi thông qua chức năng gọi lại .

var util = require('util')
exec = require('child_process').exec

setInterval(function(){
        child  = exec('fullpath to bash script',
                function (error, stdout, stderr) {
                console.log('stdout: ' + stdout);
                console.log('stderr: ' + stderr);
                if (error !== null) {
                        console.log('exec error: ' + error);
                }
        });
},200);

Javascript này chạy cứ sau 200 mili giây và phản hồi nhận được thông qua chức năng gọi lại function (error, stdout, stderr).

Bằng cách này, bạn có thể kiểm soát rằng nó không bao giờ vượt quá 5 cuộc gọi mỗi giây một cách độc lập về việc thực thi lệnh chậm hay nhanh như thế nào hoặc phải chờ bao lâu để nhận được phản hồi.


Tôi thích giải pháp này: nó bắt đầu chính xác 5 lệnh mỗi giây, đều đặn. Hạn chế duy nhất tôi có thể thấy là nó thiếu một biện pháp bảo vệ để có tối đa n quá trình chạy cùng một lúc! Nếu đây là một cái gì đó bạn có thể bao gồm dễ dàng? Tôi không quen thuộc với node.js.
Benjamin

0

Tôi đã sử dụng pvgiải pháp dựa trên Stéphane Chazelas một thời gian, nhưng phát hiện ra rằng nó đã thoát ngẫu nhiên (và âm thầm) sau một thời gian, bất cứ nơi nào từ vài phút đến vài giờ. - Chỉnh sửa: Lý do là tập lệnh PHP của tôi thỉnh thoảng bị chết vì vượt quá thời gian thực hiện tối đa, thoát với trạng thái 255.

Vì vậy, tôi quyết định viết một công cụ dòng lệnh đơn giản thực hiện chính xác những gì tôi cần.

Đạt được mục tiêu ban đầu của tôi đơn giản như:

./parallel.phar 5 20 ./my-command-line-script

Nó bắt đầu gần như chính xác 5 lệnh mỗi giây, trừ khi đã có 20 quy trình đồng thời, trong trường hợp đó, nó bỏ qua (các) lần thực hiện tiếp theo cho đến khi có sẵn một vị trí.

Công cụ này không nhạy cảm với lối thoát 255.

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.