แก้ไขตัวแปรในสคริปต์ Bash ที่กำลังรันอยู่

ฉันมีสคริปต์ทุบตีที่ประมวลผลข้อมูลหลายปี ดังนั้นสคริปต์อาจใช้เวลาหนึ่งสัปดาห์จึงจะเสร็จสิ้น เพื่อเร่งกระบวนการให้เร็วขึ้น ฉันใช้มัลติเธรดโดยเรียกใช้หลายอินสแตนซ์พร้อมกัน (แต่ละอินสแตนซ์ = ข้อมูล 1 วัน) แต่ละอินสแตนซ์ใช้ CPU 1 ตัว ดังนั้นฉันจึงสามารถเรียกใช้อินสแตนซ์ได้มากเท่าที่มี CPU ขณะที่ฉันกำลังเรียกใช้กระบวนการในเซิร์ฟเวอร์ที่มีประสิทธิภาพซึ่งฉันแบ่งปันกับผู้อื่น ในบางครั้ง ฉันอาจมี CPU ที่พร้อมใช้งานไม่มากก็น้อย สคริปต์ปัจจุบันของฉันคือ:

#!/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

ดังนั้น คำถามของฉันคือ หากสคริปต์นี้ทำงานอยู่ มีวิธีใดที่ฉันสามารถเปลี่ยนตัวแปร NUMCPUS เป็นค่าใดๆ (เช่น NUMCPUS=23) โดยไม่ต้องหยุดสคริปต์ได้ ถ้าเป็นไปได้ ฉันต้องการวิธีที่ไม่เกี่ยวข้องกับการอ่านหรือการเขียนไฟล์ (ฉันต้องการลดไฟล์ชั่วคราวให้เป็น 0 ถ้าเป็นไปได้) ฉันไม่รังเกียจหากมันเป็นกระบวนการ "แฮ็ก" เช่นวิธีที่อธิบายไว้ใน คำตอบนี้ ที่จริงแล้วฉันลองใช้คำสั่งที่คล้ายกันของ gdb เช่นเดียวกับคำตอบนั้น แต่มันใช้งานไม่ได้ ฉันมีข้อผิดพลาดต่อไปนี้ใน gdb (และทำให้กระบวนการขัดข้องด้วย):

(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

แก้ไข 1: ความคิดเห็นบางส่วนต่อสคริปต์:

  • gLAB_linux เป็นโปรแกรมประมวลผลแกนเดียวและไม่ทราบถึงตัวแปร NUMCPUS
  • การดำเนินการ gLAB_linux แต่ละครั้งจะใช้เวลาประมาณ 5 ชั่วโมงจึงจะเสร็จสิ้น ดังนั้นสคริปต์ทุบตีจึงใช้เวลาส่วนใหญ่อยู่ใน wait -n
  • NUMCPUS ต้องเป็นตัวแปรท้องถิ่นสำหรับสคริปต์ เนื่องจากอาจมีสคริปต์อื่นที่มีลักษณะเช่นนี้ทำงานแบบขนาน (เปลี่ยนเฉพาะพารามิเตอร์ที่กำหนดให้กับ gLAB_linux) ดังนั้น NUMCPUS จึงไม่สามารถเป็นตัวแปรสภาพแวดล้อมได้
  • กระบวนการเดียวในการเข้าถึง NUMCPUS คือสคริปต์ทุบตี

แก้ไข 2: หลังจาก @Kamil ตอบ ฉันเพิ่มข้อเสนอสำหรับการอ่านจำนวน 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 แหล่งที่มา
comment
ดูอาร์กิวเมนต์ --limit ของ GNU แบบขนาน   -  person choroba    schedule 22.11.2018
comment
แล้วการควบคุมด้วยสัญญาณล่ะ?   -  person georgexsh    schedule 22.11.2018
comment
แต่ละอินสแตนซ์จะมีไฟล์และโฟลเดอร์อินพุตและเอาท์พุตที่แตกต่างกัน ดังนั้นฉันจึงใช้คำสั่ง bash แบบขนานไม่ได้ ฉันแก้ไขคำถาม สำหรับสัญญาณ ปัญหาคือฉันไม่สามารถตั้งค่าตัวแปรตามอำเภอใจได้   -  person AwkMan    schedule 22.11.2018
comment
@AwkMan การอ่านตัวแปรจากไฟล์มีความน่าเชื่อถือมากกว่าการแฮ็กด้วย gdb มาก แต่ฉันเดาว่าคุณกำลังทำสิ่งนี้เพื่อความสนุกสนานมากกว่าโชคดี! btw parallel สามารถอ่านบรรทัด cmd จาก stdin   -  person georgexsh    schedule 22.11.2018
comment
เกี่ยวกับข้อผิดพลาด wait -n: wait -n รอให้งานพื้นหลังถัดไปเสร็จสิ้น ถัดไป เช่นเดียวกับใน ถัดไปของงานพื้นหลังทั้งหมดของเซสชันปัจจุบัน PID ที่ระบุหลัง -n จะถูกละเว้น คุณสามารถตรวจสอบสิ่งนี้ได้ด้วย sleep 1 & sleep 9 & wait -n $! $! คือ PID ของ sleep 9 แต่ wait จะรอเพียง sleep 1 เท่านั้น   -  person Socowi    schedule 22.11.2018
comment
@Socowi ข้อผิดพลาดที่ฉันพูดถึงคือรอผลตอบแทนก่อนที่กระบวนการใด ๆ จะเสร็จสิ้น เนื่องจากกระบวนการเดียวของเซสชันนี้คือกระบวนการ gLAB_linux ในเบื้องหลัง ดังนั้นการรอควรกลับมาเฉพาะเมื่อ gLAB_linux ใด ๆ เสร็จสิ้นแล้วเท่านั้น แต่ฉันเห็นว่าฉันมี gLAB_linux มากกว่า NUMCPUs ทำงานอยู่ (และมากขึ้นเรื่อยๆ) หากฉันไม่ได้เพิ่มในขณะที่   -  person AwkMan    schedule 22.11.2018
comment
@georgexsh ใช่ ฉันต้องการแฮ็ก gdb เพราะมันสนุก และการเปลี่ยนตัวแปรเป็นสิ่งที่ฉันแทบไม่ต้องทำ แม้ว่าฉันจะใช้งานแบบขนาน แต่ก็ยังมีปัญหาเดิม ฉันจะเปลี่ยนจำนวน CPU ไดนามิก ณ จุดใด ๆ ของการประมวลผลได้อย่างไร   -  person AwkMan    schedule 22.11.2018
comment
@AwkMan ขนานรองรับการอ่าน proc num จากไฟล์   -  person georgexsh    schedule 22.11.2018


คำตอบ (2)


วิธีที่ดีที่สุดคือแก้ไขสคริปต์ bash เพื่อให้ทราบว่าคุณเปลี่ยนค่า การแก้ไขตัวแปรสภาพแวดล้อมจากภายในเซสชัน gdb - นั่นเป็นเพียงการรบกวนและส่วนใหญ่จะละทิ้งงานของนักพัฒนารายอื่น

ด้านล่างนี้ฉันใช้ไฟล์ชื่อ /tmp/signal_num_cpus หากไม่มีไฟล์อยู่ สคริปต์จะใช้ค่า NUMCPUS หากไฟล์มีอยู่ ไฟล์จะอ่านเนื้อหาและอัปเดตจำนวน NUMCPUS ตามนั้น จากนั้นพิมพ์การแจ้งเตือนบางอย่างว่า numcpus ถูกเปลี่ยนเป็นไฟล์ หากไฟล์มีอยู่และไม่มีหมายเลขที่ถูกต้อง (เช่น ในช่วงที่กำหนดไว้ล่วงหน้าหรือ smth) ไฟล์จะพิมพ์ข้อความแสดงข้อผิดพลาดลงในไฟล์ อีกฝ่ายได้รับแจ้งว่าทุกอย่างเรียบร้อยดีหรือมีเหตุร้ายเกิดขึ้น

#!/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

และการเปลี่ยนสคริปต์ค่าอาจมีลักษณะดังนี้:

#!/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"

เครื่องหมาย:

  • วิธีที่ถูกต้องในการกำหนดฟังก์ชันคือ func() { :; } การใช้ function func { } เป็นสิ่งที่นำมาจาก ksh และได้รับการสนับสนุนเป็นส่วนขยาย ใช้ func() {}
  • เป็นการดีที่จะใช้ส่วนขยายทางคณิตศาสตร์ (( ... )) สำหรับการเปรียบเทียบตัวเลขและการจัดการ
  • การใช้ backticks ` สำหรับการทดแทนคำสั่ง $( ... ) เลิกใช้แล้ว
person KamilCuk    schedule 22.11.2018
comment
วิธีการใช้ไฟล์ของคุณ ฉันพบว่ามันซับซ้อนเกินไป โดยเฉพาะการใช้ Declaration และ mkfifo อาจเป็นเพราะฉันลืมชี้แจงบางสิ่งในคำถามของฉัน (ดูการแก้ไขในคำถามของฉัน) ฉันจะแก้ไข (อีกครั้ง) คำถามของฉันในการเพิ่มข้อเสนอของฉัน - person AwkMan; 22.11.2018
comment
declare มีไว้เพื่อทำให้การอ่านง่ายขึ้น คุณสามารถโยนทิ้งทั้งหมดได้ mkfifo ถูกใช้เพราะมันง่ายกว่าในการซิงโครไนซ์กระบวนการโดยใช้ไฟล์เหล่านั้น คุณสามารถใช้ไฟล์ปกติ + flock หรือไม่สนใจการล็อคเลย หากคุณค้นหาสคริปต์ที่จะรันหลายอินสแตนซ์ ให้เพิ่มชื่อที่ไม่ซ้ำกันต่อท้ายชื่อไฟล์ config ในแต่ละสคริปต์ เช่น โดยใช้ $$ - person KamilCuk; 22.11.2018

บทที่ 7.1 ของ GNU Parallel 2018 ครอบคลุมถึงวิธีการเปลี่ยนจำนวนเธรดที่จะใช้ในขณะที่รัน 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

ดังนั้นคุณเพียงแค่ใส่อาร์กิวเมนต์สำหรับ --jobs ลงใน my_jobs และ GNU Parallel จะอ่านสิ่งนี้หลังจากงานแต่ละงานเสร็จสมบูรณ์

person Ole Tange    schedule 28.11.2018