質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.80%

親プロセスを終了させずに、子プロセスを正常に終了させる方法

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 875

score 10

お世話になっております。
Raspberry Pi4にて、センサー制御とデータ取得のシステムを作成しています。
今回、教えていただきたいのは、タイトルのとおりで、
「無限ループ(待機)中の親プロセスから、トリガーによって呼び出された子プロセスを、親プロセスを終了させることなく、正常に終了させる方法」
です。

前提・実現したいこと

OS:Raspbian Buster
使用言語:python3.7、bash

ラズパイに複数センサーを接続し、そのデータを取得しています。
/etc/rc.localにbutton_rec.pyを追加して、起動時に実行されるようにしています。
start.shはGPIOピンに繋がったボタンの挙動を監視しており、
特定のボタンが押されると、start.shがセンサーを起動させ、そのデータをファイルに記述していき、stop.shで止める、ということが実現したいです。

ファイル配下の通りです。

button_rec.py
  ┣ start.sh
  ┃    ┣  sensor1(C++)
  ┃    ┗ sensor2.py
  ┗ stop.sh


前述したとおり、

  1. button_rec.pyはラズパイ起動時に自動起動しGPIOの状態を監視
  2. スタート用ボタンの押下で、start.shを呼び出す
  3. start.shはsensor1とsensor2.pyをバックグラウンドで実行させ、データを取得
  4. ストップ用ボタンの押下で、stop.shを呼び、sensor1とsensor2.pyを終了させる
    という流れを期待しています。
  • ここで、sensor1とsensor2.pyはそれぞれ単体では機能しています。
    また、当然ですが、それぞれを単体で呼び出したとき、Ctrl+C、あるいはkill -2 PIDで終了させても、期待通りにデータのログファイルは生成・記録されます。
  • start.shで呼び出した後、手動およびstop.shでsensor1、sensor2.pyを終了させると、データのログファイルは生成されるも、データが記録されません。

主なコードは以下のとおりです。

#!/usr/bin/python3
# -*- coding:utf-8 -*-

import RPi.GPIO as GPIO
import time
import sys, os
import ViewMessage as vm

if __name__ == "__main__":

    vm.set_HelloMsg()

    try:
        pin_start = 15
        pin_stop = 23

        GPIO.setmode(GPIO.BCM)
        GPIO.setup(pin_start, GPIO.IN, pull_up_down = GPIO.PUD_UP)
        GPIO.setup(pin_stop, GPIO.IN, pull_up_down = GPIO.PUD_UP)

        while True:
            button_blue = GPIO.input(pin_start)
            button_yellow = GPIO.input(pin_stop)

            cmd = ""
            if button_blue == False:
                GPIO.cleanup()
                os.system("/home/pi/project/src/start.sh")
            elif button_yellow == False:
                GPIO.cleanup()
                os.system("/home/pi/project/src/stop.sh")
            if cmd != "":
                ret = os.popen(cmd).readline().strip()
                print(ret)
                time.sleep(1)

            time.sleep(1)

        GPIO.cleanup()

    except KeyboardInterrupt:
        GPIO.cleanup()
        sys.exit()
#!/bin/bash

/home/pi/project/src/sensor1 > /home/pi/project/temp/"`date +%Y%m%d_%H%M%S`"_sensor1.txt &
python /home/pi/project/src/sensor2.py > /home/pi/project/temp/"`date +%Y%m%d_%H%M%S`"_sensor2.txt &
#!/bin/bash

PID=`pgrep start.sh`
PIDS[0]=`pgrep python -n`
PIDS[1]=`pgrep sensor1`

echo $PID
echo ${PIDS[0]}
echo ${PIDS[1]}

echo `sudo kill -2 -${PIDS[0]}`
echo `sudo kill -2 -${PIDS[1]}`

試したこと

運用時のままの構成とプログラムでは問題がわかりにくいかと思い、以下のような構成でテストを行いました。

start.sh
  ┣ test1.py
  ┗ test2.py


コードは以下のとおりです。

# start.sh
#!/bin/bash

/usr/bin/python3 test1.py &
/usr/bin/python3 test2.py &

while :;
do
    echo "Now running..."
    sleep 5
done
# test1.py
#!/usr/bin/python3
# -*- coding:utf-8 -*-

import time
import sys, os

BASE_DIR = './'

try:
    path = BASE_DIR + "test1.txt"
    with open(path, mode='w') as f:
        while True:
            msg = "Hello\n"
            #print(msg)
            f.write(msg)
            time.sleep(1)

except KeyboardInterrupt:
    print("end")
    #sys.exit()
    os._exit(0)
# test2.py
#!/usr/bin/python3
# -*- coding:utf-8 -*-

import time
import sys, os

BASE_DIR = "./"
try:
    path = BASE_DIR + "test2.txt"
    with open(path, mode="w") as f:
        msg = "World!\n"
        while True:
            #print(msg)
            f.write(msg)
            time.sleep(1)

except KeyboardInterrupt:
    print("end")
    #sys.exit()
    os._exit(0)


実運用のものとの違いとしては、

  • よびだされるプログラムがこちらは両方共pythonのもの。
  • リダイレクトでファイルに記述していた出力を、python内で行う。

この構成でいくつかパターンを変えて実行しました。

  1. test1.pyまたはtest2.pyのみ実行
  2. start.shを実行

この時、1のパターンでは、kill -2 PIDではファイルが作成されますが、kill -KILL PIDではされませんでした。(このあたりは自明なのかもしれませんが、Linuxやシステムの知識が乏しいため、わかりませんでした)
2のパターンでは先にtest1.py、test.2.pyをkillしても、start.shをkillしてもファイルは作られても、記述はされませんでした。

タイトル、および冒頭に記述した通り、 start.shを止めることなく、ファイル作成・記述を行って二つの子プロセスを止める方法はありますでしょうか?

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • otn

    2020/03/23 14:00

    > start.shを止めることなく、ファイル作成・記述を行って二つの子プロセスを止める方法はありますでしょうか?

    お書きのコードで出来ているのでは?不足があるなら、
    ・こういう結果を期待した
    ・しかし、実際にはこういう結果だった
    のように、整理して書いてください。

    キャンセル

  • yoddy

    2020/03/23 14:16

    ご教示ありがとうございます。
    ただkillするだけだと問題ないのですが、記録がされないというのが致命的でした。
    最初にご回答くださった方のソースで、解決いたしましたが、otn様にもお心砕きいただいたこと、感謝いたします。

    キャンセル

  • otn

    2020/03/23 14:20

    では質問は、「子プロセスを止める際に、それまでの結果がファイルに書き込まれるようにする方法」と書くべきでしたね。

    キャンセル

  • yoddy

    2020/03/23 17:54

    おっしゃるとおりですね。
    次回からはタイトルもしっかりと推敲したいと思います。

    キャンセル

回答 2

checkベストアンサー

+1

  1. start.shでバックグラウンドで起動したプロセスidをhoge.pidsに記録する
  2. stop.shは、hoge.pidsをみてkillする
  3. *pyは、write直後に都度flushする

わたしなら、こうするかな。

diff --git a/start.sh b/start.sh
index c5a540d..d0a57a0 100644
--- a/start.sh
+++ b/start.sh
@@ -1,8 +1,13 @@
 # start.sh
 #!/bin/bash

+rm ./hoge.pids
+
 /usr/bin/python3 test1.py &
+echo $! >> hoge.pids
+
 /usr/bin/python3 test2.py &
+echo $! >> hoge.pids

 while :;
 do
diff --git a/stop.sh b/stop.sh
index a288d3f..7c02e17 100644
--- a/stop.sh
+++ b/stop.sh
@@ -1,12 +1,5 @@
-#!/bin/bash
+#!/bin/sh

-PID=`pgrep start.sh`
-PIDS[0]=`pgrep python -n`
-PIDS[1]=`pgrep sensor1`
+kill -9 $(cat ./hoge.pids)

-echo $PID
-echo ${PIDS[0]}
-echo ${PIDS[1]}
-
-echo `sudo kill -2 -${PIDS[0]}`
-echo `sudo kill -2 -${PIDS[1]}`
\ No newline at end of file
+rm ./hoge.pids
\ No newline at end of file
diff --git a/test1.py b/test1.py
index fa8398e..7f1b78f 100644
--- a/test1.py
+++ b/test1.py
@@ -11,9 +11,10 @@ try:
     path = BASE_DIR + "test1.txt"
     with open(path, mode='w') as f:
         while True:
-            msg = "Hello\n"
+            msg = "Hello (%f)\n" % (time.time())
             #print(msg)
             f.write(msg)
+            f.flush()
             time.sleep(1)

 except KeyboardInterrupt:
diff --git a/test2.py b/test2.py
index 663ad7d..c06a926 100644
--- a/test2.py
+++ b/test2.py
@@ -9,10 +9,11 @@ BASE_DIR = "./"
 try:
     path = BASE_DIR + "test2.txt"
     with open(path, mode="w") as f:
-        msg = "Hello\n"
+        msg = "Hello (2) (%f)\n" % (time.time())
         while True:
             #print(msg)
             f.write(msg)
+            f.flush()
             time.sleep(1)

 except KeyboardInterrupt:

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/23 14:15

    ご教示ありがとうございます。
    出先ですが、いまテストしたところ、期待通りの動きをしてくれました。
    実運用ファイルは、一つがC++のものですが、おそらくそちらも問題なく記録されるかと思います。
    素早く、適切、かつ簡素なソースでご回答頂き、感謝いたします!

    キャンセル

+1

KeyboardInterruptは、端末に接続されたプロセスでしか効かないようです。代わりにsignal.signal()を使います。

os._exit()は、ファイルへの書き込みバッファのフラッシュを行いませんので、代わりにsys.exit()を使います。

import time, sys, os, signal

BASE_DIR = './'

def sigint(n,x):
    print("end")
    sys.exit(0) #通常のプログラム終了処理を行う

signal.signal(2,sigint) #SIGINT時に実行する関数を登録する

path = BASE_DIR + "test1.txt"
with open(path, mode='w') as f:
    while True:
        msg = "Hello\n"
        f.write(msg)
        time.sleep(1)

kill -2 PIDではファイルが作成されますが、kill -KILL PIDではされませんでした。

ファイルがopenされてからkill -KILLされても一旦作られたファイルは消えませんので、ファイルが作成されてなかったと言うことなら、openする前にkillしてしまったのでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/03/23 18:00 編集

    タイトル詐欺みたいでお手間を取らせたのに、詳しいご回答、ありがとうございます。
    sys.exit()については、れこれググったりしている中で、os._exit()を見つけて、藁にもすがる気持ちで使ってみたのですが、バッファのフラッシュを行わないんですね。勉強になりました。
    signal.signal() というのは初めて見ました。調べてみたいと思います。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る