أهلاً! عندما درست تعدد العمليات على CodeGym، واجهت في كثير من الأحيان مفاهيم "mutex" و"monitor". دون إلقاء نظرة خاطفة، هل يمكنك أن تقول كيف تختلف؟ :) إذا كانت الإجابة بنعم، أحسنت! إذا لم يكن الأمر كذلك (وهذا هو الأكثر شيوعا)، فهذا ليس مفاجئا. "Mutex" و"الشاشة" هما في الواقع مفهومان مرتبطان. بالإضافة إلى ذلك، عندما تقرأ الدروس وتشاهد مقاطع فيديو حول تعدد العمليات على مواقع الويب الأخرى، ستصادف مفهومًا مشابهًا آخر: "الإشارة". كما أن لديها وظيفة مشابهة جدًا للشاشات وكائنات المزامنة. ولهذا السبب سنقوم بالتحقيق في هذه المصطلحات الثلاثة. سنلقي نظرة على بعض الأمثلة ونتوصل إلى فهم نهائي لكيفية اختلاف هذه المفاهيم عن بعضها البعض :)
موتيكس
كائن المزامنة (أو القفل) هو آلية خاصة لمزامنة سلاسل الرسائل. أحدهما "مرتبط" بكل كائن في Java - أنت تعرف ذلك بالفعل :) لا يهم إذا كنت تستخدم الفئات القياسية أو تقوم بإنشاء فئات خاصة بك، على سبيل المثال Cat and Dog : جميع الكائنات من جميع الفئات لها كائن المزامنة (mutex ) . مصطلح "mutex" يأتي من "الاستبعاد المتبادل"، الذي يصف الغرض منه بشكل مثالي. كما قلنا في أحد دروسنا السابقة، فإن كائن المزامنة يجعل من الممكن التأكد من أن مؤشر ترابط واحد فقط في كل مرة لديه حق الوصول إلى الكائن. من الأمثلة الواقعية الشائعة لكائن المزامنة (mutex) المراحيض. عندما يدخل الشخص إلى قسم المرحاض، فإنه يقوم بقفل الباب من الداخل. يشبه المرحاض كائنًا يمكن الوصول إليه عن طريق خيوط متعددة. يشبه القفل الموجود على باب التقسيم كائن المزامنة (mutex)، ويمثل صف الأشخاص بالخارج الخيوط. القفل الموجود على الباب هو كائن المرحاض: فهو يضمن دخول شخص واحد فقط إلى الداخل. بمعنى آخر، يمكن لمؤشر ترابط واحد فقط في المرة الواحدة العمل مع الموارد المشتركة. ستفشل محاولات المواضيع الأخرى (الأشخاص) للوصول إلى الموارد المشغولة. يحتوي كائن المزامنة (mutex) على العديد من الميزات المهمة. أولاً ، هناك حالتان فقط ممكنتان: "مفتوح" و"مقفل". يساعدنا هذا على فهم كيفية عمله: يمكنك رسم أوجه التشابه مع المتغيرات المنطقية (صواب/خطأ) أو الأرقام الثنائية (0/1). ثانياً ، لا يمكن السيطرة على الدولة بشكل مباشر. لا تحتوي Java على آلية تتيح لك أخذ كائن بشكل صريح والحصول على كائن المزامنة (mutex) الخاص به وتعيين الحالة المطلوبة. بمعنى آخر، لا يمكنك فعل شيء مثل:Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
هذا يعني أنه لا يمكنك تحرير كائن المزامنة (mutex) للكائن. فقط جهاز Java لديه حق الوصول المباشر إليه. يعمل المبرمجون مع كائنات المزامنة من خلال أدوات اللغة.
شاشة
الشاشة عبارة عن "بنية فوقية" إضافية فوق كائن المزامنة (mutex). في الواقع، الشاشة عبارة عن جزء من التعليمات البرمجية "غير مرئية" للمبرمج. عندما تحدثنا عن كائنات المزامنة (mutexes) سابقًا، قدمنا مثالًا بسيطًا:public class Main {
private Object obj = new Object();
public void doSomething() {
// ...some logic, available for all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
في كتلة التعليمات البرمجية المميزة بالكلمة الأساسية المتزامنة ، يتم الحصول على كائن المزامنة (mutex) لكائن obj الخاص بنا. عظيم، يمكننا الحصول على القفل، ولكن كيف يتم توفير "الحماية" بالضبط؟ عندما نرى الكلمة متزامنة ما الذي يمنع الخيوط الأخرى من الدخول إلى الكتلة؟ الحماية تأتي من الشاشة! يقوم المترجم بتحويل الكلمة الأساسية المتزامنة إلى عدة أجزاء خاصة من التعليمات البرمجية. مرة أخرى، دعونا نعود إلى مثالنا مع طريقة doSomething() . وسنضيف إليها:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...some logic, available for all threads
// Logic available to just one thread at a time
synchronized (obj) {
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
}
}
}
إليك ما يحدث "تحت الغطاء" بعد أن يقوم المترجم بتحويل هذا الرمز:
public class Main {
private Object obj = new Object();
public void doSomething() throws InterruptedException {
// ...some logic, available for all threads
// Logic available to just one thread at a time:
/* as long as the object's mutex is busy,
all the other threads (except the one that acquired it) are put to sleep */
while (obj.getMutex().isBusy()) {
Thread.sleep(1);
}
// Mark the object's mutex as busy
obj.getMutex().isBusy() = true;
/* Do important work that requires that the object
be accessed by only one thread */
obj.someImportantMethod();
// Free the object's mutex
obj.getMutex().isBusy() = false;
}
}
وبطبيعة الحال، هذا ليس مثالا حقيقيا. هنا، استخدمنا تعليمات برمجية تشبه Java لتصوير ما يحدث داخل جهاز Java. ومع ذلك، فإن هذا الكود الزائف يوفر فهمًا ممتازًا لما يحدث بالفعل مع الكائن والخيوط داخل الكتلة المتزامنة وكيف يقوم المترجم بتحويل هذه الكلمة الأساسية إلى عدة عبارات "غير مرئية" للمبرمج. في الأساس، تستخدم Java الكلمة الأساسية المتزامنة لتمثيل الشاشة . كل التعليمات البرمجية التي تظهر بدلاً من الكلمة الأساسية المتزامنة في المثال الأخير هي الشاشة.
إشارة
الكلمة الأخرى التي ستواجهها في دراستك الشخصية لتعدد مؤشرات الترابط هي "الإشارة". دعونا نتعرف على ما هو وكيف يختلف عن الشاشة وكائن المزامنة (mutex). الإشارة هي أداة لمزامنة الوصول إلى بعض الموارد. السمة المميزة لها هي أنها تستخدم عدادًا لإنشاء آلية المزامنة. يخبرنا العداد بعدد سلاسل العمليات التي يمكنها الوصول إلى المورد المشترك في نفس الوقت. يتم تمثيل الإشارات في Java بواسطة فئة Semaphore . عند إنشاء كائنات الإشارة، يمكننا استخدام المنشئات التالية:Semaphore(int permits)
Semaphore(int permits, boolean fair)
نمرر ما يلي إلى المنشئ:
- معرض منطقي - يحدد الترتيب الذي سيتم من خلاله الوصول إلى سلاسل الرسائل. إذا كان عادلًا صحيحًا، فسيتم منح الوصول إلى سلاسل الرسائل المنتظرة بالترتيب الذي طلبته به. إذا كان خطأ، فسيتم تحديد الترتيب بواسطة برنامج جدولة الخيط.
class Philosopher extends Thread {
private Semaphore sem;
// Did the philosopher eat?
private boolean full = false;
private String name;
Philosopher(Semaphore sem, String name) {
this.sem=sem;
this.name=name;
}
public void run()
{
try
{
// If the philosopher has not eaten
if (!full) {
// Ask the semaphore for permission to run
sem.acquire();
System.out.println(name + " takes a seat at the table");
// The philosopher eats
sleep(300);
full = true;
System.out.println(name + " has eaten! He leaves the table");
sem.release();
// The philosopher leaves, making room for others
sleep(300);
}
}
catch(InterruptedException e) {
System.out.println("Something went wrong!");
}
}
}
وإليك الكود لتشغيل برنامجنا:
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore(2);
new Philosopher(sem, "Socrates").start();
new Philosopher(sem,"Plato").start();
new Philosopher(sem,"Aristotle").start();
new Philosopher(sem, "Thales").start();
new Philosopher(sem, "Pythagoras").start();
}
}
لقد أنشأنا إشارة تم ضبط عدادها على 2 لتحقيق الشرط: يمكن لفيلسوفين فقط تناول الطعام في نفس الوقت. أي أنه يمكن تشغيل خيطين فقط في نفس الوقت، لأن فئة الفيلسوف لدينا ترث Thread ! تتحكم طريقتا الاكتساب () والإصدار () لفئة الإشارة في عداد الوصول الخاص بها. تطلب طريقة الاكتساب () من الإشارة الوصول إلى المورد. إذا كان العداد > 0، فسيتم منح الوصول وتقليل العداد بمقدار 1. "تحرر" طريقة الإصدار () الوصول الممنوح مسبقًا، وتعيده إلى العداد (يزيد عداد الوصول للإشارة بمقدار 1). ماذا نحصل عندما نقوم بتشغيل البرنامج؟ هل تم حل المشكلة؟ أفلا يقاتل فلاسفتنا وهم ينتظرون دورهم؟ :) إليك مخرجات وحدة التحكم التي حصلنا عليها:
Socrates takes a seat at the table
Plato takes a seat at the table
Socrates has eaten! He leaves the table
Plato has eaten! He leaves the table
Aristotle takes a seat at the table
Pythagoras takes a seat at the table
Aristotle has eaten! He leaves the table
Pythagoras has eaten! He leaves the table
Thales takes a seat at the table
Thales has eaten! He leaves the table
لقد فعلناها! وعلى الرغم من أن طاليس كان عليه أن يتناول العشاء بمفرده، فلا أعتقد أننا أهنناه :) ربما لاحظت بعض أوجه التشابه بين كائن المزامنة (mutex) والإشارة المرورية. في الواقع، لديهم نفس المهمة: مزامنة الوصول إلى بعض الموارد. والفرق الوحيد هو أنه يمكن الحصول على كائن المزامنة (mutex) الخاص بالكائن من خلال مؤشر ترابط واحد فقط في المرة الواحدة، بينما في حالة الإشارة، التي تستخدم عداد الخيوط، يمكن لعدة سلاسل رسائل الوصول إلى المورد في وقت واحد. هذه ليست مجرد صدفة :) كائن المزامنة (mutex) هو في الواقع إشارة ذات عدد 1. وبعبارة أخرى، إنها إشارة يمكنها استيعاب خيط واحد. تُعرف أيضًا باسم "الإشارة الثنائية" لأن عدادها يمكن أن يحتوي على قيمتين فقط - 1 ("غير مقفل") و0 ("مقفل"). هذا كل شيء! كما ترون، فإن الأمر ليس مربكًا للغاية بعد كل شيء :) الآن، إذا كنت ترغب في دراسة تعدد مؤشرات الترابط بمزيد من التفاصيل على الإنترنت، فسيكون من الأسهل عليك التنقل بين هذه المفاهيم. نراكم في الدروس القادمة!
GO TO FULL VERSION