โครงสร้างข้อมูลหลักไม่มีเธรดที่ปลอดภัย สิ่งเดียวที่ฉันรู้จักที่มาพร้อมกับ Ruby คือการนำคิวไปใช้ในไลบรารีมาตรฐาน ( require 'thread'; q = Queue.new
)
GIL ของ MRI ไม่ได้ช่วยเราจากปัญหาด้านความปลอดภัยของด้าย ตรวจสอบให้แน่ใจว่าเธรดสองเธรดไม่สามารถรันโค้ด Ruby พร้อมกันได้นั่นคือบน CPU สองตัวในเวลาเดียวกัน เธรดยังคงสามารถหยุดชั่วคราวและเปิดต่อได้ทุกเมื่อในโค้ดของคุณ หากคุณเขียนโค้ด@n = 0; 3.times { Thread.start { 100.times { @n += 1 } } }
เช่นการกลายพันธุ์ตัวแปรที่ใช้ร่วมกันจากหลายเธรดค่าของตัวแปรที่ใช้ร่วมกันหลังจากนั้นจะไม่ถูกกำหนด GIL เป็นแบบจำลองของระบบแกนเดียวไม่มากก็น้อยจะไม่เปลี่ยนประเด็นพื้นฐานของการเขียนโปรแกรมพร้อมกันที่ถูกต้อง
แม้ว่า MRI จะเป็นเธรดเดียวเช่น Node.js คุณก็ยังต้องคิดถึงการทำงานพร้อมกัน ตัวอย่างที่มีตัวแปรที่เพิ่มขึ้นจะใช้งานได้ดี แต่คุณยังสามารถรับเงื่อนไขการแข่งขันที่สิ่งต่าง ๆ เกิดขึ้นในลำดับที่ไม่ได้กำหนดและหนึ่งกลุ่มการเรียกกลับเป็นผลลัพธ์ของอีกตัวแปรหนึ่ง ระบบอะซิงโครนัสแบบเธรดเดี่ยวนั้นง่ายกว่าในการให้เหตุผล แต่ก็ไม่ปลอดจากปัญหาการทำงานพร้อมกัน ลองนึกถึงแอปพลิเคชันที่มีผู้ใช้หลายคน: หากผู้ใช้สองคนกดแก้ไขโพสต์ Stack Overflow ในเวลาเดียวกันไม่มากก็น้อยให้ใช้เวลาแก้ไขโพสต์แล้วกดบันทึกซึ่งผู้ใช้คนที่สามจะเห็นการเปลี่ยนแปลงในภายหลังเมื่อ อ่านโพสต์เดียวกันไหม
ใน Ruby เช่นเดียวกับการทำงานพร้อมกันอื่น ๆ ส่วนใหญ่สิ่งที่มากกว่าหนึ่งการดำเนินการจะไม่ปลอดภัยต่อเธรด @n += 1
เธรดไม่ปลอดภัยเนื่องจากเป็นการดำเนินการหลายอย่าง @n = 1
เธรดปลอดภัยเนื่องจากเป็นการดำเนินการเดียว (เป็นการดำเนินการจำนวนมากภายใต้ประทุนและฉันอาจประสบปัญหาหากพยายามอธิบายว่าทำไมเธรดจึง "ปลอดภัย" โดยละเอียด แต่สุดท้ายคุณจะไม่ได้รับผลลัพธ์ที่ไม่สอดคล้องกันจากการมอบหมายงาน ). @n ||= 1
ไม่ใช่และไม่มีการดำเนินการชวเลขอื่น ๆ + การมอบหมาย ความผิดพลาดอย่างหนึ่งที่ฉันเคยทำหลายครั้งคือการเขียนreturn unless @started; @started = true
ซึ่งไม่ปลอดภัยเลย
ฉันไม่ทราบรายการข้อความที่เชื่อถือได้ของเธรดที่ปลอดภัยและไม่ใช่เธรดที่ปลอดภัยสำหรับ Ruby แต่มีกฎง่ายๆคือหากนิพจน์ทำการดำเนินการเพียงอย่างเดียว (ไม่มีผลข้างเคียง) แสดงว่าเธรดปลอดภัย ตัวอย่างเช่นa + b
มีการตกลงa = b
เป็นยัง ok และa.foo(b)
จะ ok ถ้าวิธีการที่foo
เป็นผลข้างเคียงฟรี (ตั้งแต่เพียงเกี่ยวกับอะไรในรูบีเป็นวิธีการเรียกแม้งานในหลาย ๆ กรณีนี้ไปสำหรับตัวอย่างอื่น ๆ ด้วย) ผลข้างเคียงในบริบทนี้หมายถึงสิ่งที่เปลี่ยนสถานะ def foo(x); @x = x; end
คือไม่ได้ผลข้างเคียงฟรี
สิ่งที่ยากที่สุดอย่างหนึ่งเกี่ยวกับการเขียนโค้ดที่ปลอดภัยของเธรดใน Ruby คือโครงสร้างข้อมูลหลักทั้งหมดรวมถึงอาร์เรย์แฮชและสตริงนั้นไม่แน่นอน เป็นเรื่องง่ายมากที่จะทำให้ชิ้นส่วนของสถานะของคุณรั่วไหลโดยไม่ตั้งใจและเมื่อชิ้นส่วนนั้นเป็นสิ่งที่ไม่แน่นอนอาจทำให้เสียหายได้ พิจารณารหัสต่อไปนี้:
class Thing
attr_reader :stuff
def initialize(initial_stuff)
@stuff = initial_stuff
@state_lock = Mutex.new
end
def add(item)
@state_lock.synchronize do
@stuff << item
end
end
end
อินสแตนซ์ของคลาสนี้สามารถใช้ร่วมกันระหว่างหัวข้อและพวกเขาได้อย่างปลอดภัยสามารถเพิ่มสิ่งที่มัน แต่มีข้อผิดพลาดการทำงานพร้อมกัน (มันไม่ได้เป็นเพียงคนเดียว): รัฐภายในของการรั่วไหลของวัตถุที่ผ่านstuff
การเข้าถึง นอกจากจะมีปัญหาจากมุมมองของการห่อหุ้มแล้วมันยังเปิดโอกาสของเวิร์มที่เกิดขึ้นพร้อมกัน อาจมีใครบางคนใช้อาร์เรย์นั้นและส่งต่อไปยังที่อื่นและรหัสนั้นก็คิดว่าตอนนี้เป็นเจ้าของอาร์เรย์นั้นและสามารถทำอะไรก็ได้ที่ต้องการ
อีกตัวอย่างคลาสสิกของ Ruby คือ:
STANDARD_OPTIONS = {:color => 'red', :count => 10}
def find_stuff
@some_service.load_things('stuff', STANDARD_OPTIONS)
end
find_stuff
ใช้งานได้ดีในครั้งแรกที่ใช้ แต่ส่งคืนอย่างอื่นในครั้งที่สอง ทำไม? วิธีการที่เกิดขึ้นจะคิดว่ามันเป็นเจ้าของกัญชาตัวเลือกผ่านไปและไม่load_things
color = options.delete(:color)
ตอนนี้STANDARD_OPTIONS
ค่าคงที่ไม่มีค่าเหมือนเดิมอีกต่อไป ค่าคงที่เป็นค่าคงที่ในสิ่งที่อ้างอิงเท่านั้นไม่รับประกันความคงที่ของโครงสร้างข้อมูลที่อ้างถึง แค่คิดว่าจะเกิดอะไรขึ้นถ้าโค้ดนี้ทำงานพร้อมกัน
หากคุณหลีกเลี่ยงสภาวะที่ไม่สามารถใช้ร่วมกันได้ (เช่นตัวแปรอินสแตนซ์ในออบเจ็กต์ที่เข้าถึงโดยหลายเธรดโครงสร้างข้อมูลเช่นแฮชและอาร์เรย์ที่เข้าถึงโดยเธรดหลายเธรด) ความปลอดภัยของเธรดนั้นไม่ยากนัก พยายามย่อส่วนของแอปพลิเคชันของคุณที่เข้าถึงพร้อมกันและมุ่งเน้นความพยายามของคุณที่นั่น IIRC ในแอ็พพลิเคชัน Rails อ็อบเจ็กต์คอนโทรลเลอร์ใหม่จะถูกสร้างขึ้นสำหรับทุกคำร้องขอดังนั้นมันจะถูกใช้โดยเธรดเดียวเท่านั้นและสิ่งเดียวกันนี้จะใช้กับอ็อบเจ็กต์โมเดลใด ๆ ที่คุณสร้างจากคอนโทรลเลอร์นั้น อย่างไรก็ตาม Rails ยังสนับสนุนให้ใช้ตัวแปรส่วนกลาง ( User.find(...)
ใช้ตัวแปร globalUser
คุณอาจคิดว่ามันเป็นเพียงคลาสและเป็นคลาส แต่ก็เป็นเนมสเปซสำหรับตัวแปรส่วนกลางเช่นกัน) บางส่วนมีความปลอดภัยเนื่องจากเป็นแบบอ่านอย่างเดียว แต่บางครั้งคุณบันทึกสิ่งต่างๆไว้ในตัวแปรส่วนกลางเหล่านี้เนื่องจาก สะดวก ระมัดระวังอย่างยิ่งเมื่อคุณใช้สิ่งที่สามารถเข้าถึงได้ทั่วโลก
เป็นไปได้ที่จะเรียกใช้ Rails ในสภาพแวดล้อมแบบเธรดมาระยะหนึ่งแล้วดังนั้นหากไม่เป็นผู้เชี่ยวชาญด้าน Rails ฉันก็ยังคงพูดได้ว่าคุณไม่ต้องกังวลเกี่ยวกับความปลอดภัยของเธรดเมื่อพูดถึง Rails เอง คุณยังคงสามารถสร้างแอปพลิเคชัน Rails ที่ไม่ปลอดภัยสำหรับเธรดได้โดยทำบางสิ่งที่ฉันกล่าวถึงข้างต้น เมื่อพูดถึงอัญมณีอื่น ๆ จะคิดว่าพวกเขาไม่ปลอดภัยเว้นแต่พวกเขาจะบอกว่าพวกเขาเป็นและถ้าพวกเขาบอกว่าพวกเขาคิดว่าพวกเขาไม่ใช่และมองผ่านรหัสของพวกเขา (แต่เพียงเพราะคุณเห็นว่าพวกเขาไปในสิ่งต่างๆเช่น@n ||= 1
ไม่ได้หมายความว่าพวกมันไม่ปลอดภัยเธรดนั่นเป็นสิ่งที่ถูกต้องตามกฎหมายอย่างสมบูรณ์ที่จะทำในบริบทที่ถูกต้อง - คุณควรมองหาสิ่งต่างๆเช่นสถานะที่ไม่แน่นอนในตัวแปรส่วนกลางแทนวิธีจัดการกับวัตถุที่เปลี่ยนแปลงไม่ได้ที่ส่งผ่านไปยังวิธีการของมันและโดยเฉพาะอย่างยิ่งวิธีการนั้น จัดการแฮชตัวเลือก)
สุดท้ายการไม่ปลอดภัยของเธรดเป็นคุณสมบัติสกรรมกริยา สิ่งใดก็ตามที่ใช้สิ่งที่ไม่ปลอดภัยต่อเธรดคือตัวมันเองไม่ปลอดภัยต่อเธรด