रूबी मैजिक के पिछले संस्करण में हमने दिखाया था कि आप कई प्रक्रियाओं का उपयोग करके चैट सिस्टम को कैसे लागू कर सकते हैं। इस बार हम आपको दिखाएंगे कि आप एक से अधिक थ्रेड का उपयोग करके एक ही काम कैसे कर सकते हैं।
त्वरित पुनर्कथन
यदि आप मूल सेटअप की पूरी व्याख्या प्राप्त करना चाहते हैं तो पिछला लेख देखें। लेकिन आपको शीघ्रता से याद दिलाने के लिए:हमारा चैट सिस्टम ऐसा दिखता है:
हम उसी क्लाइंट का उपयोग कर रहे हैं जिसका हमने पहले उपयोग किया था:
# client.rb
# $ ruby client.rb
require 'socket'
client = TCPSocket.open(ARGV[0], 2000)
Thread.new do
while line = client.gets
puts line.chop
end
end
while input = STDIN.gets.chomp
client.puts input
end
सर्वर के लिए मूल सेटअप समान है:
# server_threads.rb
# $ ruby server_threads.rb
require 'socket'
puts 'Starting server on port 2000'
server = TCPServer.open(2000)
इस आलेख के उदाहरणों में उपयोग किया गया संपूर्ण स्रोत कोड GitHub पर उपलब्ध है, इसलिए आप स्वयं इसका प्रयोग कर सकते हैं।
मल्टी-थ्रेडेड चैट सर्वर
अब हम उस हिस्से पर जा रहे हैं जो बहु-प्रक्रिया कार्यान्वयन की तुलना में अलग है। मल्टी-थ्रेडिंग का उपयोग करना हम सिर्फ एक रूबी प्रक्रिया के साथ एक ही समय में कई काम कर सकते हैं। हम यह काम करने वाले कई धागों को पैदा करके करेंगे।
थ्रेड
एक थ्रेड स्वतंत्र रूप से चलता है, एक प्रक्रिया के भीतर कोड निष्पादित करता है। एकाधिक थ्रेड एक ही प्रक्रिया में रह सकते हैं और वे स्मृति साझा कर सकते हैं।
<img src="/images/blog/2017-04/threads.png">
आने वाले चैट संदेशों को संग्रहीत करने के लिए कुछ संग्रहण की आवश्यकता होगी। हम एक सादे Array
का उपयोग करेंगे , लेकिन हमें एक Mutex
. भी चाहिए यह सुनिश्चित करने के लिए कि एक ही समय में केवल एक थ्रेड संदेशों को बदलता है (हम देखेंगे कि कैसे Mutex
थोड़ी देर में काम करता है)।
mutex = Mutex.new
messages = []
आगे हम एक लूप शुरू करते हैं जिसमें हम चैट क्लाइंट से आने वाले कनेक्शन स्वीकार करेंगे। एक बार कनेक्शन स्थापित हो जाने के बाद, हम उस क्लाइंट कनेक्शन से आने वाले और बाहर जाने वाले संदेशों को संभालने के लिए एक थ्रेड तैयार करेंगे।
Thread.new
server.accept
. तक कॉल ब्लॉक करें कुछ देता है, और फिर नव निर्मित धागे में निम्न ब्लॉक उत्पन्न करता है। थ्रेड में कोड तब भेजी गई पहली पंक्ति को पढ़ने के लिए आगे बढ़ता है और इसे उपनाम के रूप में संग्रहीत करता है। अंत में यह संदेश भेजना और पढ़ना शुरू कर देता है।
loop do
Thread.new(server.accept) do |socket|
nickname = read_line_from(socket)
# Send incoming message (coming up)
# Read incoming messages (coming up)
end
end
म्यूटेक्स
एक म्यूटेक्स एक ऐसी वस्तु है जो कई थ्रेड्स को समन्वयित करती है कि वे साझा संसाधनों का उपयोग कैसे करते हैं, जैसे कि एक सरणी। एक थ्रेड संकेत कर सकता है कि उसे एक्सेस की आवश्यकता है, और इस दौरान अन्य थ्रेड्स साझा संसाधन तक नहीं पहुंच सकते।
सर्वर सॉकेट से आने वाले संदेशों को पढ़ता है। यह synchronize
. का उपयोग करता है संदेश स्टोर पर लॉक प्राप्त करने के लिए, ताकि यह संदेशों में सुरक्षित रूप से संदेश जोड़ सके Array
।
# Read incoming messages
while incoming = read_line_from(socket)
mutex.synchronize do
messages.push(
:time => Time.now,
:nickname => nickname,
:text => incoming
)
end
end
अंत में, एक Thread
यह सुनिश्चित करने के लिए कि सर्वर द्वारा प्राप्त किए गए सभी नए संदेश क्लाइंट को भेजे जा रहे हैं, यह सुनिश्चित करने के लिए एक लूप में लगातार चलता है। फिर से इसे एक ताला मिलता है ताकि यह जान सके कि अन्य धागे हस्तक्षेप नहीं कर रहे हैं। लूप के एक टिक के साथ यह हो जाने के बाद यह थोड़ा सोता है और फिर जारी रहता है।
# Send incoming message
Thread.new do
sent_until = Time.now
loop do
messages_to_send = mutex.synchronize do
get_messages_to_send(nickname, messages, sent_until).tap do
sent_until = Time.now
end
end
messages_to_send.each do |message|
socket.puts "#{message[:nickname]}: #{message[:text]}"
end
sleep 0.2
end
end
वैश्विक दुभाषिया लॉक
आपने शायद यह कहानी सुनी होगी कि रूबी के ग्लोबल इंटरप्रेटर लॉक (GIL) के कारण रूबी "असली" थ्रेडिंग नहीं कर सकती है। यह आंशिक रूप से सच है। जीआईएल सभी रूबी कोड के निष्पादन के चारों ओर एक ताला है और रूबी प्रक्रिया को एक साथ कई सीपीयू का उपयोग करने से रोकता है। IO संचालन (जैसे कि इस लेख में हमारे द्वारा उपयोग किए गए नेटवर्क कनेक्शन) GIL के बाहर संचालित होते हैं, जिसका अर्थ है कि आप वास्तव में इस मामले में अच्छी संगति प्राप्त कर सकते हैं।
समाप्त हो रहा है
अब हमारे पास प्रति कनेक्शन एथ्रेड का उपयोग करके एक ही प्रक्रिया के भीतर एक चैट सर्वर चल रहा है। यह बहु-प्रक्रिया कार्यान्वयन की तुलना में बहुत कम संसाधनों का उपयोग करेगा। यदि आप कोडर का विवरण देखना चाहते हैं या इसे आजमाएं तो आप यहां उदाहरण कोड पा सकते हैं।
इस श्रृंखला के अंतिम लेख में हम एक ही थ्रेड और एक ईवेंट लूप का उपयोग करके इसी चैट सर्वर को लागू करेंगे। सैद्धांतिक रूप से इसे थ्रेड कार्यान्वयन की तुलना में कम संसाधनों का भी उपयोग करना चाहिए!