Threads ในภาษา Ruby
ในบทนี้ คุณจะได้เรียนรู้เกี่ยวกับ Thread ในภาษา Ruby เราจะแนะนำให้คุณรู้จักกับ Thread และการนำ Thread มาใช้งานในการเขียนโปรแกรม นอกจากนี้ เรายังจะพูดถึงการเขียนโปรแกรมแบบ Multi-Threaded ซึ่งเป็นวิธีการเขียนโปรแกรมที่ใช้ประโยชน์จาก Thread ซึ่งจะทำให้คอมพิวเตอร์สามารถทำงานหลายอย่างพร้อมกันได้ และนี่เป็นเนื้อหาที่เราจะพูดในบทนี้
- การสร้าง Thread ในภาษา Ruby
- Thread Joining
- Multi-Threaded Programming
- Thread Synchronization
- การสร้างคลาส Thread
Thread คืออะไร
Thread (เทร็ด) คือลำดับการทำงานของชุดคำสั่งโปรแกรมที่เล็กที่สุดซึ่งโดยทั่วไปแล้วจะอยู่ภายใน Process โดยที่ในหนึ่ง Process นั้นจะสามารถมีได้หลาย Thread การใช้งาน Thread จะทำให้เราสามารถเขียนโปรแกรมที่ทำงานแบบคู่ขนานและใช้ทรัพยากรบางอย่างร่วมกัน ยกตัวอย่างเช่น หน่วยความจำ เมื่อมีหลาย Thread ทำงานพร้อมกัน เราจะเรียกการเขียนโปรแกรมในรูปแบบนี้ว่า Multi-Threaded
รูปภาพแสดงการทำงานของ Thread และ Process
โดยทั่วไปแล้วโปรแกรมในภาษา Ruby ที่เราเขียนขึ้นจะรันอยู่ภายใน Thread เดียวที่เรียกว่า Thread หลัก ในภาษา Ruby เราสามารถสร้าง Thread ใหม่ที่รันภายใต้ Thread หลักได้โดยการใช้คลาส Thread
มันเป็นคลาสที่ทำให้เราสามารถใช้ประโยชน์จาก Thread และทำให้โปรแกรมของเราสามารถทำงานคู่ขนานกันไปได้
การสร้าง Thread ในภาษา Ruby
ต่อไปเรามาเริ่มสร้าง Thread กัน ในภาษา Ruby คุณสามารถสร้าง Thread ได้จากคลาส Thread
คลาสนี้จะทำให้เราสามารถเขียนโปรแกรมที่ทำงานในรูปแบบขนานกันได้ มันมีเมธอดจำนวนมากสำหรับควบคุมการทำงานของ Thread แต่สำหรับตอนนี้ เราจะมาสร้าง Thread อย่างง่ายกันก่อน
thr1 = Thread.new {
puts "Hello from Thread"
}
thr2 = Thread.new("Mateo") { |name|
puts "Hello #{name} from Thread"
}
puts "Main Thread"
thr1.join
thr2.join
ในตัวอย่าง เราได้สร้างสอง Thread ย่อยขึ้นมาจาก Thread หลัก (Main Thread) ของเราจากการใช้งานคลาส Thread
เราจำเป็นต้องส่งบล็อคเข้าไปยังเมธอด new
ซึ่งเป็นคอนสตรัคเตอร์ของคลาส แล้วโค้ดที่เรากำหนดภายในบล็อคจะถูกรันภายใน Thread และในตัวแปร thr2
นั้นเราสามารถส่งค่าจากภายนอกเข้าไปใช้ใน Thread ได้ผ่านอาร์กิวเมนต์ของเมธอด new
ซึ่งค่าที่ส่งไปนั้นจะถูกส่งผ่านไปยังอาร์กิวเมนต์ของบล็อค
หรือคุณอาจจะต้องการส่งหลายค่าเข้าไปยัง Thread โดยการเรียกใช้งานเมธอด new
เหมือนกับในรูปแบบด้านล่างนี้
Thread.new(val1, val2, ...) { |arg1, arg2, ...|
...
}
ทันทีที่เราสร้าง Thread มันจะเริ่มทำงานในทันที และในภาษา Ruby อย่างหนึ่งเกี่ยวกับ Thread ที่คุณจะต้องจำไว้ก็คือ Thread หลักนั้นจะไม่รอให้ Thread ย่อยทำงานให้เสร็จก่อน และเนื่องจากทุก Thread นั้นทำงานไปพร้อมกัน ดังนั้นในบางครั้ง Thread หลักอาจจะทำงานเสร็จก่อน และเมื่อ Thread หลักทำงานเสร็จ มันจะส่งผลให้ Thread ย่อยหยุดทำงานในทันที
thr1.join
thr2.join
ดังนั้น เราสามารถเรียกใช้งานเมธอด join
เพื่อรอให้ Thread ย่อยทำงานให้เสร็จก่อนได้ ซึ่งเมื่อ Ruby พบกับการเรียกใช้งานเมธอดนี้ มันจะรอให้ Thread ทำงานเสร็จก่อน ก่อนที่จะทำงานในบรรทัดต่อไป
Main Thread
Hello from Thread
Hello Mateo from Thread
นี่เป็นผลลัพธ์การทำงานของโปรแกรม สังเกตว่าข้อความใน Thread หลักนั้นแสดงขึ้นมาก่อน นั่นเป็นเพราะว่าทุก Thread ทำงานไปพร้อมกัน และมันมีช่วงเวลาที่ออบเจ็คของ Thread ย่อยจะถูกสร้างและทำงาน จึงทำให้การแสดงผลภายใน Thread ย่อยปรากฏทีหลังนั่นเอง
Thread Joining
ในภาษา Ruby เราสามารถรวม Thread ได้ด้วยเมธอด join
การรวม Thread จะบล็อคการทำงานใน Thread หลักและรอจนกว่า Thread ย่อยจะทำงานเสร็จ ก่อนที่โปรแกรมจะทำงานในบรรทัดต่อไปได้ นั่นทำให้การทำงานของโปรแกรมเป็นแบบตามลำดับนั่นเอง การรวม Thread นั้นมีประโยชน์ในการกรณีที่เราต้องการทำงานอย่างแรกก่อน และทำบางอย่างในทีหลัง มาดูตัวอย่าง
thr1 = Thread.new {
for i in (1..10)
puts "Thread 1: #{i}"
sleep(0.1)
end
}
thr2 = Thread.new {
for i in (1..10)
puts "Thread 2: #{i}"
sleep(0.1)
end
}
thr1.join
thr2.join
thr3 = Thread.new {
for i in (1..10)
puts "Thread 3: #{i}"
end
}
thr3.join
puts "Main Thread"
ในตัวอย่าง เป็นโปรแกรมสำหรับแสดงตัวเลข 1 - 10 ออกทางหน้าจอ เราได้แสดงผลตัวเลขพร้อมกับชื่อของ Thread เพื่อทำให้คุณสามารถเห็นภาพได้ชัดเจนขึ้น
thr1 = Thread.new {
for i in (1..10)
puts "Thread 1: #{i}"
sleep(0.1)
end
}
thr2 = Thread.new {
for i in (1..10)
puts "Thread 2: #{i}"
sleep(0.1)
end
}
ในตอนแรกของโปรแกรม เราได้สร้าง Thread ขึ้นมาสอง Thread ในตัวแปร thr1
และ thr2
ทั้งสอง Thread นี้ทำงานเหมือนกันคือแสดงตัวเลขจาก 1 - 10 ออกทางหน้าจอ ในการแสดงตัวเลขแต่รอบในคำสั่ง for loop เราได้ใช้เมธอด sleep
เพื่อหน่วงการทำงานให้ช้าลงเป็นเวลา 0.1 วินาที นั่นเป็นเพราะว่าคอมพิวเตอร์ทำงานเร็วมาก เราทำเช่นนี้เพื่อให้คุณเห็นว่าทั้งสอง Thread นั้นทำงานไปพร้อมๆ กัน
thr1.join
thr2.join
หลังจากนั้นเราเรียกใช้เมธอด join
จากออบเจ็คของ Thread ทั้งสอง นี่หมายความว่าเราต้องการรอให้ Thread ทั้งสองทำงานเสร็จก่อน ก่อนที่จะทำงานในบรรทัดต่อไป
thr3 = Thread.new {
for i in (1..10)
puts "Thread 3: #{i}"
end
}
thr3.join
นั่นหมายความว่าใน Thread ที่สามจะเริ่มทำงานก็ต่อเมื่อสอง Thread แรกทำงานเสร็จแล้ว ใน Thread สุดท้ายนี้เราจะยังคงเรียกใช้เมธอด join
เพื่อเป็นการป้องกันไม่ให้ Thread หลักทำงานเสร็จก่อน เพราะถ้านั่นเกิดขึ้น จะส่งผลให้ถ้า Thread นี้ทำงานยังไม่เสร็จมันจะหยุดการทำงานในทันที
puts "Main Thread"
สุดท้ายเป็นคำสั่งที่ทำงานใน Thread หลัก คำสั่งนี้จะทำงานท้ายสุดหลังจากที่ทุก Thread ย่อยทำงานเสร็จหมดแล้ว นั่นเป็นเพราะการใช้งานเมธอด join
เพื่อรอให้ Thread อื่นๆ ทำงานให้เสร็จก่อนนั่นเอง
Thread 1: 1
Thread 2: 1
Thread 1: 2
Thread 2: 2
Thread 2: 3
Thread 1: 3
Thread 2: 4
Thread 1: 4
Thread 2: 5
Thread 1: 5
Thread 2: 6
Thread 1: 6
...
นี่เป็นผลลัพธ์การทำงานของโปรแกรม โปรแกรมเริ่มทำงานจากสอง Thread แรกก่อน หลังจากนั้นทำงาน Thread ที่สาม และสุดท้ายทำงานในคำสั่งของ Thread หลัก
Multi-Threaded Programming
การเขียนโปรแกรมแบบ Multi-Threaded คือการใช้ประโยชน์จากการทำงานพร้อมกันของ Thread เพื่อแบ่งงานออกเป็นส่วนย่อยๆ เพื่อให้แต่ละ Thread ช่วยกันทำงาน นั่นจะส่งผลให้โปรแกรมของเราสามารถทำงานได้รวดเร็วขึ้น
ตัวอย่างต่อมาจะเป็นการใช้ประโยชน์จากเขียนโปรแกรมแบบ Multi-Threaded เราจะเขียนโปรแกรมสำหรับคำนวณเกรดและนับเกรดของนักเรียนจำนวน 40 คน เนื่องจากการคำนวณเกรดของนักเรียนแต่ละคนนั้นไม่ขึ้นต่อกัน ดังนั้นเราสามารถใช้ Thread เพื่อแบ่งการทำงานนี้ได้ นี่เป็นโค้ดของโปรแกรม
def cal_grade(score)
if score >= 80
"A"
elsif score >= 70
"B"
elsif score >= 60
"C"
elsif score >= 50
"D"
else
"F"
end
end
score1 = [54, 84, 6, 72, 100, 35, 92, 15, 73, 21, 43, 72, 4, 4, 100, 29, 49, 25, 61, 75]
score2 = [31, 39, 83, 42, 18, 20, 2, 47, 6, 34, 83, 98, 36, 7, 100, 12, 87, 69, 92, 42]
thr1 = Thread.new(score1) { |scores|
hash = {}
scores.each { |item|
grade = cal_grade(item)
if hash.key?(grade)
hash[grade] = hash[grade] + 1
else
hash[grade] = 1
end
}
hash
}
thr2 = Thread.new(score2) { |scores|
hash = {}
scores.each { |item|
grade = cal_grade(item)
if hash.key?(grade)
hash[grade] = hash[grade] + 1
else
hash[grade] = 1
end
}
hash
}
thr1.join
thr2.join
hash1 = thr1.value
hash2 = thr2.value
puts "Data computed from each threads"
p hash1
p hash2
sum_hash = {}
["A", "B", "C", "D", "F"].each { |key|
sum_hash[key] = hash1.fetch(key, 0) + hash2.fetch(key, 0)
}
puts "Studies result of 40 students"
sum_hash.each_pair { |key, value|
puts "Grade #{key}: #{value} students"
}
ในตัวอย่าง เป็นโปรแกรมสำหรับคำนวณเกรดและนับว่าจากนักเรียนจำนวน 40 คนนั้นแบ่งออกเป็นเกรดต่างๆ จำนวนกี่คน นั่นหมายความว่าเราต้องหาว่านักเรียนแต่ละคนได้เกรดอะไรจากคะแนนของพวกเขา หลังจากนั้นก็นับจำนวนเกรดทั้งหมดที่ได้ว่ามีเกรดละกี่คน
def cal_grade(score)
if score >= 80
"A"
elsif score >= 70
"B"
elsif score >= 60
"C"
elsif score >= 50
"D"
else
"F"
end
end
ในตอนแรกของโปรแกรม เราได้สร้างเมธอด cal_grade
สำหรับคำนวณเกรดจากคะแนนที่รับเข้ามา โดยมีเงื่อนไขว่า ถ้าคะแนนมากกว่าหรือเท่ากับ 80 จะได้เกรด A ถ้าคะแนนมากกว่าหรือเท่ากับ 70 จะได้เกรด B ถ้าคะแนนมากกว่าหรือเท่ากับ 60 จะได้เกรด C ถ้าคะแนนมากกว่าหรือเท่ากับ 50 จะได้เกรด D และถ้าหากต่ำกว่า 50 จะได้เกรด F
score1 = [54, 84, 6, 72, 100, 35, 92, 15, 73, 21, 43, 72, 4, 4, 100, 29, 49, 25, 61, 75]
score2 = [31, 39, 83, 42, 18, 20, 2, 47, 6, 34, 83, 98, 36, 7, 100, 12, 87, 69, 92, 42]
นี่เป็นคะแนนของนักเรียนทั้งหมดที่เรามี เราได้แบ่งออกคะแนนออกเป็นสองอาเรย์เนื่องจากว่าเราจะใช้สอง Thread ในการประมวลผลคะแนนเหล่านี้ นี่จะทำให้โปรแกรมของเราสามารถทำงานได้เร็วขึ้นเป็นสองเท่า เนื่องจากว่า Thread ทั้งสองนั้นจะประมวลผลคะแนนเหล่านี้ไปพร้อมกันๆ
thr1 = Thread.new(score1) { |scores|
hash = {}
scores.each { |item|
grade = cal_grade(item)
if hash.key?(grade)
hash[grade] = hash[grade] + 1
else
hash[grade] = 1
end
}
hash
}
นี่เป็น Thread สำหรับคำนวณเกรดและนับว่าแต่ละเกรดนั้นมีนักเรียนจำนวนเท่าไหร่ที่ได้ บล็อครับค่าเป็นอาเรย์ของคะแนนที่ส่งเข้ามาและนำคะแนนไปคำนวณเกรดโดยเรียกใช้เมธอด cal_grade
หลังจากที่ได้รับผลของเกรดแล้ว เรานับจำนวนของคนที่ได้แต่ละเกรดโดยใช้ Hash ในการนับและใช้ชื่อเกรดเป็น Key ของ Hash
ในตอนท้ายของบล็อค เราส่งค่ากลับจากบล็อคเป็น Hash ของจำนวนนักเรียนที่นับได้สำหรับแต่ละเกรด นี่จะทำให้เราสามารถรับเอาค่าดังกล่าวนี้ได้จากเมธอด value
ของ Thread
thr1.join
thr2.join
hash1 = thr1.value
hash2 = thr2.value
หลังจากเราสร้าง Thread ทั้งสองและเรียกใช้งานโดยการส่งคะแนนเข้าไปใน Thread แล้ว เรารอให้ Thread ทั้งสองทำงานให้เสร็จด้วยเมธอด join
หลังจากนั้นเราใช้เมธอด value
สำหรับรับเอาค่าที่ส่งกลับมาจาก Thread
puts "Data computed from each threads"
p hash1
p hash2
เราได้แสดงจำนวนของนักเรียนที่นับได้จากทั้งสอง Thread ข้อมูลเหล่านี้เป็น Hash ที่นับว่ามีนักเรียนได้เกรดต่างๆ เป็นจำนวนเท่าไหร่ โดยใช้ชื่อของเกรดเป็น Key ของ Hash
sum_hash = {}
["A", "B", "C", "D", "F"].each { |key|
sum_hash[key] = hash1.fetch(key, 0) + hash2.fetch(key, 0)
}
puts "Studies result of 40 students"
sum_hash.each_pair { |key, value|
puts "Grade #{key}: #{value} students"
}
มีอีกอย่างหนึ่งที่เราต้องทำก็คือรวมจำนวนนักเรียนที่นับได้จากทั้งสอง Thread เข้าด้วยกัน เราได้วนอ่านค่า Key ของ Hash ทั้งสองและนับค่าดังกล่าวมารวมไว้ใน Hash ใหม่ที่เราสร้างขึ้นชื่อว่า sum_hash
และแสดงผลออกมาว่าแต่ละเกรดนั้นมีจำนวนนักเรียนเท่าไหร่
Data computed from each threads
{"D"=>1, "A"=>4, "F"=>10, "B"=>4, "C"=>1}
{"F"=>13, "A"=>6, "C"=>1}
Studies result of 40 students
Grade A: 10 students
Grade B: 4 students
Grade C: 2 students
Grade D: 1 students
Grade F: 23 students
นี่เป็นผลลัพธ์การทำงานของโปรแกรมจากการนับว่านักเรียนได้เกรดต่างๆ เป็นจำนวนเท่าไหร่จากคะแนนทั้งหมดที่เรามี
ในตอนนี้ คุณคงเห็นแล้วว่าเราสามารถใช้ประโยชน์จาก Thread ในการแบ่งงานออกเป็นงานย่อยๆ เพื่อแบ่งให้แต่ละ Thread ช่วยกันทำได้ จากในตัวอย่างของเรานั้นใช้เพียงสอง Thread ซึ่งในความเป็นจริงแล้ว คุณสามารถใช้มากกว่าสอง Thread ได้
Thread Synchronization
Thread Synchronization คือการทำงานพร้อมกันตั้งแต่สอง Thread ขึ้นไปและ Thread เหล่านั้นใช้ทรัพยากรบางอย่างร่วมกันซึ่งทรัพยากรดังกล่าวเรียกว่า Critical section ในการที่จะเข้าถึงส่วนดังกล่าว Thread จะต้องทำงานแบบตามลำดับเพื่อหลีกเลี่ยงความขัดแย้งที่อาจจะเกิดขึ้น เช่น การที่แต่ละ Thread พยายามเปลี่ยนแปลงค่าของตัวแปรตัวเดียวกันในเวลาเดียวกัน ดังนั้น Thread Synchronization จึงเป็นวิธีในการป้องกันปัญหาดังกล่าวที่อาจจะเกิดขึ้นได้
ในภาษา Ruby เราสามารถใช้คลาส Mutex
ในการสร้างตัวส่งสัญญาณระหว่าง Thread ได้ คลาสนี้ถูกออกแบบมาสำหรับใช้ประสานงานในการเข้าถึงข้อมูลร่วมกันระหว่าง Thread ที่ใช้งานง่ายและตรงไปตรงมา มาดูตัวอย่าง
def is_prime(n)
if n == 1
return false
end
for i in (2...n)
return false if n % i == 0 && i != n
end
return true
end
semaphore = Mutex.new
counter = 1
thr1 = Thread.new {
while counter <= 100
n = nil
semaphore.synchronize {
n = counter
counter = counter + 1
}
if is_prime(n)
puts "Thread 1: #{n} is prime"
else
puts "Thread 1: #{n} is not prime"
end
sleep(0.1)
end
}
thr2 = Thread.new {
while counter <= 100
n = nil
semaphore.synchronize {
n = counter
counter = counter + 1
}
if is_prime(n)
puts "Thread 2: #{n} is prime"
else
puts "Thread 2: #{n} is not prime"
end
sleep(0.1)
end
}
thr1.join
thr2.join
ในตัวอย่าง เป็นโปรแกรมสำหรับแสดงตัวเลขจำนวนเฉพาะระหว่าง 1 - 100 เราต้องการทราบว่ามีตัวเลขไหนที่เป็นจำนวนเฉพาะบ้าง เนื่องจากการตรวจสอบจำนวนเฉพาะในแต่ละตัวเลขนั้นใช้เวลาแตกต่างกัน ตัวเลขที่ใหญ่กว่ามีแนวโน้มว่าจะใช้เวลานานกว่า ดังนั้นเราจะไม่แบ่งข้อมูลออกเป็นสองส่วนเหมือนกับในตัวอย่างก่อนหน้า แต่เราจะใช้วิธีให้แต่ละ Thread ค่อยๆ นำตัวเลขไปคำนวณที่ละตัวแทน
def is_prime(n)
if n == 1
return false
end
for i in (2...n)
return false if n % i == 0 && i != n
end
return true
end
ในตอนแรก เราสร้างเมธอด is_prime
สำหรับหาว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ เมธอดนี้ส่งค่ากลับเป็น Boolean ว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ จำนวนเฉพาะคือจำนวนที่มีแค่หนึ่งและตัวมันเองเท่านั้นที่สามารถหารลงตัวได้
semaphore = Mutex.new
counter = 1
หลังจากนั้นเราสร้างออบเจ็ค semaphore
ซึ่งเป็นออบเจ็คจากคลาส Mutex
สำหรับควบคุมการเข้าถึงตัวแปรที่ใช้ร่วมกัน เราได้ประกาศตัวแปร counter
ที่ใช้สำหรับเก็บค่าตัวเลขที่ถูกประมวลผลไปแล้ว ตัวแปรนี้จะถูกเข้าถึงจากทั้งสอง Thread ดังนั้นเราจะใช้ออบเจ็ค semaphore
เพื่อควบคุมให้ทีละ Thread สามารถอ่านค่าและเปลี่ยนแปลงค่าในตัวแปรได้นั่นเอง ซึ่งกระบวนการนี้เองเรียกว่า Synchronization
thr1 = Thread.new {
while counter <= 100
n = nil
semaphore.synchronize {
n = counter
counter = counter + 1
}
if is_prime(n)
puts "Thread 1: #{n} is prime"
else
puts "Thread 1: #{n} is not prime"
end
sleep(0.1)
end
}
ต่อมาเป็นการทำงานของแต่ละ Thread ซึ่งจะวนอ่านค่าตัวเลขจากตัวแปร counter
มาคำนวณเพื่อหาว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ เนื่องจากตัวแปร counter
นั้นจะต้องเข้าถึงจากทั้งสอง Thread เราใช้เมธอด synchronize
เพื่อทำให้แน่ใจว่ามีเพียงหนึ่ง Thread เท่านั้นที่สามารถเข้าถึงตัวแปรได้ในช่วงเวลานั้นๆ เราได้อ่านค่าเข้ามาเก็บไว้ในตัวแปร n
เพื่อใช้งานใน Thread หลังจากนั้นเพิ่มค่าในตัวแปรขึ้นไป 1
if is_prime(n)
puts "Thread 1: #{n} is prime"
else
puts "Thread 1: #{n} is not prime"
end
หลังจากนั้นเรียกใช้งานเมธอด is_prime
เพื่อตรวจสอบว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ และแสดงผลลัพธ์ของตัวเลขออกทางหน้าจอพร้อมกับชื่อของ Thread ดังนั้นเราจะสามารถทราบได้ว่าตัวเลขนี้ถูกคำนวณจาก Thread ไหน
sleep(0.1)
เนื่องจากคอมพิวเตอร์นั้นทำงานเร็วมาก และ Thread นั้นเริ่มทำงานในทันทีหลังจากที่มันถูกสร้าง ดังนั้นก่อนที่ Thread ทั้งสองจะเริ่มทำงาน Thread แรกอาจจะนำตัวเลขไปคำนวณจนหมดแล้วก็ได้ ดังนั้นเราจึงได้หน่วงเวลา 0.1 วินาที เพื่อทำให้การทำงานใน Thread ช้าลง
Thread 1: 1 is not prime
Thread 2: 2 is prime
Thread 1: 3 is prime
Thread 2: 4 is not prime
Thread 1: 5 is prime
Thread 2: 6 is not prime
Thread 1: 7 is prime
Thread 2: 8 is not prime
Thread 2: 9 is not prime
Thread 1: 10 is not prime
Thread 1: 11 is prime
Thread 2: 12 is not prime
Thread 1: 13 is prime
Thread 2: 14 is not prime
Thread 1: 15 is not prime
...
นี่เป็นผลลัพธ์การทำงานของโปรแกรม จะเห็นว่าทั้งสอง Thread นำตัวเลขจากตัวแปร counter
มาใช้ในการหาว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ ซึ่งในตอนอัพเดทค่าในตัวแปรขึ้นไปหนึ่งนั้นอาจจะทำให้เกิดความขัดแย้งได้ ดังนั้นเมธอด synchronize
จะทำให้เราแน่ใจได้ว่ามีเพียงหนึ่ง Thread เท่านั้นที่เข้าถึงตัวแปรในขณะนั้นนั่นเอง
การสร้างคลาส Thread
ในตัวอย่างก่อนหน้า เราได้สร้าง Thread จากคลาส Thread
โดยการกำหนดบล็อคการทำงานของ Thread ผ่านทางเมธอด new
ซึ่งเป็นคอนสตรัคเตอร์ของคลาส และอย่างที่คุณเห็นว่าการทำเช่นนั้นเราจำเป็นจะต้องทำซ้ำโค้ดถ้าหากว่าการทำงานของทั้งสอง Thread เหมือนกัน อย่างไรก็ตาม ในภาษา Ruby เราสามารถสร้างคลาส Thread ของเราเองได้ โดยการสืบทอดคลาสของเรามาจากคลาส Thread
นี่เป็นตัวอย่าง
class MyThread < Thread
def initialize(name, count)
@name = name
@count = count
super {
puts "#{@name} started"
counter
}
end
def counter
for i in (1..@count)
puts "#{@name}: #{i}"
end
end
end
threads = [
MyThread.new("Thread 1", 10),
MyThread.new("Thread 2", 5),
MyThread.new("Mateo", 5)
]
threads.each { |thr|
thr.join
}
ในตัวอย่าง เราได้สร้างคลาสชื่อว่า MyThread
คลาสนี้เป็นคลาส Thread เนื่องจากเราได้สืบทอดคลาสมาจากคลาส Thread
ในคอนสตรัคเตอร์ของคลาสหรือเมธอด initialize
รับอาร์กิวเมนต์สองตัวคือ name
และ count
สำหรับเก็บชื่อของ Thread และจำนวนตัวเลขที่ต้องการนับใน Thread ตามลำดับ
super {
puts "#{@name} started"
counter
}
ในเมธอด initialize
ในการที่จะทำให้ Thread เริ่มต้นทำงาน เราจำเป็นต้องเรียกใช้คอนสตรัคเตอร์ของคลาสหลักด้วยเมธอด super
ซึ่งโค้ดการทำงานของ Thread จะเขียนไว้ในบล็อคของเมธอดนี้ ในบล็อคของเราแสดงชื่อของ Thread ออกทางหน้าจอ และเรียกใช้งานเมธอด counter
สำหรับนับตัวเลข
def counter
for i in (1..@count)
puts "#{@name}: #{i}"
end
end
เมธอด counter
เป็นเมธอดสำหรับนับตัวเลขจาก 1 - @count
ซึ่งจะเป็นค่าที่เราส่งเข้ามาในตอนสร้าง Thread ซึ่งแต่ละ Thread ที่สร้างจากคลาสนี้จะมีเมธอดนับเลขเป็นของมันเอง ซึ่งนี่จะทำให้เราไม่ต้องเขียนโค้ดซ้ำสำหรับแต่ละ Thread
threads = [
MyThread.new("Thread 1", 10),
MyThread.new("Thread 2", 5),
MyThread.new("Mateo", 5)
]
threads.each { |thr|
thr.join
}
หลังจากนั้นเราสร้างสาม Thread จากคลาส MyThread
ของเรา ในตอนนี้เราสร้าง Thread และเก็บไว้ในอาเรย์ หลังจากนั้นเรียกใช้งานเมธอด join
ของแต่ละ Thread เพื่อรอให้ทุก Thread ทำงานเสร็จก่อนจบโปรแกรม
Thread 1 started
Thread 1: 1
Thread 1: 2
Thread 1: 3
Thread 1: 4
Thread 1: 5
Thread 1: 6
Thread 1: 7
Thread 1: 8
Thread 1: 9
Thread 1: 10
Thread 2 started
Thread 2: 1
Thread 2: 2
Thread 2: 3
Thread 2: 4
Thread 2: 5
Mateo started
Mateo: 1
Mateo: 2
Mateo: 3
Mateo: 4
Mateo: 5
นี่เป็นผลลัพธ์การทำงานของโปรแกรม จะเห็นว่าแต่ละ Thread แสดงชื่อและนับเลขตามจำนวนที่ส่งเข้าไปในตอนสร้าง Thread ซึ่งทุก Thread นั้นใช้คลาสร่วมกันโดยที่เราไม่ต้องเขียนการทำงานของแต่ละ Thread เหมือนกับในตัวอย่างก่อนหน้าที่เราเคยทำ
ในบทนี้ คุณได้เรียนรู้พื้นฐานเกี่ยวกับ Thread ในภาษา Ruby อย่างไรก็ตาม ยังมีเมธอดอีกเป็นจำนวนมากของ Thread ที่เรายังไม่ได้พูดถึง ยกตัวอย่างเช่น เมธอด exit
ใช้สำหรับหยุดการทำงานของ Thread หรือเมธอด status
ใช้สำหรับดูสถานะการทำงานของ Thread ถ้าหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Thread คุณสามารถดูได้ที่ https://ruby-doc.org/core-2.7.1/Thread.html