Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การทดสอบเธรดแบบอะซิงโครนัสใน Ruby

เธรดและสภาพแวดล้อมแบบอะซิงโครนัสนั้นค่อนข้างยุ่งยากในตอนแรก หากไม่มีแบบจำลองทางจิตใจที่ดีในการจัดการปฏิสัมพันธ์ ก็จะเกิดปัญหาได้ง่ายและจบลงด้วยผลลัพธ์ที่ไม่คาดคิด ยิ่งไปกว่านั้น การทดสอบโค้ดอะซิงโครนัสอาจทำได้ยากหากไม่มีเครื่องมือหรือรูปแบบการทดสอบที่เหมาะสม

การคิดถึงเธรดในฐานะบุคคลและวัตถุที่แชร์เป็น 'สิ่งของ' ที่สามารถเป็นเจ้าของได้ ช่วยจัดระเบียบการทำงานของระบบมัลติเธรด ในตอนนี้ เราจะยกตัวอย่างเพื่อเรียนรู้ทั้งหมดเกี่ยวกับการทดสอบโค้ด Ruby แบบอะซิงโครนัส

หากคุณกำลังใช้ Rails หรือ Rack หรือแอปพลิเคชันใดๆ ที่เป็นส่วนหน้าของเว็บเบราว์เซอร์ คุณอยู่ในสภาพแวดล้อมแบบอะซิงโครนัส แร็ค #call มักจะถูกเรียกแบบอะซิงโครนัสเสมอ ดังนั้นไม่ว่าคุณจะรู้หรือไม่ก็ตาม มีโอกาสดีที่คุณจะใช้คอมโพเนนต์แบบมัลติเธรดอยู่แล้ว

การทดสอบ:ทริกเกอร์ รวบรวม และตรวจสอบ

การทดสอบ API การเรียกกลับแบบอะซิงโครนัสสามารถทำได้แบบซิงโครนัสโดยทำตามรูปแบบสามขั้นตอน ทริกเกอร์ , สะสม และ ตรวจสอบ . ลองนึกดูว่าแต่ละเธรดเป็นวัตถุและสิ่งของที่แยกจากกัน เช่นกัน สิ่งที่สามารถเป็นเจ้าของได้ทีละคนเท่านั้น

เราจะใช้ตัวอย่างของแบทแมนและชุดที่แตกต่างกัน 7 ชุดของเขา เพราะนั่นเป็นตัวอย่างที่ใช้งานได้จริง และเราสามารถเข้าใจถึงความสำคัญของการรู้ว่าชุดทั้งหมดอยู่กับอัลเฟรดที่จะล้างหรือไม่เมื่อคุณกำลังจะหมดและกอบกู้เมือง

ตัวอย่าง:วันซักผ้าที่ถ้ำค้างคาว

ตัวอย่างคืออัลเฟรดกำลังซักชุดแบทแมน SuitWashScheduler เป็นตัวกำหนดตารางเวลาที่เรียกใช้การโทรกลับสำหรับการซักแต่ละครั้ง ตัวกำหนดตารางเวลาจะทำการโทรกลับเจ็ดครั้งในช่วงเวลาหนึ่งวินาทีโดยเริ่มตั้งแต่หนึ่งวินาทีหลังจากเริ่มต้น ทริกเกอร์คือการสร้าง SuitWashScheduler .

class SuitWashScheduler
  def initialize(cnt)
    Thread.new {
      cnt.times {
        sleep(1.0)
        yield
      }
    }
  end
end

การสะสม

การรวบรวมผลจะต้องเป็นเธรดที่ปลอดภัยเพื่อหลีกเลี่ยงสภาวะการแข่งขัน อ็อบเจ็กต์ใดๆ ที่แชร์ข้ามเธรดมากกว่าหนึ่งรายการจะต้องได้รับการปกป้อง การป้องกันเป็นวิธีการติดตามเจ้าของวัตถุ มีเพียงเจ้าของเท่านั้นที่สามารถเปลี่ยนแปลงหรือดูวัตถุได้ ชุดสูทสามารถใช้ได้เฉพาะกับแบทแมนเพื่อใช้ในการต่อสู้ หรือให้ล้างด้วยอัลเฟรด

เพื่อรักษาเธรดที่เป็นมิตร (ในอุปมาแบทแมนหรืออัลเฟรด) จะเป็นเจ้าของเพียงช่วงเวลาสั้น ๆ แล้วละทิ้งความเป็นเจ้าของ A Mutex มักใช้เพื่อติดตามเจ้าของ SuitwashScheduler การโทรกลับจะเป็นเจ้าของตัวนับผลลัพธ์เมื่อตัวนับเพิ่มขึ้น การเรียกกลับที่ทำงานใน SuitWashScheduler เธรดส่งสัญญาณว่าได้รับผลลัพธ์ทั้งหมดแล้วเมื่อตัวนับกระทบกับเป้าหมาย

การเขียนตัวอย่างเริ่มต้นด้วยการตั้งค่าโกลบอลบางส่วน ในการใช้งานจริง globals จะถูกแทนที่ด้วย class หรือ object แอตทริบิวต์

$main_thread = Thread.current
$mu = Mutex.new
$count = 0
$target = 7

ผู้บริหารและเจ้าของ

$main_thread และ $mu ใช้เพื่อจัดการเธรดและรอให้การทดสอบเสร็จสิ้นในขณะที่ $target และ $count ติดตามผลการทดสอบ จำไว้ว่านี่เป็นการทดสอบเล็กน้อย ดังนั้นการรวบรวมและตรวจสอบผลลัพธ์จะต้องง่าย

การทดสอบเริ่มต้นด้วยการสร้างอินสแตนซ์ใหม่ของ SuitWashScheduler ให้ตัวเริ่มต้น $target จำนวนการทำซ้ำ ในกรณีนี้ ชุด 7 ชุดที่ต้องซัก บล็อกที่ให้มาจะทำงานใน SuitWashScheduler เกลียว. สำหรับการทำซ้ำแต่ละครั้ง $count เพิ่มขึ้นและพิมพ์

เมื่อมองไปข้างหน้า เราตระหนักดีว่าเธรดทดสอบหลักกำลังตรวจสอบ$count ซึ่งหมายความว่าจะต้องเป็นเจ้าของ $count เช่นเดียวกับวิธีการเป็นเจ้าของ $count มันจำเป็น. $mu Mutex อินสแตนซ์คือโทเค็นการเป็นเจ้าของ ในบล็อกที่ส่งผ่านไปยัง SuitWashScheduler.new เรียก $mu.synchronize block ใช้เวลาความเป็นเจ้าของนานพอที่จะตั้งค่า $count และตรวจสอบผลลัพธ์ ตรวจสอบผลลัพธ์เพิ่มเติมในอีกสักครู่

SuitWashScheduler.new($target) {
  $mu.synchronize {
    $count += 1
    puts $count
    $main_thread.wakeup if $target <= $count
  }
}

ตรวจสอบ:ครบชุดหรือยัง

กลับมาที่หัวข้อหลัก เราต้องรอให้การทดสอบเสร็จสิ้น แบทแมนต้องรอก่อนครบทั้ง 7 ชุด มีสองเงื่อนไขในการตรวจสอบ; การทดสอบจะอัปเดต $count ตามที่คาดไว้ มิฉะนั้น Batman จะเบื่อกับการรอการทดสอบให้เสร็จและหมดเวลา ก่อนตรวจสอบ $count เพื่อดูว่ามันถึง $target . หรือไม่ , ความเป็นเจ้าของ $count มันจำเป็น. เช่นเดียวกับในบล็อกสำหรับ SuitWashScheduler เรียก $mu.synchronize ถูกนำมาใช้

แต่มันไม่ถูกต้อง ถ้าเราล็อกเธรดหลัก SuitWashScheduler จะทำได้อย่างไร เธรดเคยเปลี่ยน $count ? โชคดีสำหรับเราที่มีเคล็ดลับดีๆ ที่ดูแลเรื่องนี้ Mutex คลาสมี #sleep วิธีที่สละสิทธิ์ความเป็นเจ้าของและรอจนกว่าจะหมดเวลาหรือถูกปลุก เมื่อถูกปลุกผ่านระยะหมดเวลาหรือ #wakeup โทรไปที่กระทู้หลัก $mu พยายามเข้าครอบครองอีกครั้งก่อนดำเนินการต่อ เมื่อบรรลุความเป็นเจ้าของแล้ว สามารถตรวจสอบผลลัพธ์และกำหนดสถานะผ่านหรือไม่ผ่านของการทดสอบได้

$mu.synchronize {
  $mu.sleep($target + 1)
  if $target != $count
    puts 'FAILED'
  else
    puts 'Passed! All suits are washed and clean'
  end
}

หากคุณต้องการเจาะลึกในเรื่องนี้ คุณสามารถทำให้ตัวอย่างน่าสนใจขึ้นอีกเล็กน้อยโดยพยายามสร้างตัวกำหนดตารางเวลาหลายตัวและดูว่า Mutex รักษา $count ได้อย่างไร เปลี่ยนจากการชนกัน ราวกับว่าแบทแมนส่งชุดสูทไปให้อัลเฟรดเพื่อซักเสื้อผ้า และชุดอื่นๆ ให้กับร้านซักแห้ง อย่าลืมเปลี่ยนตรรกะเพื่อให้แน่ใจว่า $target เช็คคือผลรวมของผลตอบแทนที่คาดหวังทั้งหมด

บทสรุป

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

เราหวังว่าตัวอย่างจะทำให้คุณจำกลไกของ async ได้ แต่เราหวังว่าภาพ Batman ที่กำลังจะหมดลงในขณะที่กำลังซักชุดของเขาจะไม่ติดอยู่กับคุณนานเกินไป

ป.ล. หากคุณใช้คำอุปมาเรื่องแบทแมนในบล็อกเสร็จแล้ว โปรดแจ้งให้เราทราบ