この記事では、外部コマンドの sleep を呼び出すことなく、bash スクリプトを秒単位でスリープさせる方法を示します。本日、ふと思いつきました。
bash のビルトインの read には、入力待ちをタイムアウトさせるための -t オプションが備わっています。これを使えば sleep の代用にできるはずです。ただし、絶対に入力が発生しない何かを read して、かつ、ブロックさせなくてはいけません。もし、C であれば、pipe() システムコールを呼んで、作成されたパイプを read すれば良いと思いますが、bash の構文で pipe() 相当をやる方法はありません。そこで、mkfifo で名前つきパイプ (FIFO) を作成することにしました。
次が実験に使ったスクリプトです。
#!/bin/bash # # Name: test_my_builtin_sleep.bash # setup_my_sleep() { local sleep_fifo=/tmp/.sleep_fifo.$$.$RANDOM$RANDOM mkfifo $sleep_fifo exec 9<>$sleep_fifo rm -f $sleep_fifo } setup_my_sleep my_sleep() { read -t $1 0<&9 } time { for ((i=0;i<20;i++)) do sleep 1 read < /proc/uptime echo $REPLY done } echo time { for ((i=0;i<20;i++)) do my_sleep 1 read < /proc/uptime echo $REPLY done }exec 9<>$sleep_fifo という書き方で、fd 9 を使って FIFO を rw モードでオープンしています。また、直後に FIFO のパスを rm していますが、オープン中なのでスクリプトが終わるまでは、FIFO を利用できます。
次のような実行結果になりました。
ThinkPad X301 Core2 1.40GHz 上の CentOS 5 x86_64 で実行しています。
# dmidecode | grep Version Version: 6EET54WW (3.14 ) Version: ThinkPad X301 Version: Not Available Version: Not Available Version: Intel(R) Core(TM)2 Duo CPU U9400 @ 1.40GHz SBDS Version: 03.01 # cat /etc/redhat-release CentOS release 5.7 (Final) # uname -a Linux my41 2.6.18-274.12.1.el5xen #1 SMP Tue Nov 29 14:18:21 EST 2011 x86_64 x86_64 x86_64 GNU/Linux # ./test_my_builtin_sleep.bash 4066.52 3937.55 4067.53 3938.46 4068.54 3939.47 4069.55 3940.45 4070.55 3941.45 4071.56 3942.46 4072.57 3943.47 4073.58 3944.47 4074.59 3945.47 4075.59 3946.48 4076.60 3947.48 4077.61 3948.49 4078.62 3949.49 4079.63 3950.50 4080.63 3951.50 4081.64 3952.49 4082.65 3953.50 4083.66 3954.50 4084.67 3955.51 4085.68 3956.51 real 0m20.161s user 0m0.012s sys 0m0.040s 4086.68 3957.52 4087.68 3958.52 4088.69 3959.53 4089.69 3960.53 4090.69 3961.53 4091.70 3962.53 4092.70 3963.54 4093.71 3964.54 4094.71 3965.55 4095.72 3966.55 4096.72 3967.55 4097.72 3968.56 4098.73 3969.56 4099.73 3970.57 4100.74 3971.57 4101.74 3972.58 4102.74 3973.58 4103.75 3974.58 4104.75 3975.59 4105.76 3976.59 real 0m20.079s user 0m0.000s sys 0m0.000s前半は外部コマンドの sleep を使っており、後半は read -t & FIFO 方式です。なお、時間の経過が読み取り易いように、/proc/uptime を表示しています。
外部コマンドを用いた場合は、user+sys で 52 ミリ秒のオーバーヘッドが見えているのに対して、read -t & FIFO 方式では、ゼロ (オーバーヘッドが1ミリ秒未満) に見えています。期待通りです。
システムの負荷が高いと、fork & exec が遅延する場合があるので、実験で用いたスクリプトのように小刻みに sleep する処理は、影響を受け易いです。そんな場面に遭遇したら、read -t & FIFO 方式を試してみては。
それだったら、無理に bash で書かなくてもいいんじゃないのという声もありそうですが。
そりゃその通りで、適材適所で perl に Ruby に Python に・・・豊かな時代ですね。
2014-05-11追記
RHEL7.0 RC を試していたのですが、最近は read -t で少数(0.5秒とか)を指定できるようです。sleep も、いつの間にか。
[root@hoge ~]# uname -a Linux hoge 3.10.0-121.el7.x86_64 #1 SMP Tue Apr 8 10:48:19 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux [root@hoge ~]# rpm -q bash bash-4.2.45-5.el7.x86_64 [root@hoge ~]# time sleep 0.5 real 0m0.501s user 0m0.001s sys 0m0.000s [root@hoge ~]# time read -t 0.5 real 0m0.500s user 0m0.000s sys 0m0.000sというわけで、read -t + FIFO でミリ秒単位の sleep も出来そうです。ご入用の方は、どうぞ。
なお、mkfifo を使わないことが可能なのかどうかは、未だに分からずです。bash のビルトインを自作すれば可能でしょうけども。
このオーバーヘッドが無くなるのはいいですね!!
返信削除mkfifoはどうやってbashで実現すればいいですか
返信削除