Ubah variabel dalam skrip Bash yang sedang berjalan

Saya memiliki skrip bash yang memproses data beberapa tahun, oleh karena itu skrip memerlukan waktu seminggu untuk menyelesaikannya. Untuk mempercepat prosesnya, saya menggunakan multithreading, dengan menjalankan beberapa instance secara paralel (setiap instance = 1 hari data). Setiap instance menempati 1 CPU, jadi saya dapat menjalankan instance sebanyak CPU yang tersedia. Saat saya menjalankan proses di server kuat yang saya bagikan dengan orang lain, suatu saat saya mungkin memiliki lebih banyak atau lebih sedikit CPU yang tersedia. Skrip saya saat ini adalah:

#!/bin/bash
function waitpid {
   #Gather the gLABs PID background processes (Maximum processes in 
   #background as number of CPUs)
   NUMPIDS=`jobs -p|awk 'END {print NR}'`
   #A while is set because there seems to be a bug in bash that makes 
   #sometimes the "wait -n" command
   #exit even if none of provided PIDs have finished. If this happens, 
   #the while loops forces the 
   #script to wait until one of the processes is truly finished
   while [ ${NUMPIDS} -ge ${NUMCPUS} ]
   do
     #Wait for gLAB processes to finish
     PIDS="`jobs -p|awk -v ORS=" " '{print}'`"
     wait -n ${PIDS} >/dev/null 2>/dev/null
     NUMPIDS=`jobs -p|awk 'END {print NR}'`
   done
}
NUMPCUS=10
for(...) #Loop for each day
do
   day=... #Set current day variable
   #Command to execute, put in background
   gLAB_linux -input ${day}folder/${day}.input -output ${day)outfolder/${day}.output &        
   #Wait for any process to finish if NUMCPUS number of processes are running in background
   waitpid 
done

Oleh karena itu, pertanyaan saya adalah: jika skrip ini berjalan, apakah ada cara untuk mengubah variabel NUMCPUS ke nilai apa pun (misalnya NUMCPUS=23) tanpa menghentikan skrip?. Jika memungkinkan, saya lebih memilih metode yang tidak melibatkan membaca atau menulis ke file (saya ingin mengurangi file sementara menjadi 0 jika memungkinkan). Saya tidak keberatan jika ini adalah proses "retas", seperti metode yang dijelaskan dalam jawaban ini. Sebenarnya, saya mencoba perintah serupa di gdb seperti pada jawaban itu tetapi tidak berhasil, saya mengalami kesalahan berikut di gdb (dan juga membuat proses macet):

(gdb) attach 23865
(gdb) call bind_variable("NUMCPUS",11,0)
'bind_variable' has unknown return type; cast the call to its declared return type
(gdb) call (int)bind_variable("NUMCPUS",11,0)
Program received signal SIGSEGV, Segmentation fault

EDIT1: Beberapa komentar pada skrip:

  • gLAB_linux adalah program pemrosesan inti tunggal dan tidak mengetahui variabel NUMCPUS
  • Setiap eksekusi gLAB_linux membutuhkan waktu sekitar 5 jam untuk diselesaikan, oleh karena itu skrip bash sebagian besar waktunya tertidur di dalam file wait -n.
  • NUMCPUS harus berupa variabel lokal untuk skrip, karena mungkin ada skrip lain seperti ini yang berjalan secara paralel (hanya mengubah parameter yang diberikan ke gLAB_linux). Oleh karena itu NUMCPUS tidak dapat menjadi variabel lingkungan.
  • Satu-satunya proses yang mengakses NUMCPUS adalah skrip bash

EDIT2: Setelah jawaban @Kamil, saya menambahkan proposal saya untuk membaca dari file jumlah CPU

function waitpid {
   #Look if there is a file with new number of CPUs
   if [ -s "/tmp/numCPUs_$$.txt" ]
   then
     TMPVAR=$(awk '$1>0 {print "%d",$1} {exit}' "/tmp/numCPUs_$$.txt")
     if [ -n "${TMPVAR}" ]
     then
       NUMCPUS=${TMPVAR}
       echo "NUMCPUS=${TMPVAR}"
     fi
     rm -f "/tmp/numCPUs_$$.txt"
   fi

   #Gather the gLABs PID background processes (Maximum processes in 
   #background as number of CPUs)
   NUMPIDS=`jobs -p|awk 'END {print NR}'`
   #A while is set because there seems to be a bug in bash that makes 
   #sometimes the "wait -n" command
   #exit even if none of provided PIDs have finished. If this happens, 
   #the while loops forces the 
   #script to wait until one of the processes is truly finished
   while [ ${NUMPIDS} -ge ${NUMCPUS} ]
   do
     #Wait for gLAB processes to finish
     PIDS="`jobs -p|awk -v ORS=" " '{print}'`"
     wait -n ${PIDS} >/dev/null 2>/dev/null
     NUMPIDS=`jobs -p|awk 'END {print NR}'`
   done
}

person AwkMan    schedule 22.11.2018    source sumber
comment
Lihat argumen --limit dari paralel GNU.   -  person choroba    schedule 22.11.2018
comment
bagaimana dengan kontrol dengan sinyal?   -  person georgexsh    schedule 22.11.2018
comment
Setiap instance akan memiliki file dan folder input dan output yang berbeda, jadi saya tidak bisa menggunakan perintah bash paralel. Saya mengedit pertanyaannya. Dengan sinyal, masalahnya adalah saya tidak bisa menetapkan nilai sembarang ke variabel   -  person AwkMan    schedule 22.11.2018
comment
@AwkMan membaca variabel dari file jauh lebih dapat diandalkan daripada meretas dengan gdb, tapi saya rasa Anda melakukan ini untuk bersenang-senang, daripada beruntung! btw parallel bisa membaca baris cmd dari stdin.   -  person georgexsh    schedule 22.11.2018
comment
Mengenai bug wait -n: wait -n menunggu pekerjaan latar belakang berikutnya selesai; berikutnya seperti pada berikutnya semua pekerjaan latar belakang pada sesi saat ini. PID yang ditentukan setelah -n diabaikan. Anda dapat memverifikasi ini dengan sleep 1 & sleep 9 & wait -n $!. $! adalah PID dari sleep 9, tetapi wait hanya akan menunggu sleep 1.   -  person Socowi    schedule 22.11.2018
comment
@Socowi Bug yang saya sebutkan adalah menunggu kembali sebelum proses apa pun selesai. Karena satu-satunya proses pada sesi ini adalah proses gLAB_linux di latar belakang, wait akan kembali hanya ketika gLAB_linux telah selesai. Tetapi saya melihat bahwa saya menjalankan lebih dari NUMCPU gLAB_linux (dan semakin banyak) jika saya tidak menambahkan while   -  person AwkMan    schedule 22.11.2018
comment
@georgexsh Ya, saya ingin peretasan gdb karena menyenangkan, dan mengubah variabel adalah sesuatu yang jarang perlu saya lakukan. Tentang paralel, meskipun saya menggunakannya, saya masih memiliki masalah yang sama, bagaimana cara mengubah jumlah CPU secara dinamis pada setiap titik eksekusi?   -  person AwkMan    schedule 22.11.2018
comment
@AwkMan paralel mendukung pembacaan proc num dari suatu file.   -  person georgexsh    schedule 22.11.2018


Jawaban (2)


Yang terbaik adalah memodifikasi skrip bash sehingga diketahui bahwa Anda mengubah nilainya. Memodifikasi variabel lingkungan dari dalam sesi gdb - itu hanya mengganggu dan sebagian besar membuang pekerjaan pengembang lain.

Di bawah ini saya menggunakan file bernama /tmp/signal_num_cpus. Jika file tidak ada, skrip menggunakan nilai NUMCPUS. Jika file tersebut memang ada, ia membaca kontennya dan memperbarui jumlah NUMCPUS yang sesuai dan kemudian mencetak beberapa pemberitahuan bahwa numcpus telah diubah ke file tersebut. Jika file memang ada dan tidak berisi nomor yang valid (misalnya dalam rentang yang telah ditentukan sebelumnya), maka file tersebut akan mencetak beberapa pesan kesalahan ke dalam file. Pihak lain diberitahu bahwa semuanya baik-baik saja atau sesuatu yang buruk telah terjadi

#!/bin/bash

is_not_number() { 
    (( $1 != $1 )) 2>/dev/null
}

# global variable to hold the number of cpus with a default value
NUMCPUS=${NUMCPUS:=5}
# this will ideally execute on each access to NUMCPUS variable
# depending on content
get_num_cpus() { 
   # I tell others that NUMCPUS is a global variable and i expect it here
   declare -g NUMCPUS
   # I will use this filename to communicate
   declare -r file="/tmp/signal_num_cpus"
   # If the file exists and is a fifo...
   if [ -p "$file" ]; then
       local tmp
       # get file contents
       tmp=$(<"$file")
       if [ -z "$tmp" ]; then
           #empty is ignored
           :;
       elif is_not_number "$tmp"; then
           echo "Error reading a number from $file" >&2
           echo "error: not a number, please give me a number!" > "$file"
       else
           # If it is ok, update the NUMCPUS value
           NUMCPUS=$tmp
           echo "ok $NUMCPUS" > "$file"  # this will block until other side starts reading
       fi
   fi
   # last but not least, let's output it
   echo "$NUMCPUS"
}

# code duplication is the worst (ok, sometimes except for databases frameworks)
get_num_bg_jobs() {
    jobs -p | wc -l
}

waitpid() {
   while 
         (( $(get_num_bg_jobs) >= $(get_num_cpus) ))
   do
         wait -n
   done
}

# rest of the script

NUMPCUS=10
for(...) #Loop for each day
do
   day=... #Set current day variable
   #Command to execute, put in background
   gLAB_linux -input "${day}folder/${day}.input" -output "${day)outfolder/${day}.output" &        
   #Wait for any process to finish if NUMCPUS number of processes are running in background
   waitpid 
done

Dan mengubah skrip nilai akan terlihat seperti ini:

#!/bin/bash

# shared context between scripts
declare -r file="/tmp/signal_num_cpus"

mkfifo "$file"

echo 1 > "$file" # this will block until other side will start reading

IFS= read -r line < "$file"

case "$line" in
ok*) 
     read _ numcpus <<<"$line"
     echo "the script changed the number of numcpus to $numcpus"
     ;;
*)
     echo "the script errored with $error"
     ;;
esac

rm "$file"

Tanda:

  • cara yang benar untuk mendefinisikan suatu fungsi adalah func() { :; } Menggunakan function func { } adalah sesuatu yang diambil dari ksh dan didukung sebagai ekstensi. Gunakan func() {}
  • Sangat menyenangkan menggunakan ekspansi aritmatika (( ... )) untuk perbandingan angka dan penanganan.
  • Penggunaan backticks ` untuk substitusi perintah $( ... ) tidak digunakan lagi.
person KamilCuk    schedule 22.11.2018
comment
Metode Anda dalam menggunakan file menurut saya terlalu rumit, khususnya dalam penggunaan deklarasi dan mkfifo. Mungkin karena saya lupa menjelaskan beberapa hal di pertanyaan saya (lihat edit di pertanyaan saya). Saya akan mengedit (lagi) pertanyaan saya untuk menambahkan proposal saya - person AwkMan; 22.11.2018
comment
declare hanya ada untuk menyederhanakan pembacaan, Anda dapat membuang semuanya. mkfifo digunakan karena lebih mudah untuk menyinkronkan proses yang menggunakannya, Anda dapat menggunakan file normal + flock atau mengabaikan penguncian sama sekali. Jika Anda mencari beberapa contoh skrip untuk dijalankan, tambahkan nama unik ke nama file konfigurasi di setiap skrip, misalnya dengan menggunakan $$. - person KamilCuk; 22.11.2018

Bab 7.1 GNU Parallel 2018 membahas cara mengubah jumlah thread yang akan digunakan saat menjalankan https://zenodo.org/record/1146014

echo 50% > my_jobs
/usr/bin/time parallel -N0 --jobs my_jobs sleep 1 :::: num128 &
sleep 1
echo 0 > my_jobs
wait

Jadi Anda cukup memasukkan argumen untuk --jobs ke dalam my_jobs dan GNU Parallel akan membacanya setelah setiap pekerjaan selesai.

person Ole Tange    schedule 28.11.2018