توزيع Freebie: خيوط غير كبح في جافا. المنوال المشروع

هل تريد في خيوط جافا التي لا تأكل الذاكرة كما لو أنها ليست في حد ذاتها ولا تبطئ؟ ائتمان جيد ، وهذه المسألة تجيب على هذا السؤال.


نفسر عمل Project Loom على علب البيتزا! تعال!


كل هذا تمت إزالته وكتابته خصيصًا لحبر .





علامات النداء


هل ترى هذه الصورة غالبًا على خدمة الويب الخاصة بك: في البداية كان كل شيء على ما يرام ، ثم جاء إليك مليون صيني ، وأنتجت الخدمة مليون خيط وغرقوا في الجحيم؟





هل تريد مثل هذه الصورة الجميلة؟





وبعبارة أخرى ، هل تريد في خيوط جافا التي لا تأكل الذاكرة ولكنها ليست في حد ذاتها ولا تبطئ؟ ائتمان جيد ، وهذه المسألة تجيب على هذا السؤال.


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


أولا ، دقيقتين إلى مقل العيون وإخلاء المسؤولية والقمامة الأخرى ، والتي يجب أن يقال. يمكنك تخطيه إذا كنت كسولًا جدًا.


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


ولكن هناك هدف إضافي. نتحدث باستمرار عن coroutines في Kotlin. في الآونة الأخيرة كانت هناك مقابلات مع روما اليزاروف ، إله كوروتين ، ومع باشا فينكلشتاين ، الذي سيكتب لهم خلفية. قريبا سيكون هناك مقابلة مع أندريه بريسلاف - والد Kotlin. وفي كل مكان يتم ذكر مشروع Loom بطريقة أو بأخرى ، لأنه تناظري من coroutine. وإذا كنت لا تعرف ما هو Loom ، فقد تشعر بالغباء عند قراءة هذه المقابلات. هناك بعض الرجال الرائعون ، يناقشون أشياء رائعة. وها أنت ، وأنت لست معهم ، أيها الأحمق. هذا غبي جدا.


لا تفعل هذا ، اقرأ ما هو Loom في هذه المقالة ، أو شاهد هذا الفيديو أكثر ، سأشرح كل شيء.


لذا ، ما هو التعقيد. هناك مثل هذا الرجل ، رون بريسلر.






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


هناك الكثير من الجحافل التي لا تفعل شيئًا ، قل فقط. حسنًا ، افهم الأمر جيدًا ، أنا على ما يرام. أو هناك أشخاص يبدو أنهم مهندسون رائعون ، ولكن بشكل عام في اللاوعي ، يقولون ما يلي: "في جافا ، تحتاج إلى تحسين الخيوط". ما لتحسين؟ ما هي المواضيع؟


الناس كسالى بشكل عام بحيث لا يمكنهم التفكير.


مثل النكتة:
بتكا وفاسيلي إيفانوفيتش يطيران على متن طائرة.
يسأل فاسيلي إيفانوفيتش: - Petka والأجهزة؟
ردود Petka: - 200!
فاسيلي إيفانوفيتش: - وماذا عن 200؟
بيتكا: - وماذا عن الأجهزة؟


سأحكي قصة. كنت في أوكرانيا هذا الربيع ، سافرنا من روسيا البيضاء (هل تفهم سبب استحالة مباشرة من سان بطرسبرغ). وفي الجمارك جلسنا لمدة ساعتين تقريبًا. ضباط الجمارك لطيفون للغاية ، بكل جدية سئلوا عما إذا كانت Java تقنية قديمة. جلس الناس القريبين الذين يطيرون إلى نفس الكونف. وأنا نوع من المتحدثين ، يجب أن أقوم بالوقوف والوقوف ، وكما هو متوقع ، أتحدث بلا خجل عن أشياء لا أستخدمها على الإطلاق. وعلى طول الطريق تحدث عن توزيع JDK المسمى Liberica ، هذا هو JDK لـ Raspberry Pi.


وما رأيك. لا يمر حتى ستة أشهر قبل أن يطرق الناس عربة التسوق ويقولون ، انظروا ، لقد وضعنا حلاً في Liber on prod ، ولدي بالفعل تقرير عن هذا في jfuture.by konf البيلاروسية. هذا هو النهج. هذا ليس مبشراً رديئاً ، بل رجل عادي ، مهندس عادي.


بالمناسبة ، سيكون لدينا قريبًا مؤتمر جوكر 2018 ، والذي سيشمل كلا من أندريه بريسلاف (يفتش بوضوح من خلال coroutines) ، و Pasha Finkelshtein ، ويمكن سؤال جوش لونج عن دعم Spring لـ Loom. حسنا ، ومجموعة من الخبراء البارزين ، هيا!

والآن نعود إلى الخيوط. يحاول الناس استخلاص الأفكار من خلال خليتي عصبتين متدهورتين ، ويلتفان على قبضتهما ، ويتمتمان: "الخيوط ليست كذلك في جاوا ، الخيوط ليست كذلك في جاوا". الأجهزة! ما الأجهزة؟ هذا هو الجحيم بشكل عام.


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


جنبا إلى جنب مع العرض التوضيحي ، تحدث بريسلر في المؤتمر وأصدر هذا الفيديو:



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


تدور المناقشة حول ثلاثة مواضيع مؤلمة:


  • استمرار
  • الألياف
  • مكالمات الذيل

على الأرجح ، كان مريضًا جدًا من خلال رؤية الكوازار والقتال مع مواطن الخلل التي لا توجد بها قوة - تحتاج إلى دفعها في وقت التشغيل.


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






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


العدد


يضطر المستخدمون غير الراضين ، مطوري التطبيقات ، عندما يقومون بإنشاء API ، للاختيار بين كرسيين. القمم مبنية على كرسي واحد ، وتنمو الأزهار على الأخرى. ولا أحد ولا الآخر يناسبنا.






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


(حسنًا ، نعم ، لها علاقة بحقيقة أن المقابس في Java مرتبة بشكل مغفل ، ولكن هذا موضوع لمحادثة أخرى)


تخيل أنك تكتب نوعًا من MMO.






على سبيل المثال ، خلال الحرب الشمالية في EVE Online ، تجمع ألفان وأربعمائة طيار في نقطة واحدة في الفضاء ، كل واحد منهم - بشكل مشروط ، سواء كان مكتوبًا في Java - لن يكون خيطًا واحدًا ، بل عدة. والرائد ، بالطبع ، هو منطق عمل معقد ، وليس إصدار أي HTML يمكن استبعاده يدويًا في عدسة مكبرة.


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


على الرغم من أنني ربما أذكر EVE كمثال دون جدوى ، لأنه ، بقدر ما أفهم ، كل شيء مكتوب في Python ، وفي Python مع multithreading لا يزال أسوأ من لدينا - ويمكننا النظر في منافسة ضعيفة لميزات اللغة. لكن المثال واضح مع الصور.


إذا كنت مهتمًا بموضوع المنظمة البحرية الدولية بشكل عام وتاريخ "الحرب الشمالية" على وجه الخصوص ، فقد ظهر مؤخرًا مقطع فيديو جيد جدًا حول هذا الموضوع على قناة Bulzhat (مهما كان الاسم) ، شاهد من الطابع الزمني الخاص بي.



نعود إلى الموضوع.


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


سألت الخبير ، الاكاديمي الشهير اسكوبار ، عن رأيه في هذا:






ماذا تفعل؟ يندفع ما يسمى faybers لإنقاذ.


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


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


عند مفترق الطرق


أول شيء أريد مناقشته. الناس لا يفهمون الفرق بين الاستمرارية والألياف.
الآن سيكون هناك تنوير عبادة!


سنعلن حقيقة: الاستمرارية والألياف شيئان مختلفان.


استمرار


يتم بناء الألياف فوق ميكانيكي يسمى Continuations.


الاستمرارية (بشكل أدق ، الاستمرارات المحددة) هي نوع من الحساب والتنفيذ ، وهو جزء من برنامج يمكن أن ينام ، ثم يستيقظ ويستمر في التنفيذ من المكان الذي نام فيه. يمكن أحيانًا استنساخه أو إجراء تسلسل له ، حتى أثناء نومه.


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


سأستخدم أحيانًا كلمة "مزاحمة" بدلاً من النسخة الإنجليزية من "محصول". مجرد كلمة "العائد" - إنها نوع من السوء حقًا. لذلك سيكون هناك "مزاحمة".


حتى هنا. من المهم جدًا ألا تكون هناك أي منافسة داخل السلسلة المتصلة. في حد ذاته هو الحد الأدنى من هذه العملية.


يمكنك التفكير في الاستمرار باعتباره Runnable ، والذي يمكنك من خلاله استدعاء طريقة pause() . إنها في الداخل ومباشرة ، لأن تعدد المهام لدينا تعاونية. ثم يمكنك تشغيله مرة أخرى ، وبدلاً من إعادة حساب كل شيء ، سيستمر من حيث توقف. هذا النوع من السحر. سنعود إلى السحر.


مكان الحصول على عرض توضيحي مع استمرار العمل - سنناقش في النهاية. الآن دعونا نتحدث عن ما هو موجود.


تقع فئة الاستمرارية نفسها في java.base ، وستكون جميع الروابط في الوصف. ( src/java.base/share/classes/java/lang/Continuation.java ). لكن هذه الفئة كبيرة جدًا ، كبيرة الحجم ، لذلك من المنطقي أن ننظر فقط إلى نوع من الضغط منه.


 public class Continuation implements Runnable { public Continuation(ContinuationScope scope, Runnable body); public final void run(); public static void yield(ContinuationScope scope); public boolean isDone(); protected void onPinned(Reason reason) { throw new IllegalStateException("Pinned: " + reason); } } 

لاحظ أنه في الواقع هذا الملف يتغير باستمرار. على سبيل المثال ، اعتبارًا من اليوم السابق ، لم تقم Runnable بتنفيذ واجهة Runnable . تعامل مع هذا كنوع من رسم.


نلقي نظرة على المنشئ. body - هذا هو الرمز الذي تحاول تشغيله ، scope - هو نوع من التخطي الذي يسمح لك بتداخل الاستمرارية في عمليات الاستمرارية.


وفقًا لذلك ، يمكنك إما جدولة هذا الرمز مسبقًا إلى النهاية باستخدام طريقة run ، أو استبداله ببعض الصفيف المحدد باستخدام طريقة yield (الصفيف مطلوب هنا لشيء مثل إجراءات إعادة التوجيه إلى معالجات متداخلة ، لكننا لا نهتم كمستخدمين). يمكنك أن تسأل باستخدام طريقة isDone إذا اكتمل كل شيء حتى النهاية.


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


يمكنك استخدام هذا بالطريقة التالية تقريبًا:


 Continuation cont = new Continuation(SCOPE, () -> { while (true) { System.out.println("before"); Continuation.yield(SCOPE); System.out.println("after"); } }); while (!cont.isDone()) { cont.run(); } 

هذا مثال من عرض بريسلر. مرة أخرى ، هذا ليس رمزًا "تافهًا" ، هذا نوع من الرسم التخطيطي.


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


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


والآن ، فوق التتالي ، يتم بناء الألياف.


الألياف


لذا ، ما يعني في حالتنا الألياف.

هذا نوع من التجريد ، وهو:


  • خيوط معالجة خفيفة الوزن في JVM نفسها ، وليس في نظام التشغيل ؛
  • مع النفقات العامة المنخفضة للغاية لإنشاء المهام والحفاظ عليها ، وتبديل المهام ؛
  • والتي يمكن تشغيلها ملايين المرات.

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


كحد أدنى ، يعرف JVM بالفعل كيفية التعامل مع سلاسل الرسائل بشكل جيد ، وكل ما نحتاجه هو تبسيط عملية الترميز للتعددية. يمكنك استخدام واجهات برمجة التطبيقات غير المتزامنة ، ولكن لا يمكن أن يطلق عليها "التبسيط": حتى استخدام أشياء مثل Reactor ، Spring Project Reactor ، التي تسمح بكتابة رمز خطي على ما يبدو ، لن يساعد كثيرًا إذا كنت بحاجة إلى تصحيح المشكلات المعقدة.


حتى الألياف.


تتكون الألياف من مكونين. هذا:


  • استمرار
  • المجدول

هذا هو:


  • استمرار
  • مخطط





يمكنك تحديد من هو المخطط هنا. أعتقد أن المخطط هنا هو جاي.


  • الألياف تلتف الكود الذي تريد تنفيذه بشكل مستمر
  • يقوم المجدول بتشغيلها على مجموعة من سلاسل عمليات الحامل

سأدعوهم خيوط الناقل.







يستخدم النموذج الأولي الحالي java.util.concurrent.Executor ForkJoinPool المجدول ForkJoinPool . لدينا كل شيء. قد يظهر شيء أكثر ذكاءً في المستقبل ، ولكن في الوقت الحالي ، مثل هذا.


كيف يتصرف الاستمرارية:


  • إنه مزدحم (العائد) عندما يحدث قفل (على سبيل المثال ، على IO) ؛
  • يستمر عندما يكون جاهزًا للمتابعة (على سبيل المثال ، اكتملت عملية الإدخال / الإخراج ، ويمكنك الانتقال).

الوضع الحالي للأعمال:


  • التركيز الرئيسي على الفلسفة والمفاهيم ؛
  • لم يتم إصلاح API ، هو "للعرض". هذا نموذج أولي للبحث ؛
  • هناك نموذج أولي مشفر جاهز للعمل من فئة java.lang.Fiber .

سيتم مناقشتها.


ما تم نشره بالفعل في الألياف:


  • يدير إطلاق مهمة.
  • وقوف السيارات دون وقوف السيارات على الناقل ؛
  • في انتظار الانتهاء من الألياف.

مخطط الدائرة


 mount(); try { cont.run(); } finally () { unmount(); } 

  • يمكننا تركيب ألياف على حامل خيط ؛
  • ثم قم بتشغيل الاستمرار.
  • وانتظر حتى تزاحم أو تتوقف بصدق ؛
  • في النهاية ، نترك الخيط دائمًا.

سيتم تنفيذ هذا الكود الزائف على برنامج ForkJoinPool أو على برنامج آخر (والذي سيكون في النهاية في النسخة النهائية).


استخدم في الواقع


 Fiber f = Fiber.execute( () -> { System.out.println("Good Morning!"); readLock.lock(); try { System.out.println("Good Afternoon"); } finally { readLock.unlock(); } System.out.println("Good Night"); }); 

انظر ، نحن نصنع أليافًا:


  • نرحب بالجميع ؛
  • نحن نعيق ظهوره مرة أخرى ؛
  • عند العودة ، مبروك على غداءك.
  • أخيراً حرر القفل ؛
  • وقل وداعا.

كل شيء بسيط للغاية.


نحن لا نتسبب في الازدحام مباشرة. يعلم Project Loom نفسه أنه عند readLock.lock(); يجب أن يتدخل وأن يقوم بالقمع ضمنياً. لا يرى المستخدم هذا ، ولكنه يحدث هناك.


مكدسات ، مكدسات في كل مكان!


دعنا نوضح ما يحدث باستخدام كومة البيتزا كمثال.


في البداية ، يكون مؤشر ترابط الحامل في حالة انتظار ، ولا يحدث شيء.





أعلى المكدس في الأعلى ، أذكر.


ثم تمت جدولة الألياف للتنفيذ ، وبدأت مهمة الألياف في العمل.





داخل نفسه ، من الواضح أنه يطلق استمرارًا ، حيث يوجد الرمز الحقيقي بالفعل.





من وجهة نظر المستخدم ، لم نقم بعد بإطلاق أي شيء هنا.


هذا هو الإطار الأول من رمز المستخدم الذي ظهر على المكدس ، وتم تمييزه باللون الأرجواني.


علاوة على ذلك ، يتم تنفيذ الشفرة وتنفيذها ، وفي مرحلة ما ، تحاول المهمة التقاط القفل والكتلة عليه ، مما يؤدي إلى الازدحام التلقائي.





يتم تخزين كل شيء موجود على كومة الاستمرارية في مكان سحري معين. ويختفي.





كما ترون ، يعود الدفق إلى الألياف ، وفقًا للتعليمات التي تلي Continuation.run . وهذه هي نهاية كود الألياف.


تنتهي مهمة الألياف ، ناقل الوسائط ينتظر وظيفة جديدة.





الألياف متوقفة ، في مكان ما تكمن ، التسلسل مزدحم بالكامل.


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


  • Reentrantlock.unlock
  • Locksupport.unpark
  • Fiber.unpark
  • ForkJoinPool.execute

ونعود بسرعة إلى المكدس ، الذي كان مؤخرًا.





علاوة على ذلك ، قد يكون خيط الناقل مختلفًا تمامًا. وهذا منطقي!


قم بتشغيل المتابعة مرة أخرى.





وهنا يأتي السحر !!! يتم استعادة المكدس ، ويستمر التنفيذ مع التعليمات بعد Continuation.yield .





نقوم بالزحف خارج القفل الذي تم إيقافه للتو ونبدأ في تنفيذ جميع الرموز المتبقية في المتابعة:





تنتهي مهمة المستخدم ، ويعود التحكم إلى مهمة الألياف مباشرة بعد تعليمة Continu.run





في نفس الوقت ، ينتهي تنفيذ الألياف ، ونجد أنفسنا مرة أخرى في وضع الاستعداد.





يبدأ الإطلاق التالي للألياف مرة أخرى الدورة الكاملة للولادات الموصوفة أعلاه.





أمثلة حية


ومن قال أن كل هذا يعمل؟ هل هذا عن اثنين من علامات microbench مكتوبة على المساء؟


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


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


المشاكل


هل هناك مشاكل هنا؟ نعم بالطبع! القصة كاملة مع faybers هي قصة عن المشاكل والمفاضلات المستمرة.


مشاكل فلسفية


  • هل نحن بحاجة إلى إعادة اختراع الخيوط؟
  • هل يجب أن تعمل جميع التعليمات البرمجية الموجودة بشكل صحيح داخل الألياف؟

يعمل النموذج الأولي الحالي مع قيود. والذي قد يدخل حيز التنفيذ ، على الرغم من أنني لا أريد ذلك. ومع ذلك ، فإن OpenJDK هو شيء يحترم التوافق اللانهائي.


ما هي القيود التقنية؟ القيود الأكثر وضوحًا هي قطعتان.


كانت المشكلة مرة واحدة - لا يمكنك استبدال الإطارات الأصلية


 PrivilegedAction<Void> pa = () -> { readLock.lock(); // may park/yield try { // } finally { readLock.unlock(); } return null; } AccessController.doPrivileged(pa); //native method 

هنا يستدعي doPrivileged الطريقة الأصلية.


يمكنك استدعاء doPrivileged ، والقفز من VM ، يظهر إطار أصلي على المكدس الخاص بك ، وبعد ذلك تحاول الوقوف على خط readLock.lock() . وفي تلك اللحظة ، سيتم تلطيخ خيط الناقل حتى يتم انتقاؤه. أي ، يختفي الخيط. في هذه الحالة ، قد تنتهي خيوط الحامل ، وبشكل عام ، هذا يكسر فكرة الألياف بالكامل.


طريقة حل ذلك معروفة بالفعل ، والمناقشات جارية حول هذا الموضوع.


المشكلة الثانية - الكتل المتزامنة


هذه قمامة أكثر خطورة


 synchronized (object) { //may park object.wait(); //may park } 

 synchronized (object) { //may park socket.getInputStream().read(); //may park } 

في حالة التقاط الشاشة في الألياف ، فإن خيوط الحامل تنطلق أيضًا.


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


API الموضوع؟ الموضوع. الخيط المحليين؟


في النموذج الأولي الحالي ، جعل مؤشر Thread Fiber فئة سوبر شائعة واحدة تسمى Strand .


يتيح لك هذا نقل واجهة برمجة التطبيقات (API) بأبسط طريقة.
ما يجب القيام به بعد ذلك - كما هو الحال دائمًا في هذا المشروع ، هو سؤال.


ما الذي يحدث مع Thread API الآن؟


  • أول استخدام لـ Thread.currentThread() في الألياف يخلق نوعًا من خيط الظل ، خيط الظل ؛
  • من وجهة نظر النظام ، هذا هو موضوع "لم يتم إصداره" ، ولا توجد معلومات تعريفية VM فيه ؛
  • يحاول ST لمحاكاة كل ما يستطيع ؛
  • ولكن عليك أن تفهم أن واجهة برمجة التطبيقات القديمة لديها الكثير من القمامة ؛
  • وبشكل أكثر تحديدًا ، يطبق Shadow Thread واجهة برمجة تطبيقات Thread لكل شيء باستثناء stop والتعليق والاستئناف والتعامل مع الاستثناءات غير المعروفة.

ماذا تفعل مع السكان المحليين الخيط؟


  • الآن يتحول السكان المحليون الخيط إلى سكان محليين من الألياف ؛
  • هناك الكثير من المشاكل في هذا ، كل هذا قيد المناقشة ؛
  • تمت مناقشة مجموعة من الاستخدامات بشكل خاص ؛
  • استخدمت خيوط المعالجة تاريخيًا بشكل صحيح وغير صحيح (أولئك الذين يستخدمون بشكل غير صحيح لا يزالون يأملون في شيء ما ، ولا يمكنك خيبة أملهم تمامًا) ؛
  • بشكل عام ، يخلق هذا مجموعة كاملة من التطبيقات:
    • عالي المستوى: ذاكرة التخزين المؤقت للاتصالات أو كلمات المرور في الحاوية ؛
    • منخفض المستوى: معالج في مكتبات النظام.

كم يأكل كل شيء





خيط:


  • المكدس: 1 ميغا بايت و 16 كيلو بايت على بنية بيانات النواة ؛
  • لكل مثيل مؤشر ترابط: 2300 بايت ، بما في ذلك معلومات التعريف VM.

الألياف:


  • كومة الاستمرارية: من مئات البايت إلى الكيلوبايت ؛
  • لكل مثيل من الألياف: 200-240 بايت.

الفرق كبير!
وهذا بالضبط ما يسمح لملايين الأشخاص بإشعال النار.


ما يمكن بارك


من الواضح أن أكثر شيء سحري هو وقوف السيارات التلقائي عند حدوث بعض الأحداث. ما هو المدعوم حاليا؟


  • Thread.sleep ، الانضمام ؛
  • java.util.concurrent و LockSupport.lock ؛
  • IO: شبكة على مآخذ (قراءة المقبس ، الكتابة ، الاتصال ، القبول) ، الملفات ، الأنابيب ؛
  • كل هذا لم يكتمل ، ولكن الضوء في النفق مرئي.

التواصل بين الألياف


سؤال آخر يطرحه الجميع هو: كيفية التبادل التنافسي للمعلومات بين الألياف.


  • يقوم النموذج الأولي الحالي بإطلاق المهام في Runnable ، ويمكن تحويله إلى CompletableFuture ، إذا كنت بحاجة لسبب ما ؛
  • java.util.concurrent "يعمل فقط". يمكنك تحسس كل شيء بطريقة قياسية ؛
  • قد تكون هناك واجهات برمجة تطبيقات جديدة للمؤشرات المتعددة ، ولكن هذا ليس دقيقًا ؛
  • مجموعة من الأسئلة الصغيرة مثل "هل يجب أن تُرجع الألياف القيم؟" ؛ تتم مناقشة كل شيء ، فهي ليست في النموذج الأولي.

كيف يتم تنفيذ الاستمرارات في النموذج الأولي؟


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


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


لدينا كرسيان ... أعني مكدسين. صفين من جافا في الورك. الأول هو مصفوفة كائن ، حيث سنقوم بتخزين مراجع للكائنات. والثاني هو بدائي (على سبيل المثال ، حميم) ، والذي سيتعامل مع كل شيء آخر.





نحن الآن في حالة يكون فيها الاستمرار على وشك التنفيذ لأول مرة.


run المكالمات طريقة داخلية تسمى enter :





ثم يتم تنفيذ رمز المستخدم ، حتى أول ازدحام.





عند هذه النقطة ، يتم إجراء مكالمة VM ، والتي تقوم freeze المكالمات. في هذا النموذج الأولي ، يتم ذلك بشكل مادي مباشر - باستخدام النسخ.





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





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





وإذا كان كل شيء على ما يرام ، فإننا ننسخ أولاً في مصفوفة بدائية:





ثم نقوم بعزل المراجع إلى الكائنات وحفظها في صفيف الكائن:





في الواقع ، شايان لكل من يقرأ على هذا المكان!


علاوة على ذلك ، نواصل هذا الإجراء لجميع العناصر الأخرى من المكدس الأصلي.





مرحى! نسخنا كل شيء إلى العش في الورك. يمكنك القفز بأمان إلى مكان المكالمة دون خوف من فقدان شيء ما. كل شيء هو الورك.





الآن ، عاجلاً أم آجلاً ، سيدعو رمز الاتصال استمرارنا مرة أخرى. ويجب أن تستمر من المكان الذي تركت فيه آخر مرة. هذه هي مهمتنا.





التحقق من ما إذا كان الاستمرار قيد التشغيل ، يقول نعم ، كان يعمل. لذلك ، تحتاج إلى استدعاء VM ، وتنظيف بعض المساحة على المكدس واستدعاء وظيفة VM الداخلية. تُترجم كلمة "Thaw" إلى الروسية كـ "thaw" و "unfreeze" ، والتي تبدو منطقية تمامًا. من الضروري فك تجميد الإطارات من كومة الاستمرارية إلى مجموعتنا الأصلية الرئيسية.


لست متأكدًا من أن تذويب الشاي واضح تمامًا. التجريد السيئ يشبه الهريرة ذات الباب. لكن هذا سيفيدنا.





نحن نصنع نسخ واضحة


أولاً مع مجموعة بدائية:





ثم من الرابط:





تحتاج إلى تصحيح النسخة المنسوخة قليلاً للحصول على المكدس الصحيح:





كرر الفحش لجميع الإطارات:





الآن يمكنك العودة yield والمتابعة وكأن شيئًا لم يحدث.





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


بدلاً من ذلك ، لدينا فكرة أخرى - النسخ البطيء.


دعونا نعود إلى المكان الذي لدينا فيه بالفعل استمرار متجمد.





نواصل العملية كما كان من قبل:





بنفس الطريقة السابقة ، نقوم بتنظيف المكان على المكدس الأصلي:





لكننا لا ننسخ كل شيء على التوالي ، ولكن إطار واحد أو إطارين فقط:





الآن الإختراق. تحتاج إلى تصحيح عنوان المرسل للطريقة C بحيث يشير إلى حاجز عودة معين:





الآن يمكنك العودة بأمان إلى yield :





وهذا بدوره سيؤدي إلى استدعاء رمز المستخدم في الطريقة C :





تخيل الآن أن C يريد العودة إلى الرمز الذي يطلق عليه. لكن مستدعيه هو B ، وهو ليس على المكدس! لذلك ، عندما يحاول العودة ، سيذهب إلى عنوان العودة ، وهذا العنوان هو الآن حاجز العودة. وكما تعلمون ، فإن هذا سيجذب مرة أخرى مكالمة thaw :





وسيؤدي فك التجميد إلى إلغاء تجميد الإطار التالي على مكدس الاستمرارية ، وهذا هو B :





في الواقع ، قمنا بنسخه بتكاسل عند الطلب.


بعد ذلك ، نسقط B من مكدس الاستمرارية ونضع الحاجز مرة أخرى (يحتاج الحاجز إلى الضبط لأن هناك شيء متبقي على مكدس الاستمرارية). وهكذا دواليك.





ولكن لنفترض أن B لن يعود إلى رمز الاتصال ، ولكنه يستدعي أولاً طريقة أخرى D وتريد هذه الطريقة الجديدة أيضًا أن تكون مزدحمة.





في هذه الحالة ، عندما يحين وقت freeze ، سنحتاج إلى نسخ الجزء العلوي فقط من المكدس الأصلي إلى المكدس المستمر:





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


ماذا بقي؟


يضع المطورون بعض الميزات في الاعتبار ، لكنهم لم يدخلوا في النموذج الأولي.


  • التسلسل والاستنساخ. القدرة على الاستمرار على جهاز آخر ، في وقت آخر ، إلخ.
  • JVM TI والتصحيح ، كما لو كانت خيوط منتظمة. إذا تم حظرك من قراءة المقبس ، فلن ترى قفزة جميلة من العائد ، في النموذج الأولي سيتم حظر الخيط ببساطة ، مثل أي خيط عادي آخر.
  • لم يلمس العود الذيل حتى.

الخطوات التالية:


  • إنشاء واجهة برمجة تطبيقات بشرية ؛
  • أضف جميع الميزات المفقودة ؛
  • تحسين الأداء.

من أين تحصل عليه


تم إنشاء النموذج الأولي ليقضوا في مستودع OpenJDK. يمكنك تنزيل النموذج الأولي هنا بالتبديل إلى fibers البرنش.


يتم ذلك على النحو التالي:


 $ hg clone http://hg.openjdk.java.net/loom/loom $ cd loom $ hg update -r fibers $ sh configure $ make images 

كما تعلمون ، كل هذا سيبدأ تجميع OpenJDK اللعين بأكمله. لذلك ، أولاً ، سيتعين على النصف ساعة التالية من حياتك أن تفعل شيئًا آخر ، بينما كل هذا سوف يحدث.





ثانيًا ، أنت بحاجة إلى أن يكون لديك جهاز كمبيوتر تم تكوينه بشكل صحيح مع سلسلة أدوات C ++ و GNU libs. أنا ألمح إلى أنه لا يوصى بإجراء ذلك على Windows. بجدية ، حتى مع تنزيل VirtualBox وتثبيت Ubuntu جديد هناك ، ستقضي أوامر بحجم أقل من محاولة إدراك خطأ غير إنساني آخر عند البناء من Cygwin أو msys64. هذا هو المكان الذي تأتي فيه msys في أسوأ من Cygwin.


على الرغم من أن هذا ، بالطبع ، مجرد كذبة ، فقد سئمت من كتابة تعليمات التجميع .


- , mercurial extension fsmonitor. , , hg help -e fsmonitor .
~/.hgrc :


 [fsmonitor] mode = on 

- . -, cp -R ./loom ./loom-backup .


, . , Java- , .


sh configure - . , Ubuntu, Autoconf ( sudo apt-get install autoconf ). — OpenJDK Ubuntu, , . Windows , .


, , hg diff --stat -r default:fibers .


, , , .


الخلاصة


«, ». «», . «Loom» — « ». Project Loom .


, . , «» , , — , , , — .


, , XIX , .




. -, .


, . IDE .


, , , « », «», « » .


? . .


شكرا لك

Source: https://habr.com/ru/post/ar422519/


All Articles