ทำไมถึงมีสภาพการแข่งขัน
ทั้งสองด้านของไปป์จะถูกดำเนินการในแบบคู่ขนาน มีวิธีง่ายๆในการสาธิต: เรียกใช้
time sleep 1 | sleep 1
ใช้เวลาหนึ่งวินาทีไม่ใช่สอง
เชลล์เริ่มกระบวนการลูกสองกระบวนการและรอให้กระบวนการทั้งสองเสร็จสมบูรณ์ กระบวนการทั้งสองนี้ทำงานแบบขนาน: สาเหตุเดียวที่ทำให้กระบวนการหนึ่งทำข้อมูลให้ตรงกันกับอีกกระบวนการหนึ่งคือเมื่อต้องรอกระบวนการอื่น จุดที่พบบ่อยที่สุดของการซิงโครไนซ์คือเมื่อบล็อกด้านขวารอให้อ่านข้อมูลในอินพุตมาตรฐานและจะถูกปลดบล็อกเมื่อด้านซ้ายเขียนข้อมูลมากขึ้น การสนทนาสามารถเกิดขึ้นได้เมื่อด้านขวาช้าในการอ่านข้อมูลและบล็อกด้านซ้ายในการดำเนินการเขียนจนกระทั่งด้านขวาอ่านข้อมูลมากขึ้น (มีบัฟเฟอร์ในท่อเองจัดการโดย เคอร์เนล แต่มีขนาดสูงสุดเล็ก ๆ )
เมื่อต้องการสังเกตจุดการซิงโครไนซ์ให้สังเกตคำสั่งต่อไปนี้ ( sh -x
พิมพ์แต่ละคำสั่งขณะที่เรียกใช้งาน):
time sh -x -c '{ sleep 1; echo a; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { cat; }'
time sh -x -c '{ echo a; sleep 1; } | { sleep 1; cat; }'
time sh -x -c '{ sleep 2; echo a; } | { cat; sleep 1; }'
เล่นกับรูปแบบต่างๆจนกว่าคุณจะพอใจกับสิ่งที่คุณสังเกต
รับคำสั่งผสม
cat tmp | head -1 > tmp
กระบวนการทางซ้ายทำตามขั้นตอนต่อไปนี้ (ฉันเพิ่งทำรายการตามขั้นตอนที่เกี่ยวข้องกับคำอธิบายของฉัน):
- รันโปรแกรมภายนอกที่มีการโต้แย้ง
cat
tmp
- เปิด
tmp
ให้อ่าน
- ในขณะที่ยังไม่ถึงจุดสิ้นสุดของไฟล์ให้อ่านอันจากไฟล์และเขียนไปยังเอาต์พุตมาตรฐาน
กระบวนการทางขวาทำสิ่งต่อไปนี้:
- เปลี่ยนทิศทางเอาต์พุตมาตรฐานไปที่การ
tmp
ตัดทอนไฟล์ในกระบวนการ
- รันโปรแกรมภายนอกที่มีการโต้แย้ง
head
-1
- อ่านหนึ่งบรรทัดจากอินพุตมาตรฐานและเขียนลงในเอาต์พุตมาตรฐาน
จุดเดียวของการซิงโครไนซ์คือ Right-3 รอให้ Left-3 ประมวลผลเต็มหนึ่งบรรทัด ไม่มีการซิงโครไนซ์ระหว่าง left-2 และ right-1 ดังนั้นพวกเขาจึงสามารถเกิดขึ้นได้ในทั้งสองคำสั่ง สิ่งที่พวกเขาเกิดขึ้นนั้นไม่สามารถคาดเดาได้: มันขึ้นอยู่กับสถาปัตยกรรมของ CPU, บนเชลล์, ในเคอร์เนล, ซึ่งแกนประมวลผลเกิดขึ้นตามกำหนดเวลา, ในสิ่งที่อินเตอร์รัปต์ที่ CPU ได้รับในช่วงเวลานั้นเป็นต้น
วิธีการเปลี่ยนพฤติกรรม
คุณไม่สามารถเปลี่ยนพฤติกรรมได้โดยเปลี่ยนการตั้งค่าระบบ คอมพิวเตอร์ทำในสิ่งที่คุณบอกให้ทำ คุณบอกให้ตัดทอนtmp
และอ่านจากtmp
แบบขนานดังนั้นทั้งสองอย่างขนานกัน
ตกลงมี "การตั้งค่าระบบ" หนึ่งที่คุณสามารถเปลี่ยนได้: คุณสามารถแทนที่/bin/bash
ด้วยโปรแกรมอื่นที่ไม่ใช่การทุบตี ฉันหวังว่ามันจะไปโดยไม่บอกว่านี่ไม่ใช่ความคิดที่ดี
หากคุณต้องการให้การตัดปลายเกิดขึ้นก่อนทางด้านซ้ายของไพพ์คุณต้องวางไว้ด้านนอกไพพ์ไลน์เช่น:
{ cat tmp | head -1; } >tmp
หรือ
( exec >tmp; cat tmp | head -1 )
ฉันไม่รู้ว่าทำไมคุณถึงต้องการสิ่งนี้ จุดใดในการอ่านไฟล์ที่คุณรู้ว่าว่างเปล่า
ในทางกลับกันหากคุณต้องการให้การเปลี่ยนเส้นทางเอาต์พุต (รวมถึงการตัดปลาย) เกิดขึ้นหลังจากcat
อ่านเสร็จแล้วคุณต้องบัฟเฟอร์ข้อมูลในหน่วยความจำเช่น
line=$(cat tmp | head -1)
printf %s "$line" >tmp
หรือเขียนไปยังไฟล์อื่นแล้วย้ายเข้าที่ นี่เป็นวิธีที่ดีในการทำสิ่งต่าง ๆ ในสคริปต์และมีข้อได้เปรียบที่ไฟล์เขียนเต็มก่อนที่จะสามารถมองเห็นได้ด้วยชื่อดั้งเดิม
cat tmp | head -1 >new && mv new tmp
moreutilssponge
คอลเลกชันรวมถึงโปรแกรมที่ไม่เพียงแค่นั้นเรียกว่า
cat tmp | head -1 | sponge tmp
วิธีการตรวจสอบปัญหาโดยอัตโนมัติ
หากเป้าหมายของคุณคือการเขียนสคริปต์ที่ไม่ดีและคิดออกโดยอัตโนมัติว่าพวกเขาแตกหักอย่างไรเสียใจด้วยชีวิตไม่ใช่เรื่องง่าย การวิเคราะห์รันไทม์จะไม่พบปัญหาได้อย่างน่าเชื่อถือเพราะบางครั้งcat
เสร็จสิ้นการอ่านก่อนที่จะตัดทอน การวิเคราะห์เชิงสถิตในหลักการทำได้ ตัวอย่างที่ง่ายในคำถามของคุณถูกตรวจจับโดยShellcheckแต่อาจไม่พบปัญหาที่คล้ายกันในสคริปต์ที่ซับซ้อนกว่านี้