English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Ruby 基础教程

Ruby 高级教程

المحاور المتعددة لـ Ruby

每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。

线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。

Ruby 中我们可以通过 Thread 类来创建多线程,Ruby的线程是一个轻量级的,可以以高效的方式来实现并行的代码。

创建 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

تحكم التوازي للthreads

في Ruby، تقدم ثلاث طرق لتحقيق التوازي، وهي:

1. من خلال استعمال فئة Mutex تحقيق التوازي

2. استخدام فئة Queue لمراقبة التبادل البياناتي لتحقيق التوازي

3. استخدام ConditionVariable لتحقيق التحكم في التوازي

من خلال استعمال فئة Mutex تحقيق التوازي

من خلال استعمال فئة 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 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 = قفل الالتزام) هو آلية تستخدم في البرمجة المتعددة النواة لمنع اثنتين من النوات من كتابة نفس المصدر المشترك (مثل المتغيرات العالمية) في نفس الوقت.

مثال على عدم استخدام Mutax

نموذج على الإنترنت

#!/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

مثال على استخدام mutex

نموذج على الإنترنت

#!/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: لديني قسم حرج مرة أخرى! أنا أرتبط!

أساليب class thread

تمثيل Thread (thread) للأساليب كاملة كالتالي:

الترقيموصف الطريقة
1Thread.abort_on_exception
إذا كانت قيمتها true، عند توقف thread بسبب استثناء، سيتم إيقاف تشغيل المفسر بأكمله. قيمتها الافتراضية هي false، مما يعني أن في حالات الطبيعية، إذا حدث استثناء في thread وأن الاستثناء لم يتم الكشف عنه من قبل Thread#join وما إلى ذلك، سيتم إيقاف تشغيل thread دون أي إنذار.
2Thread.abort_on_exception=
إذا تم تعيينها true، عند توقف thread بسبب استثناء، سيتم إيقاف تشغيل المفسر بأكمله. يعود إلى الحالة الجديدة
3Thread.critical
إرجاع قيمة بولية.
4Thread.critical=
عندما يكون قيمته true، لن يتم إجراء تغيير thread. إذا كانت thread معطلة (stop) أو هناك إشارة (signal) تدخل، سيصبح قيمته false تلقائيًا.
5Thread.current
يعود إلى thread التشغيل الحالي (thread الحالي).
6Thread.exit
يوقف تشغيل thread الحالي. يعود إلى thread الحالي. إذا كان thread الحالي هو thread الوحيد، سيستخدم exit(0) لتوقف تشغيله.
7Thread.fork { block }
يخلق thread جديد مثل Thread.new.
8Thread.kill( aThread )
يوقف تشغيل thread.
9Thread.list
يعود إلى مجموعة من threads التي تعمل حاليًا أو معطلة.
10Thread.main
يعود إلى thread الرئيسي.
11Thread.new( [ arg ]* ) {| args | block }
إنشاء سطر وإعادة تشغيله. سيتم نقل القيم دون تغيير إلى الكتلة. هذا يمكن أن يسمح بمرور القيم إلى المتغيرات المحلية الخاصة بالسطر عند بدء تشغيل السطر.
12Thread.pass
يحول الحق في التشغيل إلى thread أخرى. لن يغير حالة thread التشغيل حاليًا، بل سيدخل في سيطرة thread القابلة للتشغيل الأخرى (تخطيط thread مكتوب بشكل واضح).
13Thread.start( [ args ]* ) {| args | block }
إنشاء سطر وإعادة تشغيله. سيتم نقل القيم دون تغيير إلى الكتلة. هذا يمكن أن يسمح بمرور القيم إلى المتغيرات المحلية الخاصة بالسطر عند بدء تشغيل السطر.
14Thread.stop
تعليق السطر الحالي حتى يستدعي طريقة run سطر آخر ويستيقظ السطر.

طرق نموذج السطر

هذا المثال يستدعي نموذج طريقة السطر threads: join

نموذج على الإنترنت

#!/usr/bin/ruby
 
thr = Thread.new do   # مثال
   puts " In second thread "
   raise " Raise exception "
fin
thr.join   # مثال على استخدام الطريقة المثبتة join

هذه هي قائمة الطرق المثبتة بالكامل:

الترقيموصف الطريقة
1thr[ name ]
استخراج البيانات الجذرية للسطر المتعلقة بالاسم. يمكن أن يكون الاسم نصًا أو رمزًا. إذا لم تكن هناك بيانات مرتبطة بالاسم، فسيتم إرجاع nil.
2thr[ name ]=
تعيين قيمة البيانات الجذرية للسطر المتعلقة بالاسم، يمكن أن يكون الاسم نصًا أو رمزًا. إذا تم تعيينها على nil، سيتم حذف البيانات الجذرية للسطر المطلوبة.
3thr.abort_on_exception
إرجاع قيمة بولية.
4thr.abort_on_exception=
إذا كانت القيمة true، فإن الم的解释ر سيتم تerrupt إذا تم إنهاء سطر ما بسبب استثناء.
5thr.alive?
إذا كان السطر "مزيفًا"، فسيتم إرجاع true.
6thr.exit
إنهاء تشغيل السطر. إرجاع self.
7thr.join
تعليق السطر الحالي حتى ينتهي تشغيل السطر self. إذا توقف self بسبب استثناء، فإن السطر الحالي سيقوم بتحفيز نفس الاستثناء.
8thr.key?
إذا تم تعريف البيانات الجذرية للسطر المتعلقة بالاسم، فسيتم إرجاع true
9thr.kill
مثل Thread.exit
10thr.priority
إرجاع أولوية السطر. قيمة الأولوية الافتراضية هي 0. كلما زادت القيمة، زادت الأولوية.
11thr.priority=
ضبط أولوية السطر. يمكن أيضًا ضبطها على عدد سالب.
12thr.raise( anException )
تحفيز استثنائية في هذا السطر.
13thr.run
إعادة تشغيل السطر المعطل (stop). يختلف عن wakeup لأنه يقوم بتحويل السطر الفوري. إذا تم استخدامه على عملية ميتة، فسيتم إصدار استثناء ThreadError.
14thr.safe_level
عد إلى مستوى الأمان لـ self. مستوى الأمان للنواة الحالية هو نفسه كـ $SAFE.
15thr.status
استخدم الأنماط النصية "run"، "sleep" أو "aborting" لتمثيل حالة النواة النشطة. إذا كانت النواة تنتهي بشكل طبيعي، فإنها تعود إلى الحقيقة. إذا كانت تنتهي بسبب استثناء، فإنها تعود إلى nil.
16thr.stop?
إذا كانت النواة في حالة النهاية (dead) أو الموقوفة (stop)، فإنها تعود إلى الحقيقة.
17thr.value
انتظر حتى تنتهي نواة self من العمل (تقارب الأمر بـ join) وعد إلى القيمة التي يعود إليها الكتلة. إذا حدث استثناء أثناء تشغيل النواة، فإنه سيؤدي إلى إعادة إطلاق الاستثناء.
18thr.wakeup
تغيير حالة النواة الموقوفة (stop) إلى حالة القيادة (run). إذا تم تطبيق هذا الأسلوب على النواة الميتة، فإنه سيؤدي إلى إطلاق استثناء ThreadError.