English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。
线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。
Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。
要启动一个新的线程,只需要调用 Thread.new 即可:
# 线程 #1 代码部分 Thread.new { # 线程 #2 执行代码 } # 线程 #1 执行代码
以下示例展示了如何在Ruby程序中使用多线程:
#!/usr/bin/ruby def func1 i=0 while i<=2 puts "func1 at: #{Time.now}" sleep(2) i=i+1 fin fin def func2 j=0 while j<=2 puts "func2 at: #{Time.now}" sleep(1) j=j+1 fin fin puts "Started At #{Time.now}" t1=Thread.new{func1()} t2=Thread.new{func2()} t1.join t2.join puts "End at #{Time.now}"
نتيجة تنفيذ الكود أعلاه هي:
Started At Wed May 14 08:21:54 -0700 2014 func1 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:54 -0700 2014 func2 at: Wed May 14 08:21:55 -0700 2014 func1 at: Wed May 14 08:21:56 -0700 2014 func2 at: Wed May 14 08:21:56 -0700 2014 func1 at: Wed May 14 08:21:58 -0700 2014 End at Wed May 14 08:22:00 -0700 2014
1- يمكن إنشاء النواة باستخدام Thread.new، كما يمكن استخدام نفس النحو لإنشاء النواة باستخدام Thread.start أو Thread.fork.
2- لا تحتاج إلى تشغيل النواة بعد إنشائها، ستقوم النواة بالتنفيذ تلقائيًا.
3- تعريف كائن النواة يحتوي على بعض الطرق لتحكم النواة. تنفذ النواة كود كائن Thread.new.
4- آخر جملة في كائن النواة هي قيمة النواة، يمكن الوصول إليها عبر طرق النواة، إذا كانت النواة قد انتهت، فإنها تعود بقيمة النواة، وإلا فإنها لا تعود بأي قيمة حتى تنتهي النواة.
5- تعود طريقة Thread.current بناءً على النواة الحالية. وتعود طريقة Thread.main بالنواة الرئيسية.
6- يمكن تنفيذ النواة باستخدام طريقة Thread.Join، هذه الطريقة تؤدي إلى تعليق النواة الرئيسية حتى تنتهي النواة الحالية.
النواة تحتوي على 5 حالات:
حالة النواة | القيمة العائدة |
---|---|
قابل للتنفيذ | run |
النوم | Sleeping |
الخروج | aborting |
الانتهاء بشكل طبيعي | false |
الانتهاء بسبب الاستثناء | nil |
عندما يحدث استثناء في thread ولم يتم التقاطعه بواسطة rescue، عادة ما يتم إنهاء هذا thread بدون تحذير. ولكن إذا كان هناك threads أخرى تنتظر هذا thread بسبب Thread#join، فإن threads التي تنتظر ستكون لها نفس الاستثناء.
begin t = Thread.new do Thread.pass # رئيسية thread حقاً ينتظر join raise "unhandled exception" fin t.join rescue p $! # => "unhandled exception" fin
باستخدام هذه الطرق الثلاث يمكن للمفسر التوقف عند حدوث استثناء في thread معين.
تحديد عند بدء البرنامج.-dخيار، ويتم تشغيله في نمط التتبع.
استخدام Thread.abort_on_exception لتعيين العلامة.
استخدام Thread#abort_on_exception لتعيين علامة للمthreads المحدد.
عند استخدام أي من هذه الطرق الثلاث، يتم قطع كل المفسر.
t = Thread.new { ... } t.abort_on_exception = true
في Ruby، تقدم ثلاث طرق لتحقيق التوازي، وهي:
1. من خلال استعمال فئة Mutex تحقيق التوازي
2. استخدام فئة Queue لمراقبة التبادل البياناتي لتحقيق التوازي
3. استخدام ConditionVariable لتحقيق التحكم في التوازي
من خلال استعمال فئة Mutex تحقيق التحكم في التوازي للthreads، إذا كان يتطلب برنامج متعدد threads مغير من نفس المتغير، يمكن جزء من هذا المتغير استخدام lock. 代码如下:
#!/usr/bin/ruby require "thread" puts "Synchronize Thread" @num=200 @mutex=Mutex.new def buyTicket(num) @mutex.lock if @num>=num @num=@num-num puts "you have successfully bought #{num} tickets" else puts "sorry,no enough tickets" fin @mutex.unlock fin ticket1=Thread.new 10 do 10.times do |value| ticketNum=15 buyTicket(ticketNum) نوم 0.01 fin fin ticket2=Thread.new 10 do 10.times do |value| ticketNum=20 buyTicket(ticketNum) نوم 0.01 fin fin sleep 1 ticket1.join ticket2.join
نتيجة تنفيذ الكود أعلاه هي:
تزامن السطر لقد اشتريت بنجاح 15 تذاكر لقد اشتريت بنجاح 20 تذاكر لقد اشتريت بنجاح 15 تذاكر لقد اشتريت بنجاح 20 تذاكر لقد اشتريت بنجاح 15 تذاكر لقد اشتريت بنجاح 20 تذاكر لقد اشتريت بنجاح 15 تذاكر لقد اشتريت بنجاح 20 تذاكر لقد اشتريت بنجاح 15 تذاكر لقد اشتريت بنجاح 20 تذاكر لقد اشتريت بنجاح 15 تذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر آسف، ليست هناك كفاية التذاكر
بالإضافة إلى استخدام lock لتحديد المتغير، يمكن استخدام try_lock لتحديد المتغير، بالإضافة إلى استخدام Mutex.synchronize لتزامن الوصول إلى متغير معين.
Class Queue تمثل حاوية تدعم السطور، وتسمح بالوصول المتزامن إلى نهاية الحاوية. يمكن استخدام نفس الحاوية من قبل سطور مختلفة، ولكن لا يجب أن نقلق بشأن تزامن البيانات في الحاوية، بالإضافة إلى أن استخدام Class SizedQueue يمكن أن يحد من طول الحاوية.
يستطيع أن يساعدنا Class SizedQueue بشكل كبير في تطوير تطبيقات التزامن بين السطور، لأنه بمجرد إضافة شيء إلى هذه الحاوية، لا نحتاج إلى العناية بتزامن السطور.
مشكلة الإنتاج والاستهلاك الكلاسيكية:
#!/usr/bin/ruby require "thread" puts "اختبار SizedQuee" queue = Queue.new producer = Thread.new do 10.times do |i| sleep rand(i) # تجعل السطر ينام لفترة من الزمن queue << i puts "#{i} انتجت" fin fin consumer = Thread.new do 10.times do |i| value = queue.pop sleep rand(i/2) puts "استهلكت #{value}" fin fin consumer.join
مخرجات البرنامج:
اختبار SizedQuee انتجت 0 انتجت 1 استهلكت 0 انتجت 2 استهلكت 1 استهلكت 2 انتجت 3 استهلكت 34 انتجت استهلكت 4 انتجت 5 استهلكت 5 انتجت 6 استهلكت 6 انتجت 7 استهلكت 7 انتجت 8 انتجت 9 استهلكت 8 استهلكت 9
السطر يمكن أن يمتلك متغيرات خاصة به، وتكتب متغيرات السطر الخاصة في وقت إنشاء السطر. يمكن استخدامها داخل نطاق السطر، ولكن لا يمكن مشاركتها خارج نطاق السطر.
لكن أحياناً، تحتاج متغيرات السطر المحلية إلى الوصول إليها من قبل سطر آخر أو السطر الرئيسي كيف؟ يوفر Ruby اسمًا للسطر المتغير، يشبه وضع السطر كجدول هاش. يمكن استخدام []= لتبديل البيانات، واستخدام [] لقراءة البيانات. لنلقي نظرة على الكود التالي:
#!/usr/bin/ruby count = 0 arr = [] 10.times do |i| arr[i] = Thread.new { sleep(rand(0)/10.0) Thread.current["mycount"] = count count += 1 } fin arr.each {|t| t.join; print t["mycount"], ", "} puts "count = #{count}"
نتيجة التشغيل للكود أعلاه هي:
8, 0, 3, 7, 2, 1, 6, 5, 4, 9, count = 10
ينتظر السطر الرئيسي اكتمال تنفيذ السطر الفرعي، ثم ينشر كل قيمة. 。
يؤثر مستوى الأولوية للسطر بشكل رئيسي على جدولة السطر. العوامل الأخرى تشمل وقت التنفيذ المستخدم للمعالج، جدولة السطور المجمعة، وما إلى ذلك.
يمكن الحصول على مستوى الأولوية للسطر باستخدام Thread.priority، وتعديل مستوى الأولوية باستخدام Thread.priority=.
يكون مستوى الأولوية للسطر افتراضياً 0. يجب أن يكون السطر ذو الأولوية العالية أسرع في التنفيذ.
يمكن للسطر الوصول إلى جميع البيانات في نطاقه، ولكن ماذا إذا كان هناك حاجة إلى الوصول إلى بيانات سطر آخر داخل سطر معين؟ توفر فئة Thread طرق للوصول إلى بيانات السطور المتبادلة، يمكنك ببساطة اعتبار السطر كجدول هاش، يمكنك استخدام []= في أي سطر لتبديل البيانات، باستخدام [] لقراءة البيانات.
athr = Thread.new { Thread.current["name"] = "Thread A"; Thread.stop } bthr = Thread.new { Thread.current["name"] = "Thread B"; Thread.stop } cthr = Thread.new { Thread.current["name"] = "Thread C"; Thread.stop } Thread.list.each {|x| puts "#{x.inspect}: #{x["name"]}"}
يمكن رؤية ذلك، باستخدام السطر كجدول هاش، باستخدام طرق [] و []=، قمنا بتحقيق مشاركة البيانات بين السطور.
Mutex (Mutal Exclusion = قفل الالتزام) هو آلية تستخدم في البرمجة المتعددة النواة لمنع اثنتين من النوات من كتابة نفس المصدر المشترك (مثل المتغيرات العالمية) في نفس الوقت.
#!/usr/bin/ruby require 'thread' count1 = count2 = 0 difference = 0 counter = Thread.new do loop do count1 += 1 count2 += 1 fin fin spy = Thread.new do loop do difference += (count1 - count2).abs fin fin sleep 1 puts "count1: #{count1}" puts "count2: #{count2}" puts "الفرق: #{difference}"
نتيجة النسخة المثبتة للنموذج أعلاه هي:
count1: 9712487 count2: 12501239 الفرق: 0
#!/usr/bin/ruby require 'thread' mutex = Mutex.new count1 = count2 = 0 difference = 0 counter = Thread.new do loop do mutex.synchronize do count1 += 1 count2 += 1 fin fin fin spy = Thread.new do loop do mutex.synchronize do difference += (count1 - count2).abs fin fin fin sleep 1 mutex.lock puts "count1: #{count1}" puts "count2: #{count2}" puts "الفرق: #{difference}"
نتيجة النسخة المثبتة للنموذج أعلاه هي:
count1: 1336406 count2: 1336406 الفرق: 0
عندما تكون هناك وحدات حسابية أكثر من واحدة، وكل منهما ينتظر أن يتوقف الآخر قبل أن يحقق الطلب على موارد النظام، ولكن لا يوجد من يتراجع مسبقًا، فإن هذا الوضع يُسمى عقد النوعي.
على سبيل المثال، عملية p1 تستخدم جهاز العرض، وتحتاج أيضًا إلى استخدام الطابعة، بينما تستخدم الطابعة عملية p2، ويحتاج p2 أيضًا إلى استخدام جهاز العرض، مما يؤدي إلى تشكيل عقد النوعي.
عند استخدامنا لعدة نواة يجب أن نكون حذرين من العقد النوعي.
#!/usr/bin/ruby require 'thread' mutex = Mutex.new cv = ConditionVariable.new a = Thread.new { mutex.synchronize { puts "A: لديني جزء حرج، لكن سأنتظر cv" cv.wait(mutex) puts "A: لديني جزء حرج مرة أخرى! أنا أرأي!" } } puts "(لاحقاً، العودة إلى المزرعة...)" b = Thread.new { mutex.synchronize { puts "B: الآن أنا حرج، لكنني انتهيت من cv" cv.signal puts "B: ما زلت حرجًا، أنهي العمل" } } a.join b.join
نتيجة خروج المثال أعلاه كالتالي:
A: لدي قسم حرج، لكن سأنتظر cv (لاحقًا، عائدين إلى الحقل...) B: الآن أنا حرج، لكني انتهيت من cv B: ما زلت حرجًا، أنهي العمل A: لديني قسم حرج مرة أخرى! أنا أرتبط!
تمثيل Thread (thread) للأساليب كاملة كالتالي:
الترقيم | وصف الطريقة |
---|---|
1 | Thread.abort_on_exception إذا كانت قيمتها true، عند توقف thread بسبب استثناء، سيتم إيقاف تشغيل المفسر بأكمله. قيمتها الافتراضية هي false، مما يعني أن في حالات الطبيعية، إذا حدث استثناء في thread وأن الاستثناء لم يتم الكشف عنه من قبل Thread#join وما إلى ذلك، سيتم إيقاف تشغيل thread دون أي إنذار. |
2 | Thread.abort_on_exception= إذا تم تعيينها true، عند توقف thread بسبب استثناء، سيتم إيقاف تشغيل المفسر بأكمله. يعود إلى الحالة الجديدة |
3 | Thread.critical إرجاع قيمة بولية. |
4 | Thread.critical= عندما يكون قيمته true، لن يتم إجراء تغيير thread. إذا كانت thread معطلة (stop) أو هناك إشارة (signal) تدخل، سيصبح قيمته false تلقائيًا. |
5 | Thread.current يعود إلى thread التشغيل الحالي (thread الحالي). |
6 | Thread.exit يوقف تشغيل thread الحالي. يعود إلى thread الحالي. إذا كان thread الحالي هو thread الوحيد، سيستخدم exit(0) لتوقف تشغيله. |
7 | Thread.fork { block } يخلق thread جديد مثل Thread.new. |
8 | Thread.kill( aThread ) يوقف تشغيل thread. |
9 | Thread.list يعود إلى مجموعة من threads التي تعمل حاليًا أو معطلة. |
10 | Thread.main يعود إلى thread الرئيسي. |
11 | Thread.new( [ arg ]* ) {| args | block } إنشاء سطر وإعادة تشغيله. سيتم نقل القيم دون تغيير إلى الكتلة. هذا يمكن أن يسمح بمرور القيم إلى المتغيرات المحلية الخاصة بالسطر عند بدء تشغيل السطر. |
12 | Thread.pass يحول الحق في التشغيل إلى thread أخرى. لن يغير حالة thread التشغيل حاليًا، بل سيدخل في سيطرة thread القابلة للتشغيل الأخرى (تخطيط thread مكتوب بشكل واضح). |
13 | Thread.start( [ args ]* ) {| args | block } إنشاء سطر وإعادة تشغيله. سيتم نقل القيم دون تغيير إلى الكتلة. هذا يمكن أن يسمح بمرور القيم إلى المتغيرات المحلية الخاصة بالسطر عند بدء تشغيل السطر. |
14 | Thread.stop تعليق السطر الحالي حتى يستدعي طريقة run سطر آخر ويستيقظ السطر. |
هذا المثال يستدعي نموذج طريقة السطر threads: join
#!/usr/bin/ruby thr = Thread.new do # مثال puts " In second thread " raise " Raise exception " fin thr.join # مثال على استخدام الطريقة المثبتة join
هذه هي قائمة الطرق المثبتة بالكامل:
الترقيم | وصف الطريقة |
---|---|
1 | thr[ name ] استخراج البيانات الجذرية للسطر المتعلقة بالاسم. يمكن أن يكون الاسم نصًا أو رمزًا. إذا لم تكن هناك بيانات مرتبطة بالاسم، فسيتم إرجاع nil. |
2 | thr[ name ]= تعيين قيمة البيانات الجذرية للسطر المتعلقة بالاسم، يمكن أن يكون الاسم نصًا أو رمزًا. إذا تم تعيينها على nil، سيتم حذف البيانات الجذرية للسطر المطلوبة. |
3 | thr.abort_on_exception إرجاع قيمة بولية. |
4 | thr.abort_on_exception= إذا كانت القيمة true، فإن الم的解释ر سيتم تerrupt إذا تم إنهاء سطر ما بسبب استثناء. |
5 | thr.alive? إذا كان السطر "مزيفًا"، فسيتم إرجاع true. |
6 | thr.exit إنهاء تشغيل السطر. إرجاع self. |
7 | thr.join تعليق السطر الحالي حتى ينتهي تشغيل السطر self. إذا توقف self بسبب استثناء، فإن السطر الحالي سيقوم بتحفيز نفس الاستثناء. |
8 | thr.key? إذا تم تعريف البيانات الجذرية للسطر المتعلقة بالاسم، فسيتم إرجاع true |
9 | thr.kill مثل Thread.exit 。 |
10 | thr.priority إرجاع أولوية السطر. قيمة الأولوية الافتراضية هي 0. كلما زادت القيمة، زادت الأولوية. |
11 | thr.priority= ضبط أولوية السطر. يمكن أيضًا ضبطها على عدد سالب. |
12 | thr.raise( anException ) تحفيز استثنائية في هذا السطر. |
13 | thr.run إعادة تشغيل السطر المعطل (stop). يختلف عن wakeup لأنه يقوم بتحويل السطر الفوري. إذا تم استخدامه على عملية ميتة، فسيتم إصدار استثناء ThreadError. |
14 | thr.safe_level عد إلى مستوى الأمان لـ self. مستوى الأمان للنواة الحالية هو نفسه كـ $SAFE. |
15 | thr.status استخدم الأنماط النصية "run"، "sleep" أو "aborting" لتمثيل حالة النواة النشطة. إذا كانت النواة تنتهي بشكل طبيعي، فإنها تعود إلى الحقيقة. إذا كانت تنتهي بسبب استثناء، فإنها تعود إلى nil. |
16 | thr.stop? إذا كانت النواة في حالة النهاية (dead) أو الموقوفة (stop)، فإنها تعود إلى الحقيقة. |
17 | thr.value انتظر حتى تنتهي نواة self من العمل (تقارب الأمر بـ join) وعد إلى القيمة التي يعود إليها الكتلة. إذا حدث استثناء أثناء تشغيل النواة، فإنه سيؤدي إلى إعادة إطلاق الاستثناء. |
18 | thr.wakeup تغيير حالة النواة الموقوفة (stop) إلى حالة القيادة (run). إذا تم تطبيق هذا الأسلوب على النواة الميتة، فإنه سيؤدي إلى إطلاق استثناء ThreadError. |