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

วิธีใช้ Ruby Threads:บทช่วยสอนที่เข้าใจง่าย

เธรดใน Ruby คืออะไร

เธรดทำให้โปรแกรม Ruby ของคุณทำหลายๆ อย่างพร้อมกันได้

สิ่งที่ชอบ :

  • การอ่านหลายไฟล์
  • การจัดการคำขอเว็บหลายรายการ
  • สร้างการเชื่อมต่อ API หลายรายการ

จากการใช้เธรด คุณจะมีโปรแกรม Ruby แบบมัลติเธรด ซึ่งสามารถทำงานให้เสร็จเร็วขึ้น

แต่คำเตือน…

ใน MRI (Ruby Interpreter ของ Matz) วิธีเริ่มต้นในการเรียกใช้แอปพลิเคชัน Ruby คุณจะได้รับประโยชน์จากเธรดเมื่อเรียกใช้ แอปพลิเคชันที่ผูกกับ i/o .

ข้อจำกัดนี้มีอยู่เนื่องจาก GIL (Global Interpreter Lock) .

ล่าม Ruby ทางเลือก เช่น Jruby หรือ Rubinius ใช้ประโยชน์จากมัลติเธรดอย่างเต็มที่

วิธีใช้ Ruby Threads:บทช่วยสอนที่เข้าใจง่าย

แล้วเธรดคืออะไร

เธรดเป็นผู้ปฏิบัติงานหรือหน่วยของการดำเนินการ

ทุกกระบวนการมีอย่างน้อยหนึ่งเธรด และคุณสามารถสร้างเพิ่มเติมได้ตามต้องการ

ฉันรู้ว่าคุณต้องการดูตัวอย่างโค้ด

แต่ก่อนอื่น เราต้องพูดถึงความแตกต่างระหว่างแอปพลิเคชันที่ผูกกับ CPU และแอปพลิเคชันที่ผูกไว้กับ I/O

แอปพลิเคชันที่ถูกผูกไว้กับ I/O

แอปที่ผูกกับ i/o เป็นสิ่งที่ต้องรอทรัพยากรภายนอก:

  • คำขอ API
  • ฐานข้อมูล (ผลการสืบค้น)
  • อ่านดิสก์

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

ตัวอย่างหนึ่งของ แอปที่ผูกกับ i/o เป็นโปรแกรมรวบรวมข้อมูลเว็บ

สำหรับทุกคำขอ โปรแกรมรวบรวมข้อมูลต้องรอให้เซิร์ฟเวอร์ตอบสนอง และไม่สามารถทำอะไรได้ในขณะที่รอ

แต่ถ้าคุณใช้เธรด…

คุณสามารถส่งคำขอได้ครั้งละ 4 รายการ &จัดการการตอบกลับเมื่อพวกเขากลับมา ซึ่งจะช่วยให้คุณดึงหน้าได้เร็วขึ้น

ถึงเวลาสำหรับตัวอย่างโค้ดของคุณแล้ว

การสร้างเธรดทับทิม

คุณสามารถสร้างเธรด Ruby ใหม่ได้โดยเรียก Thread.new .

ตรวจสอบให้แน่ใจว่าได้ส่งผ่านบล็อกด้วยรหัสที่เธรดนี้จำเป็นต้องใช้

Thread.new { puts "hello from thread" }

ค่อนข้างง่ายใช่มั้ย

อย่างไรก็ตาม

หากคุณมีรหัสต่อไปนี้ คุณจะสังเกตเห็นว่าไม่มีผลลัพธ์จากเธรด:

t = Thread.new { puts 10**10 }
puts "hello"

ปัญหาคือ Ruby ไม่รอให้เธรดเสร็จ

คุณต้องเรียก join วิธีการในเธรดของคุณเพื่อแก้ไขโค้ดด้านบน:

t = Thread.new { puts 10**10 }
puts "hello"
t.join

หากคุณต้องการสร้างหลายเธรด คุณสามารถใส่มันในอาร์เรย์ &โทร join ในทุกกระทู้

ตัวอย่าง :

threads = []

10.times {
  threads << Thread.new { puts 1 }
}

threads.each(&:join)

ในระหว่างการสำรวจเธรด Ruby คุณอาจพบว่าเอกสารมีประโยชน์:

https://ruby-doc.org/core-2.5.0/Thread.html

กระทู้และข้อยกเว้น

หากเกิดข้อยกเว้นภายในเธรด เธรดนั้นจะตายอย่างเงียบ ๆ โดยไม่หยุดโปรแกรมของคุณหรือแสดงข้อความแสดงข้อผิดพลาดใดๆ

นี่คือตัวอย่าง:

Thread.new { raise 'hell' }

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

Thread.abort_on_exception = true

อย่าลืมตั้งค่าสถานะนี้ก่อนที่คุณจะสร้างเธรดของคุณ 🙂

พูลเธรด

สมมติว่าคุณมีรายการที่ต้องดำเนินการหลายร้อยรายการ การเริ่มเธรดสำหรับแต่ละรายการจะทำลายทรัพยากรระบบของคุณ

มันจะมีลักษณะดังนี้:

pages_to_crawl = %w( index about contact ... )

pages_to_crawl.each do |page|
  Thread.new { puts page }
end

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

ทางออกหนึ่งคือการใช้เธรดพูล

กลุ่มเธรดช่วยให้คุณควบคุมจำนวนเธรดที่ใช้งานอยู่ได้ตลอดเวลา

คุณสามารถสร้างสระของคุณเองได้ แต่ฉันไม่แนะนำ ในตัวอย่างต่อไปนี้ เรากำลังใช้เซลลูลอยด์เจมทำสิ่งนี้ให้คุณ

หมายเหตุ:ขณะนี้ Celluloid ไม่ได้รับการบำรุงรักษา แต่แนวคิดทั่วไปของพูลผู้ปฏิบัติงานยังคงมีผลบังคับใช้

require 'celluloid'

class Worker
  include Celluloid

  def process_page(url)
    puts url
  end
end

pages_to_crawl = %w( index about contact products ... )
worker_pool    = Worker.pool(size: 5)

# If you need to collect the return values check out 'futures'
pages_to_crawl.each do |page|
   worker_pool.process_page(page)
end

คราวนี้จะทำงานเพียง 5 เธรด และเมื่อเสร็จสิ้น พวกเขาจะเลือกรายการถัดไป

สภาพการแข่งขันและอันตรายอื่นๆ

ทั้งหมดนี้อาจฟังดูเจ๋งมาก แต่ก่อนที่คุณจะโรยเธรดให้ทั่วโค้ด คุณต้องรู้ว่ามีปัญหาบางอย่างที่เกี่ยวข้องกับโค้ดที่เกิดขึ้นพร้อมกัน

ตัวอย่างเช่น เธรดมีแนวโน้มที่จะเกิดสภาวะการแข่งขัน

สภาพการแข่งขัน คือเมื่อสิ่งต่าง ๆ เกิดขึ้นไม่เป็นระเบียบและยุ่งเหยิง

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

เพื่อหลีกเลี่ยงปัญหาเหล่านี้ วิธีที่ดีที่สุดคือหลีกเลี่ยงเธรดดิบและยึดติดกับอัญมณีที่ดูแลรายละเอียดให้คุณอยู่แล้ว

อัญมณีเกลียวเพิ่มเติม

เราใช้เซลลูลอยด์สำหรับเธรดพูลของเราแล้ว แต่มีอัญมณีที่เน้นการทำงานพร้อมกันอีกมากมายที่คุณควรตรวจสอบ:

  • https://github.com/grosser/parallel
  • https://github.com/chadrem/workers
  • https://github.com/ruby-concurrency/concurrent-ruby

เรียบร้อย หวังว่าคุณจะได้เรียนรู้สิ่งหนึ่งหรือสองอย่างเกี่ยวกับกระทู้ทับทิม !

หากคุณพบว่าบทความนี้มีประโยชน์ โปรด แชร์ กับเพื่อน ๆ ของคุณเพื่อให้พวกเขาได้เรียนรู้เช่นกัน 🙂