Queue

  • فارق بين Queue وDeque

    في عالم البرمجة بلغة جافا، يشير مصطلح “Queue” إلى هيكل بيانات يتيح إدخال العناصر في نهاية الصف وإزالتها من بدايته، بينما “Deque” (Double Ended Queue) يمثل هيكل بيانات يتيح إدخال وإزالة العناصر من كلا الطرفين للصف.

    على الرغم من أن كل منهما يقدم وظائف مشابهة، إلا أنهما يختلفان في بعض النقاط الأساسية، والتي يمكن أن تؤثر على أداء التطبيقات التي تستخدمهما.

    لنبدأ بالفارق بينهما:

    1. التصميم والوظيفة:

      • Queue: يتميز بتصميم يسمح بإدخال العناصر في نهاية الصف (وتسمى هذه العملية “الإنضمام” أو “الإدراج”) وإزالتها من بدايته (التي تعرف باسم “الاستخراج”).
      • Deque: يتميز بتصميم يسمح بإدخال وإزالة العناصر من كلا الطرفين للصف، مما يتيح مرونة أكبر في تنفيذ العمليات.
    2. الأداء:

      • Queue: عمليات الإضافة والإزالة في نهاية الصف تعمل بشكل فعّال بمرافقة هذا التصميم.
      • Deque: يمكن أن يكون أداء Deque أكثر تعقيدًا بعض الشيء بسبب مرونته الزائدة، حيث يحتاج تنفيذه إلى تحكم إضافي لضمان عدم تضارب العمليات المتزامنة.
    3. الاستخدامات:

      • Queue: يستخدم عادة في السيناريوهات التي تتطلب تنظيم العناصر وتحديد ترتيب الوصول إليها وفقًا لترتيب الوصول.
      • Deque: يُفضل استخدامه في السيناريوهات التي تتطلب عمليات الإضافة والإزالة من كلا الطرفين، مثل تنفيذ الخوارزميات الرياضية المتقدمة أو تنفيذ الهياكل البيانية المعقدة.

    من الناحية العامة، يمكن القول إن Queue هو الأكثر شيوعًا واستخدامًا بين الاثنين، ويعتبر Deque خيارًا متقدمًا يتيح مرونة أكبر لتنفيذ العمليات المتعددة. ومع ذلك، يجب اختيار الهيكل المناسب وفقًا لاحتياجات التطبيق المحددة، وفي العديد من الحالات، سيكون Queue هو الخيار الأمثل لمعظم التطبيقات.

    المزيد من المعلومات

    بالطبع، دعني أوسع النقاش حول الاختلافات بين Queue وDeque وأضيف بعض المعلومات الإضافية:

    1. هيكل البيانات:

      • Queue: غالبًا ما يتم تنفيذه باستخدام هياكل بيانات مثل LinkedList أو ArrayDeque في جافا.
      • Deque: يمكن تنفيذه أيضًا باستخدام LinkedList أو ArrayDeque، ولكن يمكن أيضًا استخدامه باستخدام هياكل بيانات مخصصة لدعم الإضافة والإزالة من الطرفين، مثل Doubly Linked List.
    2. عمليات الوصول:

      • Queue: يتم الوصول إلى عناصر Queue بشكل افتراضي من البداية (رأس الصف) عبر عمليات الإزالة، ومن النهاية (ذيل الصف) عبر عمليات الإضافة.
      • Deque: يمكن الوصول إلى عناصر Deque من كلا الطرفين، مما يوفر مرونة أكبر في استخدامه ويسمح بتنفيذ عمليات الإضافة والإزالة بشكل أكثر تنوعًا.
    3. وظائف إضافية:

      • Queue: توفير واجهة أساسية لتنفيذ FIFO (First-In-First-Out)، مما يجعله مفيدًا في العديد من السيناريوهات مثل إدارة الوظائف وتنفيذ الخوارزميات.
      • Deque: بالإضافة إلى وظائف FIFO، يمكن استخدام Deque أيضًا كـ LIFO (Last-In-First-Out)، حيث يمكن استخدامه كـ Stack أيضًا.
    4. التكامل مع واجهات أخرى:

      • Queue: قد تكون واجهة Queue جزءًا من تصميمات الأنظمة الأكثر تعقيدًا مثل Priority Queue التي تقدم طريقة لترتيب العناصر بناءً على أولويات محددة.
      • Deque: يمكن استخدام Deque كجزء من هياكل بيانات أكثر تعقيدًا مثل HashMaps أو Trees لتوفير واجهة للوصول إلى العناصر بطريقة متنوعة.

    باختصار، Queue وDeque هما هياكل بيانات قوية ومفيدة في برمجة جافا، وتختلف في التصميم والوظائف والأداء. اختيار الهيكل المناسب يعتمد على متطلبات التطبيق المحددة والعمليات التي تحتاج إلى تنفيذها.

  • تحويل كود C++ إلى Java: فهم وتوضيح

    في بداية الأمر، يتعين عليك فهم أن الشيفرة التي قدمتها هي كود برمجي مكتوب بلغة C++، وتقوم بحساب العدد القبيح الذي يأتي في الترتيب الذي يحدده المستخدم. الآن سأقوم بشرح الجزء البارز في الشيفرة ومحاولة تحويله إلى لغة Java.

    أولاً، دعونا نتناول الأسئلة التي وردت في استفسارك:

    1. u = getNextUglyNumber(twoQ, threeQ, fiveQ, &q); //and what is &q, the &?

      هنا يتم استدعاء دالة getNextUglyNumber ويتم تمرير قيم twoQ، threeQ، و fiveQ كمتغيرات من نوع Queue، وتمرير &q بشكل يشير إلى المتغير q بواسطة الرمز &، وهذا يعني أن الدالة ستقوم بتعديل قيمة q داخل الدالة.

    2. twoQ.push(u << 1); //what is << ?

      في C++، عامل الإزاحة اليسار << يُستخدم لتحقيق عملية إزاحة للبتات. في هذه الحالة، يتم ضرب u في 2 مع إزاحة جميع البتات إلى اليسار بقيمة واحدة. يمكن تحويل هذه الخطوة إلى Java بإستخدام عامل الإزاحة اليسار << أيضًا.

    3. std::cout << u << ' '; //this i dont understand at all

      هذا السطر يقوم بطباعة قيمة u إلى الإخراج القياسي (stdout) باستخدام std::cout، ويتم وضع فراغ بعد القيمة المطبوعة. في Java، يمكن استخدام System.out.print(u + " "); لتحقيق نفس النتيجة.

    4. *q = 2; // same as q = 2*q?

      هنا يتم تعيين قيمة 2 إلى المؤشر q. لذا، ليس هو نفسه كتعبير q = 2 * q، بل هو مجرد تعيين قيمة 2 للمتغير الذي يشير إليه المؤشر.

    5. if (fiveQ.front() < u) { u = fiveQ.front(); //whats front?

      front() هو دالة في STL (Standard Template Library) في C++ تستخدم للوصول إلى العنصر الأول في الطابور (queue)، وهنا يتم فحص إذا كان القيمة الأولى في fiveQ أقل من u، ثم يتم تحديث u بقيمة العنصر الأول في fiveQ.

    الشيفرة التالية توضح كيف يمكن تحويل بعض هذه الأكواد إلى Java:

    java
    import java.util.LinkedList; import java.util.Queue; public class UglyNumberCalculator { public static int findNthUglyNumber(int n) { Queue twoQ = new LinkedList<>(); Queue threeQ = new LinkedList<>(); Queue fiveQ = new LinkedList<>(); twoQ.add(2); threeQ.add(3); fiveQ.add(5); int u, q; while (n > 0) { u = getNextUglyNumber(twoQ, threeQ, fiveQ, q); switch (q) { case 2: twoQ.add(u << 1); break; case 3: threeQ.add(u << 2 - u); break; case 5: fiveQ.add(u << 2 + u); break; } n--; System.out.print(u + " "); } return u; } public static int getNextUglyNumber(Queue twoQ, Queue threeQ, Queue fiveQ, int[] q) { int u = twoQ.peek(); q[0] = 2; if (threeQ.peek() < u) { u = threeQ.peek(); q[0] = 3; } if (fiveQ.peek() < u) { u = fiveQ.peek(); q[0] = 5; } switch (q[0]) { case 2: twoQ.poll(); break; case 3: threeQ.poll(); break; case 5: fiveQ.poll(); break; } return u; } public static void main(String[] args) { int n = 10; // يمكنك تغيير قيمة n حسب رغبتك findNthUglyNumber(n); } }

    يرجى مراجعة الشيفرة بعناية، ولاتنسى أن تعديل القيم وفقًا لمتطلباتك الخاصة.

    المزيد من المعلومات

    بالطبع، سنواصل فهم الشيفرة وتوضيح المزيد من المعلومات. دعونا نتناول بعض الجوانب الأخرى المهمة في الشيفرة:

    1. typedef std::queue Queue;

      في هذا السياق، يتم استخدام typedef لإعادة تسمية نوع البيانات std::queue إلى Queue. هذا يسهل الاستفادة من الكود بشكل أكثر وضوحًا وقراءة.

    2. case 2: twoQ.push(u << 1); // u * 2

      في هذا الجزء، عندما يكون q يساوي 2، يتم إضافة u * 2 إلى طابور twoQ. هذا يمثل تضاعف القيمة u بواسطة الإزاحة اليسار (<< 1).

    3. case 3: threeQ.push(u << 2 - u); // u * 3

      عندما يكون q يساوي 3، يتم إضافة u * 3 إلى طابور threeQ. هنا، يتم استخدام الإزاحة اليسار (<< 2 - u) لتحقيق تأثير ضرب القيمة u في 3.

    4. case 5: fiveQ.push(u << 2 + u); // u * 5

      عندما يكون q يساوي 5، يتم إضافة u * 5 إلى طابور fiveQ. يستخدم الإزاحة اليسار (<< 2 + u) لتحقيق ذلك.

    5. if (threeQ.front() < u) { u = threeQ.front(); *q = 3; }

      هنا يتم فحص إذا كان القيمة الأولى في threeQ أقل من u، إذا كانت الشروط تتحقق، يتم تحديث u بقيمة العنصر الأول في threeQ ويتم تعيين q إلى 3.

    6. if (fiveQ.front() < u) { u = fiveQ.front(); *q = 5; }

      يتم هنا أيضًا فحص إذا كانت القيمة الأولى في fiveQ أقل من u، إذا كانت الشروط تتحقق، يتم تحديث u بقيمة العنصر الأول في fiveQ ويتم تعيين q إلى 5.

    7. int getNextUglyNumber(Queue &twoQ, Queue &threeQ, Queue &fiveQ, int &q)

      يتم تعريف دالة getNextUglyNumber التي تستخدم للحصول على العدد القبيح التالي. تأخذ الدالة ثلاث طوابير كمدخلات وتقوم بتحديث المؤشر q بناءً على الطابور الذي يحتوي على أقل قيمة.

    هذه هي بعض الملاحظات الإضافية حول الشيفرة. إذا كنت بحاجة إلى مزيد من التوضيح حول جزئيات محددة أو إذا كنت ترغب في فهم مفاهيم برمجية أخرى، يرجى إعلامي.

  • تنظيم تنفيذ وظائف Python باستخدام Multiprocessing وQueue

    عندما نتعامل مع سيناريوهات تشغيل الوظائف في لغة البرمجة Python، يُعد استخدام المكتبة المدمجة Queue خيارًا ممتازًا لتحقيق التفاعل بين وظائف متعددة. لكن يجب أولاً أن نعدل الكود الحالي ليتناسب مع هذا النهج.

    للبداية، يجب أن نستخدم الدالة Queue لتبادل البيانات بين الوظائف. سنقوم بإنشاء اثنين من الوظائف الرئيسية، الأولى لقراءة الملف وإنشاء قائمة النطاقات، والثانية للتصفح والتقاط الصور. سيتم تشغيل الثانية بعد اكتمال الأولى باستخدام Queue.

    قد يكون الكود على النحو التالي:

    python
    import Queue from multiprocessing import Process def domain_create(target_site_list, queue): for site in target_site_list.readlines(): site = site.strip() url = "http://" + site domain_list = open("LiveSite.txt", "a") domain_list.write(url + "\n") domain_list.close() queue.put("Domain Creation Completed") def site_browser(queue): target_site = "LiveSite.txt" tar = open(target_site, "rb") for link in tar.readlines(): link = link.strip() url_sites = "http://www." + link # Add your browser automation code here # ... queue.put("Site Browsing Completed") if __name__ == "__main__": target_site_file = "target_list.txt" target_site_list = open(target_site_file, "rb") # Create a queue to communicate between processes communication_queue = Queue.Queue() # Create and start the domain creation process domain_process = Process(target=domain_create, args=(target_site_list, communication_queue)) domain_process.start() # Wait for the domain creation to complete domain_process.join() # Start the site browsing process site_process = Process(target=site_browser, args=(communication_queue,)) site_process.start() # Wait for the site browsing to complete site_process.join()

    يتم تشغيل الكود الخاص بالقراءة وإنشاء النطاقات في عملية منفصلة، وعندما يتم الانتهاء من ذلك، يتم تشغيل الوظيفة الثانية للتصفح. الاستفادة من مكتبة multiprocessing تسمح لنا بتنظيم التنفيذ المتزامن للوظائف ومشاركة البيانات بينها باستخدام الطابور Queue.

    المزيد من المعلومات

    في هذا السياق، يمكننا التفصيل أكثر حول بعض الجوانب المهمة لفهم الكود وتنفيذه بشكل فعال.

    1. استخدام multiprocessing و Queue:

      • تعتبر multiprocessing هي واحدة من مكتبات Python التي تُستخدم لتحقيق التعددية في التنفيذ، وتسمح بتشغيل عمليات متعددة في نفس الوقت.
      • Queue هي هيكل بيانات يسمح بمشاركة البيانات بين عمليات Python. في هذا السياق، يتم استخدامه للإشارة إلى اكتمال العمليات وبدء العمليات التالية.
    2. التحكم في تدفق البرنامج باستخدام if __name__ == "__main__":

      • يستخدم هذا السطر للتحقق مما إذا كان البرنامج يعمل كبرنامج رئيسي أم لا. يتجنب ذلك مشكلات الاستمرارية عند استخدام التعددية في Python.
    3. عمليات الكتابة والقراءة في الملفات:

      • يتم استخدام open لفتح الملفات بنمط القراءة ("rb") والكتابة ("a")، حيث يتم إضافة "\n" للتحقق من أن كل عنصر يتم كتابته في سطر منفصل في الملف.
    4. التحكم في العمليات:

      • يتم استخدام join للانتظار حتى اكتمال العمليات الفرعية (العمليات) قبل الانتقال إلى الخطوة التالية، مما يضمن تنظيم التنفيذ وتجنب الاشتباكات.
    5. تشغيل البرنامج:

      • عند تشغيل البرنامج، يتم فتح ملف target_list.txt وتمريره إلى العملية الأولى (domain_create)، ثم تنتظر حتى اكتمالها. بعد ذلك، تبدأ العملية الثانية (site_browser) وتنتظر حتى اكتمالها.

    هذا التقسيم والتنظيم يجعل الكود أكثر فهمًا وسهولة صيانة، كما يُمكن التحكم بشكل أفضل في تسلسل تنفيذ الوظائف بما يتيح للمبرمج التحكم في تدفق البرنامج بشكل فعال.

  • تحسين أداء asyncio: تصميم فعّال للاستهلاك والمعالجة

    عندما نتعامل مع تصميم نظام يعتمد على asyncio في Python، يصبح من المهم فهم كيفية إدارة وتنظيم العمليات بشكل فعّال. في الكود الذي قدمته، يتم استخدام asyncio.Protocol لاستقبال البيانات من الخادم وتخزينها في asyncio.Queue. الآن، نريد تصميم coroutine آخر مسؤول عن استهلاك البيانات من الطابور ومعالجتها.

    أولاً، يمكنك استخدام asyncio.Task لتشغيل الcoroutine الخاص بالاستهلاك. يمكنك إنشاء asyncio.Task باستخدام loop.create_task كما في المثال التالي:

    python
    async def consumer(): while True: message = await q.get() # قم بتنفيذ المعالجة الخاصة بالرسالة هنا # ... consumer_task = loop.create_task(consumer())

    ثم، يمكنك تشغيل المهمة باستخدام loop.run_forever() بدلاً من loop.run_until_complete. هذا سيحافظ على تشغيل الاستهلاك بشكل دائم:

    python
    loop.run_forever()

    فيما يتعلق بالقلق بشأن انخفاض البيانات في الطابور، يمكنك استخدام asyncio.Queue.get_nowait() بدلاً من asyncio.Queue.get() للحصول على الرسائل دون الانتظار في حالة عدم وجود بيانات في الطابور. يمكنك فحص إذا ما كانت الرسالة فارغة للتحقق من وجود بيانات قبل تنفيذ أي عمليات:

    python
    async def consumer(): while True: try: message = q.get_nowait() if message: # قم بتنفيذ المعالجة الخاصة بالرسالة هنا except asyncio.QueueEmpty: await asyncio.sleep(1) # انتظر لفترة زمنية معينة إذا كانت الطابور فارغة

    بالنسبة لتحسين الرمز وتجنب استخدام متغير عام للطابور، يمكنك تمرير الطابور كمعامل إلى الcoroutine بدلاً من استخدام متغير عام. هذا يجعل الرمز أكثر تنظيمًا وقابلية للاستخدام.

    المزيد من المعلومات

    بالطبع، دعونا نستمر في توسيع المعلومات حول تصميم وتنظيم الكود بشكل أفضل.

    للبداية، يُفضل دائمًا استخدام asyncio.ensure_future بدلاً من loop.create_task لإطلاق المهام. هذا يجعل الرمز أكثر وضوحًا ويوفر قابلية التبديل بين تقنيات التشغيل في المستقبل.

    python
    consumer_task = asyncio.ensure_future(consumer())

    فيما يتعلق بالقلق بشأن توقف الconsumer بسبب قلة البيانات في الطابور، يمكنك استخدام asyncio.sleep للانتظار لفترة زمنية قبل استعادة المحاولة. هذا يسمح بالتحكم الفعّال في كيفية التعامل مع حالة الطابور الفارغة.

    python
    async def consumer(): while True: try: message = q.get_nowait() if message: # قم بتنفيذ المعالجة الخاصة بالرسالة هنا except asyncio.QueueEmpty: await asyncio.sleep(1) # انتظر لفترة زمنية معينة قبل المحاولة مرة أخرى

    كما يمكن تحسين أداء الرمز من خلال استخدام asyncio.gather لتشغيل المهام بشكل متزامن إذا كان هناك عدة مهام يمكن تنفيذها بشكل مستقل.

    python
    async def main(): # ... قم بتشغيل الاتصال والاستهلاك هنا await asyncio.gather(consumer_task, other_task1(), other_task2()) loop.run_until_complete(main())

    في النهاية، يُشدد على أهمية وضوح الرمز وقابليته للصيانة. تنظيم الكود بشكل جيد يجعل فهمه وتطويره أسهل للفريق ولأي مطور آخر قد يعمل على البرنامج في المستقبل.

  • تنفيذ مشكلة المنتج والمستهلك باستخدام خيوط في جافا

    مشكلتك تتعلق بتنفيذ مشكلة المنتج والمستهلك باستخدام خيوط في لغة البرمجة جافا، حيث يتشارك المنتجون والمستهلكون نفس المصفوفة كمساحة تخزين مشتركة. يتوجب عليك تصميم حلاً يتيح للمنتجين والمستهلكين الوصول إلى المصفوفة وتحديثها بطريقة آمنة.

    أولاً وقبل كل شيء، يجب عليك تعريف المصفوفة كمتغير عام (global variable)، بحيث يمكن لكل من المنتجين والمستهلكين الوصول إليها. يمكنك تحقيق ذلك عبر تعريف المصفوفة خارج الكلاس الرئيسي وعلامة static للتأكيد على أنها مشتركة بين جميع الكائنات. على سبيل المثال:

    java
    public class Main { // المصفوفة المشتركة بين المنتجين والمستهلكين static int[] buffer; public static void main(String[] args) { // ... رمز لتحديد قيم m و n و k // تهيئة المصفوفة بحجم k buffer = new int[k]; // ... إنشاء وتشغيل المنتجين والمستهلكين } }

    بعد تعريف المصفوفة، يمكنك إنشاء كلاسين للمنتج والمستهلك، حيث يستخدم كل منهما المصفوفة المشتركة. سأقوم بتوضيح كيف يمكنك البدء في تنفيذ ذلك:

    java
    import java.util.Random; class Producer implements Runnable { private int id; private Random random = new Random(); public Producer(int id) { this.id = id; } @Override public void run() { // ... رمز لتكوين المنتج try { while (true) { // ... رمز لإضافة عنصر إلى المصفوفة int value = random.nextInt(10); putInBuffer(value); System.out.println("Producer #" + id + " put: " + value); // ... رمز للنوم لفترة عشوائية Thread.sleep(random.nextInt(1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void putInBuffer(int value) throws InterruptedException { // ... رمز لإضافة عنصر إلى المصفوفة بشكل آمن } } class Consumer implements Runnable { private int id; private Random random = new Random(); public Consumer(int id) { this.id = id; } @Override public void run() { // ... رمز لتكوين المستهلك try { while (true) { // ... رمز لاستخراج عنصر من المصفوفة int value = getFromBuffer(); System.out.println("Consumer #" + id + " got: " + value); // ... رمز للنوم لفترة عشوائية Thread.sleep(random.nextInt(1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized int getFromBuffer() throws InterruptedException { // ... رمز لاستخراج عنصر من المصفوفة بشكل آمن } }

    يرجى ملاحظة أنه يجب عليك تحديد الكود المتعلق بإضافة واستخراج العناصر من المصفوفة بشكل آمن باستخدام المزامنة (synchronization) لتجنب مشاكل التنافس. كما يجب عليك تكملة الرموز المشار إليها بتناسق مع احتياجات برنامجك.

    المزيد من المعلومات

    بالطبع، دعنا نوسع على الحلاقة التي قدمتها سابقًا. في مشكلة المنتج والمستهلك، يتعامل المنتجون بإنتاج عناصر ووضعها في المصفوفة، بينما يقوم المستهلكون بسحب (استهلاك) العناصر من المصفوفة. يجب ضمان أن عمليات الإضافة والاستهلاك تتم بشكل آمن وغير تزامني لتجنب مشاكل التنافس والتجاوز.

    للقيام بذلك، يُستخدم الكلمة الرئيسية synchronized لتزامن الوصول إلى المصفوفة في الكلاسين Producer و Consumer. تمثل كل عملية إضافة (putInBuffer) واستخراج (getFromBuffer) عملية حساسة للتزامن، مما يضمن أنها لن تتعرض للتنافس عند الوصول إلى المصفوفة المشتركة.

    علاوة على ذلك، يمكنك استخدام طابور (Queue) لتبسيط التعامل مع المصفوفة وتجنب الحاجة إلى تنفيذ خطط خاصة للإضافة والاستهلاك. في هذا السياق، يمكنك استخدام LinkedList كهيكل للطابور.

    هنا مثال يُظهر كيفية تحسين الكود باستخدام الطابور:

    java
    import java.util.LinkedList; import java.util.Queue; import java.util.Random; class Producer implements Runnable { private int id; private Random random = new Random(); private Queue buffer; private int maxSize; public Producer(int id, Queue buffer, int maxSize) { this.id = id; this.buffer = buffer; this.maxSize = maxSize; } @Override public void run() { try { while (true) { int value = random.nextInt(10); putInBuffer(value); System.out.println("Producer #" + id + " put: " + value); Thread.sleep(random.nextInt(1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void putInBuffer(int value) throws InterruptedException { while (buffer.size() == maxSize) { wait(); // انتظار حتى يتاح المزيد من المساحة في المصفوفة } buffer.offer(value); notifyAll(); // إعلام المستهلكين بأن هناك عنصرًا جديدًا في المصفوفة } } class Consumer implements Runnable { private int id; private Random random = new Random(); private Queue buffer; public Consumer(int id, Queue buffer) { this.id = id; this.buffer = buffer; } @Override public void run() { try { while (true) { int value = getFromBuffer(); System.out.println("Consumer #" + id + " got: " + value); Thread.sleep(random.nextInt(1000)); } } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized int getFromBuffer() throws InterruptedException { while (buffer.isEmpty()) { wait(); // انتظار حتى يتوفر عنصر في المصفوفة } int value = buffer.poll(); notifyAll(); // إعلام المنتجين بأن هناك مساحة في المصفوفة return value; } } public class Main { public static void main(String[] args) { int m = 2; // عدد المنتجين int n = 3; // عدد المستهلكين int k = 5; // حجم المصفوفة Queue buffer = new LinkedList<>(); // المصفوفة المشتركة // إنشاء وتشغيل المنتجين والمستهلكين for (int i = 1; i <= m; i++) { new Thread(new Producer(i, buffer, k)).start(); } for (int i = 1; i <= n; i++) { new Thread(new Consumer(i, buffer)).start(); } } }

    هذا الكود يُظهر استخدام الطابور (Queue) لتبسيط تنظيم المصفوفة، ويستخدم wait و notifyAll للتحكم في الوصول المتزامن للمصفوفة. يتيح هذا الحلا للمنتجين والمستهلكين التفاعل بشكل آمن مع المصفوفة المشتركة.

زر الذهاب إلى الأعلى
إغلاق

أنت تستخدم إضافة Adblock

يرجى تعطيل مانع الإعلانات حيث أن موقعنا غير مزعج ولا بأس من عرض الأعلانات لك فهي تعتبر كمصدر دخل لنا و دعم مقدم منك لنا لنستمر في تقديم المحتوى المناسب و المفيد لك فلا تبخل بدعمنا عزيزي الزائر