لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 09/16/25 في كل الموقع
-
السلام عليكم كنت قد استثمرت في دورة تطوير التطبيقات باستخدام لغة بايثون هنا علي اكادمية حاسوب وبعد ان انهيت المسار الاخير المتعلق بتعلم الالة اشعر اني متحمس لهذا المجال واريد ان اتطور فيه فكنت اسال عن دورة الذكاء الاصطناعي وما مدى تقدمها والى اي مستوى ستاخذني اقصد بذلك ما الذي سيكون بااكاني فعله بعد دراسة المحتوى والتطبيق عليه وما مستوى المشاريع في نهاية كل مسار اسف للاطالة و جزاكم الله خيرا2 نقاط
-
مافائدة oop مع وجود قواعد البيانات اقصد هل يجب انا افهمها بشكل اعمق عند استخدم لغه مثل c# كيف يكون oop مفيد في اضافة البيانات او جلبها من قواعد البيانات او تعديلها او حذفها2 نقاط
-
السلام عليكم هل من الأفضل دائمًا استخدام الدوال الجاهزة مثل image_dataset_from_directory في TensorFlow أو الأدوات المشابهة في PyTorch لتحميل البيانات، أم أن هناك أحيانًا يكون من الأفضل الاعتماد على مكتبات مثل OS و OpenCV (cv2)؟ وهل في الغالب يُستهلك معظم الوقت في تحميل الصور ومعالجتها أكثر من بناء شبكة CNN نفسها؟1 نقطة
-
السلام عليكم ليه Kaggle Notebook مش دايمًا عليه أحدث نسخة من المكتبات؟1 نقطة
-
عايز شرح مبسط كدا لي Parameters and Arguments لأني مش فهمهم خالص أو مش فاهم بسبب الشخص اللي بسمع ليه فيهم لو موجود شرحهم في الدورة ممكن حد يقولي على الجزئية دي1 نقطة
-
أنا أمتلك دورتين دورة Frontend and JavaScript وقربت أخلص مشروع يوتيوبي عايز بعد مخلصه أتعلم Backend أو MERN واعمل مشاريع دورة JavaScript وبعد كدا انتقل لدورة Frontend واعمل المشاريع كاملة بدال مهيا Frontend فقط أريد مسار أمشي عليه غير إنه ميكنش فيه React Native أو Desktop عايز مسار الويب فقط1 نقطة
-
السلام عليكم انا جديده بعالم البرمجه ماعرف شي عنه حبدا اتعلمه من الصفر لو بدات بالدوره بالترتيب حفهم كل شي ولااحتاج شرح تمهيدي قبل ابدا بالشروحات1 نقطة
-
السلام عليكم احتاج اعرف نوع الحهاز يلي ممكن اشتغل عليه وانفذ مشاريع الدوره1 نقطة
-
1 نقطة
-
1 نقطة
-
ستجد أسفل فيديو الدرس في نهاية الصفحة صندوق تعليقات كما هنا، أرجو طرح الأسئلة أسفل الدرس وليس هنا في قسم أسئلة البرمجة حيث نطرح الأسئلة العامة الغير متعلقة بمحتوى الدورة أو الدرس، وذلك لمساعدتك بشكل أفضل.1 نقطة
-
السلام عليكم عند تدريب شبكه عصبيه CNN بيحصل الخطاء ده اثناء التدريب النموذج InvalidArgumentError Traceback (most recent call last) /tmp/ipykernel_36/2234119796.py in <cell line: 0>() ----> 1 history = keras_models.fit(train_ds,validation_data=val_ds,epochs=20) /usr/local/lib/python3.11/dist-packages/keras/src/utils/traceback_utils.py in error_handler(*args, **kwargs) 120 # To get the full stack trace, call: 121 # `keras.config.disable_traceback_filtering()` --> 122 raise e.with_traceback(filtered_tb) from None 123 finally: 124 del filtered_tb /usr/local/lib/python3.11/dist-packages/tensorflow/python/eager/execute.py in quick_execute(op_name, num_outputs, inputs, attrs, ctx, name) 57 e.message += " name: " + name 58 raise core._status_to_exception(e) from None ---> 59 except TypeError as e: 60 keras_symbolic_tensors = [x for x in inputs if _is_keras_symbolic_tensor(x)] 61 if keras_symbolic_tensors: InvalidArgumentError: Graph execution error: Detected at node decode_image/DecodeImage defined at (most recent call last): <stack traces unavailable> Detected at node decode_image/DecodeImage defined at (most recent call last): <stack traces unavailable> 2 root error(s) found. (0) INVALID_ARGUMENT: Input size should match (header_size + row_size * abs_height) but they differ by 2 [[{{node decode_image/DecodeImage}}]] [[IteratorGetNext]] [[IteratorGetNext/_4]] (1) INVALID_ARGUMENT: Input size should match (header_size + row_size * abs_height) but they differ by 2 [[{{node decode_image/DecodeImage}}]] [[IteratorGetNext]] 0 successful operations. 0 derived errors ignored. [Op:__inference_multi_step_on_iterator_13769] مع العلم انا بستخدم الداله دي في تحميل البيانات train_ds = tf.keras.utils.image_dataset_from_directory( '/kaggle/input/dog-and-cat-classification-dataset/PetImages', image_size=(128, 128), batch_size=32, labels="inferred", label_mode="int", validation_split=0.2, subset="training", seed=123, )1 نقطة
-
انا ادرس من دورة بايثون لتطوير التطبيقات، قريبا سأنهي ان شاء الله مسار اودو، هل ان أنهيت هذا المسار يمكنني ان احصل به علي عمل حر1 نقطة
-
وعليكم السلام ورحمة الله تعالى وبركاته، ال Kaggle Notebooks لا تحتوي دائما على أحدث نسخة من المكتبات لأن Kaggle تعطي الأولوية لاستقرار البيئة على التحديث الفوري للمكتبات لتجنب التعارضات التي قد تحدث وهذا النظام يهدف إلى ضمان الاستقرار والتوافق بين جميع المكتبات المثبتة، بدلا من التحديث الفوري الذي قد يسبب تعارضات كما أن Kaggle يختبر التحديثات قبل دمجها لتجنب كسر البيئة مما يعني وجود فجوة زمنية بين إطلاق النسخة الجديدة وتوفرها في Notebooks.1 نقطة
-
1 نقطة
-
السلام عليكم ورحمة الله تعالى وبركاته، أولا فإنّ ال image_dataset_from_directory لا تتجاهل الصور التالفة تلقائيا يمكنك رؤية ذلك في رسالة التحذير: Corrupt JPEG data والتي تشير إلى وجود صور JPEG تالفة في ال dataset ثم يحدث الخطأ الكامل InvalidArgumentError عندما تواجه TensorFlow صورة تالفة لا يمكن فك تشفيرها نهائيا وهنا المشكلة أن TensorFlow يحاول معالجة جميع الملفات في المجلد دون تصفية، وعندما يصل لصورة تالفة مثل الصورة التي تسبب الخطأ: Input size should match header_size يتوقف التدريب تماما والحل هو تنظيف البيانات مسبقا عبر إزالة الصور التالفة يدويا أو برمجيا أو استخدام كود مخصص لإنشاء dataset يتجاهل الصور التالفة باستخدام tf.data.Dataset.from_generator مع معالجة الأخطاء أو إضافة tf.data.AUTOTUNE مع dataset.map() و try-except للتعامل مع هذه الحالات.1 نقطة
-
import requests, re def get_data(): from selenium import webdriver from selenium.webdriver.chrome.options import Options from bs4 import BeautifulSoup options = Options() options.add_argument("--headless") options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') options.add_argument('--log-level=3') prefs = { "profile.default_content_setting_values": { "images": 2, "plugins": 2, "popups": 2, "notifications": 2, "media_stream": 2, } } options.add_experimental_option("prefs", prefs) drive = webdriver.Chrome(options = options) drive.get ( "https://world-weather.info/") html = drive.page_source my_headers = {"user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" } # the_response = requests.get( headers= my_headers) # if the_response.ok: his_soup = BeautifulSoup(html, "html.parser") resor = his_soup.find_all('div', id = "list") print(resor) drive.quit() get_data() 23040:25080:0916/112333.676:ERROR:gpu\command_buffer\service\gles2_cmd_decoder_passthrough.cc:1100] [GroupMarkerNotSet(crbug.com/242999)!:A0802B00545B0000]Automatic fallback to software WebGL has been deprecated. Please use the --enable-unsafe-swiftshader (about:flags#enable-unsafe-swiftshader) flag to opt in to lower security guarantees for trusted content.1 نقطة
-
السلام عليكم ورحمة الله تعالى وبركاته، ال Parameters أو المعاملات هي المتغيرات التي تعرف في تعريف الدالة نفسها بين الأقواس بمعنى آخر هي أسماء المتغيرات التي تستخدمها الدالة لتستقبل قيما عند استدعائها مثلا لو عرفت دالة تجمع رقمين، الرقمين هذان هما معاملات الدالة. أما ال Arguments فهي القيم أو المعاملات الممررة وهي القيم التي ترسل للدالة عند استدعائها أي القيم الفعلية التي تمرر إلى المعاملات (Parameters) عند استدعاء الدالة بحيث تضع القيم التي تريد معالجتها في الدالة.1 نقطة
-
السلام عليكم ورحمة الله تعالى وبركاته، بعد إكمال مشروع دورة تطوير واجهات المستخدم ودورة تطوير تطبيقات JavaScript، يمكنك الانتقال لتعلم Backend ضمن دورة JavaScript نفسها مع التركيز على تطوير الويب فقط بدون React Native أو تطبيقات سطح المكتب. والمسارات الخاصة بالBackend تشمل تعلم أساسيات Node.js لبناء الخوادم، استخدام Express.js لإنشاء APIs، التعامل مع قواعد البيانات مثل MongoDB، تطوير RESTful APIs، إدارة المصادقة وأمن التطبيقات، وأخيرا تطبيق مشاريع عملية مثل تطبيق دردشة أو نظام أسئلة وأجوبة. بعد اكتساب هذه المهارات يمكنك دمج ما تعلمته مع React.js في دورة Frontend المتقدمة لتطوير مشاريع MERN كاملة مع إمكانية تعلم تقنيات إضافية مثل Next.js وTypeScript: أساسيات Node.js Express.js لتطوير APIs قواعد البيانات (MongoDB) بناء RESTful APIs إدارة المصادقة وأمن التطبيقات مشاريع عملية مثل تطبيق دردشة ونظام أسئلة وأجوبة1 نقطة
-
أحب اعرف الفرق بين منصة سنديان و منصة ووردبريس من حيث بناء مواقع وعمل مشاريع وطريقة الاستضافة وحجز النطاق1 نقطة
-
ليس تقليلاً منك أو تثبيط، الأمر ليس بتلك السهولة، لا تتوقع أن تستطيع الحصول على عمل بمجرد إنهاء 4 مسارات فقط، ستحتاج إلى المزيد من الدراسة والخبرة، ستحتاج إلى دراسة التالي طالما نويت التخصص كـ Back-End: أساسيات لغة بايثون Python تطبيقات عملية باستخدام بايثون Python أساسيات إطار العمل جانغو Django تطوير متجر إلكتروني باستخدام جانغو Django تطوير واجهة برمجية لتعديل الصور باستخدام فلاسك Flask بناء مدونة باستخدام فلاسك Flask دمج تقنيات الذكاء الاصطناعي مع تطبيقات بايثون بالنسبة لـ Flask وDjango قم بدراسة الجزء الخاص بالـ API فقط، أي الجزء الخاص بالخادم وقواعد البيانات وتجاهل الجزء الخاص بالواجهة الأمامية (القوالب). لكن في الوقت الحالي لا أنصحك بالتخصص فقط في الواجهة الخلفية، بل عليك أن تصبح Full-stack فأغلب الوظائف حاليًا تتطلب ذلك وبالأخص لمستوى Junior، وأيضًا من كلامك أرى أنك تنوي العمل على مواقع العمل الحر والمشاريع بها تتطلب Full-stack، أي عليك دراسة الواجهة الأمامية أيضًا.1 نقطة
-
الصور لديك بها مشكلة، الأفضل حذفها ثم إعادة إضافتها، ومن أين حصلت عليها؟1 نقطة
-
هو ده حضرتك Epoch 1/10 161/625 ━━━━━━━━━━━━━━━━━━━━ 8s 19ms/step - accuracy: 0.5074 - loss: 7.7238 Corrupt JPEG data: 2226 extraneous bytes before marker 0xd9 264/625 ━━━━━━━━━━━━━━━━━━━━ 6s 18ms/step - accuracy: 0.5070 - loss: 5.4437 Corrupt JPEG data: 65 extraneous bytes before marker 0xd9 Corrupt JPEG data: 239 extraneous bytes before marker 0xd9 268/625 ━━━━━━━━━━━━━━━━━━━━ 6s 18ms/step - accuracy: 0.5070 - loss: 5.3865 --------------------------------------------------------------------------- InvalidArgumentError Traceback (most recent call last) /tmp/ipykernel_36/945077657.py in <cell line: 0>() ----> 1 history = keras_models.fit( 2 train_ds, 3 validation_data=val_ds, 4 epochs=10, 5 callbacks=[tf.keras.callbacks.EarlyStopping( /usr/local/lib/python3.11/dist-packages/keras/src/utils/traceback_utils.py in error_handler(*args, **kwargs) 120 # To get the full stack trace, call: 121 # `keras.config.disable_traceback_filtering()` --> 122 raise e.with_traceback(filtered_tb) from None 123 finally: 124 del filtered_tb /usr/local/lib/python3.11/dist-packages/tensorflow/python/eager/execute.py in quick_execute(op_name, num_outputs, inputs, attrs, ctx, name) 57 e.message += " name: " + name 58 raise core._status_to_exception(e) from None ---> 59 except TypeError as e: 60 keras_symbolic_tensors = [x for x in inputs if _is_keras_symbolic_tensor(x)] 61 if keras_symbolic_tensors: InvalidArgumentError: Graph execution error: Detected at node decode_image/DecodeImage defined at (most recent call last): <stack traces unavailable> Detected at node decode_image/DecodeImage defined at (most recent call last): <stack traces unavailable> 2 root error(s) found. (0) INVALID_ARGUMENT: Input size should match (header_size + row_size * abs_height) but they differ by 2 [[{{node decode_image/DecodeImage}}]] [[IteratorGetNext]] [[IteratorGetNext/_2]] (1) INVALID_ARGUMENT: Input size should match (header_size + row_size * abs_height) but they differ by 2 [[{{node decode_image/DecodeImage}}]] [[IteratorGetNext]] 0 successful operations. 0 derived errors ignored. [Op:__inference_multi_step_on_iterator_39708] الخطاء ده بيحصل اثناء تدريب الشبكه العصبه CNN1 نقطة
-
ما هو الخطأ الحالي، انسخه وألصقه هنا1 نقطة
-
لديك مشكلة في الملفات، فالدالة image_dataset_from_directory تقرأ كل ملف في المجلد، ولو تم قراءة ملف تالف، يتوقف التدريب بأكمله. عليك المرور على جميع الصور في مجلدي Cat و Dog وحذف أي ملف لا يمكن فتحه كصورة صالحة، ومتاح ذلك سهولة من خلال مكتبة Pillow، عن طريق السكريبت التالي، قم بتنفيذه في خلية منفصلة قبل الكود الذي يقوم بإنشاء train_ds و val_ds: import os import PIL from PIL import Image from pathlib import Path cat_path = Path("/kaggle/input/dog-and-cat-classification-dataset/PetImages/Cat") dog_path = Path("/kaggle/input/dog-and-cat-classification-dataset/PetImages/Dog") image_extensions = [".png", ".jpg", ".jpeg"] img_paths = [cat_path, dog_path] for path in img_paths: print(f"Checking directory: {path}") for filepath in path.glob("*"): if filepath.suffix.lower() in image_extensions: try: img = Image.open(filepath) img.verify() except (IOError, SyntaxError, PIL.UnidentifiedImageError) as e: print(f"Deleting corrupt image file: {filepath}") try: os.remove(filepath) except Exception as remove_error: print(f"Could not delete file: {filepath}, Error: {remove_error}") بالطبع عليك تعديل مسار cat_path وdog_path للمسار الصحيح لديك في بيئة Kaggle1 نقطة
-
بل ستحتاج أولاً إلى إنهاء دورة تطوير واجهات المستخدم، ثم دراسة مسار MERN من خلال دورة جافاسكريبت، وستدرس المسارات التالية: أساسيات لغة JavaScript أساسيات React.js أساسيات Node.js تطبيق دردشة يشبه WhatsApp إنشاء تطبيق أسئلة وأجوبة باستخدام Next.js تطبيق تعلم اللغات باستخدام Next.js وتقنيات الذكاء الاصطناعي تطبيقات الويب التقدمية PWA أساسيات TypeScript بمعنى يجب دراسة الأساسيات أولاً، أي تطوير المشاريع من خلال HTML, CSS, JS وذلك ما نفعله في دورة تطوير واجهة المستخدم، ثم بعد تعلم React وNode.js وNext.js تستطيع العمل على تطوير نفس المشاريع ولكن من خلال التقنيات السابقة، وذلك أفضل لتتفهم الفرق وستكتسب خبرة كبيرة من ذلك. الأفضل العمل على تحويلها إلى مشروع React.js من أجل التدرب على استخدام المكتبة، ثم إنشاء واجهة خلفية من خلال Node.js.1 نقطة
-
رائع جداً أنّك بدأت دراسة وتعلم مجال كالذكاء الاصطناعي في هذا العمر المبكر! هذا بحدّ ذاته إنجاز كبير ويُظهر شغفًا حقيقيًا لديك، حاول عدم الضغط على نفسك بشكل كبير فلديك الوقت أمامك لتحقيق إستفادة كبيرة. مع قليل من التنظيم والموازنة بين الدراسة والرياضة والدورة ستُحقق مبتغاك. إليك بعض النصائح التي من الممكن أن تساعدك في رحلتك التعليمية: الثبات أهم بكثير من الساعات الطويلة المتقطّعة، خصص وقت ثابت للمتابعة والتعلم خلال الأسبوع (مثلاً ساعتين او ساعتين ونصف خلال اليوم لأربعة أيام في الأسبوع) وسيتبقى لك وقت كافي لدراستك في المدرسة الحكومية وممارسة نشاطاتك الأخرى. بعد أي محاضرة او فيديو حاول تطبيق ما تعلمته فوراً عبر تجربة الأكواد وتنفيذ مشاريع مصغرة. التطبيق العملي يرسّخ المفاهيم أسرع من الدراسة النظرية وحدها إختر مشاريع شخصية صغيرة قريبة من إهتماماتك وتفضيلاتك وحاول تنفيذها. مثلأً تحليل بيانات اللاعبين والمباريات هذا يجعلك متحمساً للتعلم أكثر. دوّن ملاحظاتك وارفع التطبيقات والمشاريع التي تقوم بها على Github أو أي منصة مشابهة مع الوقت سيصبح لديك معرض أعمال قوي وتزداد خبرتك في المجال. بالتوفيق.1 نقطة
-
وعليكم السلام، عندما نقول أنّ هناك لغة آلة يتم ترجمة البرامج إليها ثم يتم تشغيل البرامج مباشرة من طرف الحاسوب، فهذه الفكرة المبسطة جدا والمختصرة اختصارا مخلّا فادحا، لكن الأمر لا يتم بهذه البساطة في الحقيقة. في الواقع، عند اختراع الحواسيب الأولى، في سنوات الأربعينات والخمسينات من القرن الماضي، والتي كان حجمها يملأ غرفة كاملة و وزنها بالقناطير، كانت بالفعل لا تملك أنظمة تشغيل! ولكن مشكلة هذه الحواسيب هي أنّها كانت بسيطة جدّا: يعمل عليها برنامج واحد فقط، ويظهر نتائجه عبر شاشة بسيطة لا تتمكن سوى من إظهار قدر قليل من النص. يمكنك أن تتخيّل أن هذه الحواسيب كانت مثل آلة حاسبة، لكنّها كانت قابلة للبرمجة! كما ترى، حواسيب مثل هذه لا تصلح لشيء في وقتنا. ماذا نفعل بحاسوب يستطيع تشغيل برنامج واحد فقط، ولا يستطيع عرض سوى نص قليل مثل آلة حاسبة؟ لذلك تمّ تطوير الحواسيب حتى تستطيع تشغيل عدّة برامج، توفّر دخولا لعدّة مستخدمين، تعطي إدارة للذاكرة الحية، للقرص الصلب، للشبكة، للبطارية .... ولكثير من الأمور الأخرى. لكن انتظر، كيف يستطيع الحاسوب فعل كلّ هذا؟ هل ستكتب له برنامجا يعلّمه كيف يقوم بذلك؟ لا أعتقد أنّك تريد فعل هذا من برنامجك، وحتى لو أردت فلن تستطيع، فهذا يتطلّب سنوات من العمل وفريق عمل ضخم حتّى تستطيع إنشاء برنامج يمكنه إدارة موارد الحاسوب بطريقة فعّالة هكذا. لحسن الحظ، ليس عليك كتابة برنامج كهذا، لأنّ هذا النوع من البرامج موجود بالفعل، وهو يسمى: نظام التشغيل! إذن نظام التشغيل ما هو إلّا برنامج كبير يقوم بتنظيم عمل البرامج الأخرى. في الواقع، الآلة لا تستطيع التعامل إلّا مع برنامج واحد، وهذا البرنامج في الحواسيب الحديثة هو نظام التشغيل الذي يقوم بدوره بتشغيل بقية البرامج. لذلك، فعندما نقول أنّ البرنامج تمّت ترجمته إلى لغة الآلة، فهذا يكافئ أن نقول أنّ هذا النص مكتوب بالحروف العربية، لكن هل كلّ نصّ مكتوب بالحروف العربيّة تستطيع فهمه إذا كنت تتحدّث باللغة العربية؟ بالطبع لا! فربّما هو باللغة الفارسية أو الأوردو أو بالعثمانية أو أيّة لغة أخرى تستخدم الحروف العربيّة. لذلك، فلغة الآلة تعني الحروف المستخدمة، بينما تفسير الحروف إلى معاني هو وظيفة نظام التشغيل. هذا بالنسبة للغات المترجمة، أمّا اللغات المفسّرة فلا يتم أصلا ترجمتها إلى لغة الآلة، بل يتم تشغيلها مباشرة من طرف المفسّر، والمفسّر هنا أيضا يعتمد على نظام التشغيل لكي يستطيع العمل وتشغيل برنامجك الذي تكتبه من خلاله.1 نقطة
-
بدأنا في مقال الجزء الأول ببناء مشروع عملي بلغة رست وهو عبارة عن خادم ويب متعدد مهام المعالجة، إذ بنينا الخادم الأساسي وكان أحادي خيط المعالجة، وعملنا في مقال الجزء الثاني على تحويله إلى خادم متعدد خيوط المعالجة، وسننهي في هذا المقال بناء الخادم ليصبح جاهزًا، فإذا لم تكن قرأت المقالات السابقة، فارجع لها قبل قراءة هذا المقال. الإغلاق الرشيق وتحرير الذاكرة تستجيب الشيفرة 20 للطلبات بصورةٍ غير متزامنة عبر استخدام مجمع خيط كما نريد، إذ نحصل على بعض التحذيرات من حقول workers و id و thread التي لن نستخدمها مباشرةً وتذكرنا أننا لم نحرر أي شيء من الذاكرة. عندما نستخدم الحل البدائي الذي هو استخدام مفتاحي "ctrl-c" لإيقاف الخيط الرئيسي، تتوقف الخيوط مباشرةً حتى لو كانوا يخدّمون طلبًا. سننفّذ سمة Drop لاستدعاء join على كل خيط في المجمع لكي ننهي الطلبات التي تعمل قبل الإغلاق، ثم سننفّذ طريقةً لإخبار الخيوط ألا تقبل طلبات جديدة قبل الإغلاق. لرؤية عمل هذا الكود سنعدّل الخادم ليقبل طلبين فقط قبل أن يغلق مجمع الخيط thread pool. تنفيذ سمة Drop على مجمع خيط لنبدأ بتنفيذ Drop على مجمع الخيط الخاص بنا. عندما يُسقط المجمع يجب أن تجتمع كل الخيوط للتأكد من أن عملهم قد انتهى. تظهر الشيفرة 22 المحاولة الأولى لتطبيق Drop، إذ لن تعمل الشيفرة حاليًا. اسم الملف: src/lib.rs impl Drop for ThreadPool { fn drop(&mut self) { for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); worker.thread.join().unwrap(); } } } [الشيفرة 22: ضم كل خيط عندما يخرج المجمع خارج النطاق] أولًا، نمرّ على كل workers في مجمع الخيط، واستُخدمت &mut هنا لأن self هو مرجع متغيّر، ونريد أيضًا تغيير worker. نطبع لكل عامل رسالةً تقول أن هذا العامل سيُغلق، ثم نستدعي join على خيط العمال. إذا فشل استدعاء join نستخدم unwrap لجعل رست تهلع وتذهب إلى إغلاق غير رشيق. سنحصل على هذا الخطأ عند تصريف هذه الشيفرة: $ cargo check Checking hello v0.1.0 (file:///projects/hello) error[E0507]: cannot move out of `worker.thread` which is behind a mutable reference --> src/lib.rs:52:13 | 52 | worker.thread.join().unwrap(); | ^^^^^^^^^^^^^ ------ `worker.thread` moved due to this method call | | | move occurs because `worker.thread` has type `JoinHandle<()>`, which does not implement the `Copy` trait | note: this function takes ownership of the receiver `self`, which moves `worker.thread` --> /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/std/src/thread/mod.rs:1581:17 For more information about this error, try `rustc --explain E0507`. error: could not compile `hello` due to previous error يوضّح الخطأ أننا لا يمكن أن نستدعي join لأنه لدينا استعارة متغيرة على كل worker وتأخذ join ملكية وسطائها، ولمعالجة هذه المشكلة نحن بحاجة لنقل الخيط خارج نسخة Worker التي تملك thread حتى تستطيع join استهلاك الخيط، وقد فعلنا ذلك في الشيفرة 15 من المقال تنفيذ نمط تصميمي Design Pattern كائني التوجه Object-Oriented في لغة رست. إذا احتفظ Worker بـ Option<thread::JoinHandle<()>>، يمكننا استدعاء تابع take على Option لنقل القيمة خارج المتغاير Some وإبقاء المتغاير None في مكانه، بمعنى آخر سيحتوي Worker عامل على متغاير Some في Thread الخاص به وعندما نريد تحرير ذاكرة Worker نستبدل Some بالقيمة None حتى لا يوجد لدى Worker أي خيط لينفذه. لذا نحن نعرف أننا نريد تحديث تعريف Worker على النحو التالي. اسم الملف: src/lib.rs struct Worker { id: usize, thread: Option<thread::JoinHandle<()>>, } الآن لنتابع المصرّف لنجد أية أماكن أُخرى تحتاج تغيير، وبالتحقق من الشيفرة نجد خطأين: $ cargo check Checking hello v0.1.0 (file:///projects/hello) error[E0599]: no method named `join` found for enum `Option` in the current scope --> src/lib.rs:52:27 | 52 | worker.thread.join().unwrap(); | ^^^^ method not found in `Option<JoinHandle<()>>` | note: the method `join` exists on the type `JoinHandle<()>` --> /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/std/src/thread/mod.rs:1581:5 help: consider using `Option::expect` to unwrap the `JoinHandle<()>` value, panicking if the value is an `Option::None` | 52 | worker.thread.expect("REASON").join().unwrap(); | +++++++++++++++++ error[E0308]: mismatched types --> src/lib.rs:72:22 | 72 | Worker { id, thread } | ^^^^^^ expected enum `Option`, found struct `JoinHandle` | = note: expected enum `Option<JoinHandle<()>>` found struct `JoinHandle<_>` help: try wrapping the expression in `Some` | 72 | Worker { id, thread: Some(thread) } | +++++++++++++ + Some errors have detailed explanations: E0308, E0599. For more information about an error, try `rustc --explain E0308`. error: could not compile `hello` due to 2 previous errors لنعالج الخطأ الثاني الذي يشير إلى الشيفرة في نهاية Worker::new، إذ نريد تغليف قيمة thread في Some عندما ننشئ Worker جديد. أجرِ الخطوات التالية لتصحيح هذا الخطأ: اسم الملف: src/lib.rs impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { // --snip-- Worker { id, thread: Some(thread), } } } الخطأ الأول هو في تنفيذ Drop، وذكرنا سابقًا أننا أردنا استدعاء take على قيمة Option لنقل thread خارج worker. أجرِ التغييرات التالية لتصحيح هذا الخطأ: اسم الملف: src/lib.rs impl Drop for ThreadPool { fn drop(&mut self) { for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } } كما تحدثنا سابقًا في المقال البرمجة كائنية التوجه OOP في لغة رست، يأخذ التابع take على Option المتغاير Some خارجًا ويبقي None بدلًا عنه. استخدمنا if let لتفكيك Some والحصول على الخيط، ثم استدعينا join على الخيط. إذا كان خيط العامل هو أساسًا None نعرف أن العامل قد حرًر ذاكرته ولا يحصل شيء في هذه الحالة. الإشارة للخيط ليتوقف عن الاستماع إلى الوظائف تُصرّف الشيفرة بدون تحذيرات بعد كل التغييرات التي أجريناها، ولكن الخبر السيء أنها لا تعمل كما أردنا. النقطة المهمة هي في منطق المغلفات المنفذة بواسطة خيوط نسخ Worker، إذ نستدعي حتى اللحظة join لكن لا تُغلق الخيوط لأننها تعمل في loop للأبد بحثًا عن وظائف. إذا أسقطنا Threadpool بتنفيذنا الحالي للسمة drop، سيُمنع الخيط الأساسي للأبد بانتظار الخيط الأول حتى ينتهي، ولحل هذه المشكلة نحتاج لتغيير تنفيذ drop في ThreadPool، ثم إجراء تغيير في حلقة Worker. أولًا، سنغير تنفيذ drop في ThreadPool ليسقِط صراحةً sender قبل انتظار الخيوط لتنتهي. تظهر الشيفرة 23 التغييرات في ThreadPool لتسقط صراحةً sender. استخدمنا نفس تقنياتOption و take كما فعلنا مع الخيط لكي يستطيع نقل sender خارج ThreadPool. اسم الملف: src/lib.rs: pub struct ThreadPool { workers: Vec<Worker>, sender: Option<mpsc::Sender<Job>>, } // --snip-- impl ThreadPool { pub fn new(size: usize) -> ThreadPool { // --snip-- ThreadPool { workers, sender: Some(sender), } } pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static, { let job = Box::new(f); self.sender.as_ref().unwrap().send(job).unwrap(); } } impl Drop for ThreadPool { fn drop(&mut self) { drop(self.sender.take()); for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } } [الشيفرة 23: إسقاط sender صراحةً قبل جمع الخيوط الفعالة] يغلق إسقاط sender القناة، وهذا يشير بدوره إلى عدم إرسال أي رسائل إضافية، وعندما نفعل ذلك تعيد كل الاستدعاءات إلى recv التي تجريها الخيوط الفعالة في الحلقة اللانهائية خطأً. نغير حلقة Worker في الشيفرة 24 لتخرج من الحلقة برشاقة في تلك الحالة، يعني أن الخيوط ستنتهي عندما يستدعي join عليهم في تنفيذ drop في ThreadPool. اسم الملف: src/lib.rs impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { let thread = thread::spawn(move || loop { let message = receiver.lock().unwrap().recv(); match message { Ok(job) => { println!("Worker {id} got a job; executing."); job(); } Err(_) => { println!("Worker {id} disconnected; shutting down."); break; } } }); Worker { id, thread: Some(thread), } } } [الشيفرة 24: الخروج صراحةً من الحلقة عندما تعيد recv خطأ] لرؤية عمل هذه الشيفرة: سنعدل main لتقبل فقط طلبين قبل أن تُغلق الخادم برشاقة كما تظهر الشيفرة 25. اسم الملف: src/main.rs fn main() { let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); let pool = ThreadPool::new(4); for stream in listener.incoming().take(2) { let stream = stream.unwrap(); pool.execute(|| { handle_connection(stream); }); } println!("Shutting down."); } [الشيفرة 25: إغلاق الخادم بعد خدمة طلبين عن طريق الخروج من الحلقة] لا نريد أن يتوقف خادم حقيقي بعد خدمة طلبين فقط، وتبين هذه الشيفرة أن الإغلاق الرشيق وتحرير الذاكرة يعملان بصورةٍ نظامية. تُعرّف دالة take في سمة Iterator وتحدد التكرار إلى أول عنصرين بالحد الأقصى. سيخرج ThreadPool خارج النطاق في نهاية main وستُطبَّق سمة drop. شغّل الخادم باستخدام cargo run وأرسل ثلاثة طلبات. سيعطي الطلب الثالث خطأ وسترى الخرج في الطرفية على النحو التالي: $ cargo run Compiling hello v0.1.0 (file:///projects/hello) Finished dev [unoptimized + debuginfo] target(s) in 1.0s Running `target/debug/hello` Worker 0 got a job; executing. Shutting down. Shutting down worker 0 Worker 3 got a job; executing. Worker 1 disconnected; shutting down. Worker 2 disconnected; shutting down. Worker 3 disconnected; shutting down. Worker 0 disconnected; shutting down. Shutting down worker 1 Shutting down worker 2 Shutting down worker 3 يمكن أن ترى ترتيبًا مختلفًا للخيوط الفعالة والرسائل المطبوعة. تعمل الشيفرة وفقًا لهذه الرسائل كما يلي: أخذ العاملان 0 و 3 الطلبين الأولين وتوقف الخادم عن قبول الاتصالات بعد ثاني اتصال، وبدأ تنفيذ Drop في العمل على ThreadPool قبل أخذ العامل 3 وظيفته. يفصل إسقاط sender كل العمال ويخبرهم أن يُغلقوا، ويطبع كل عامل رسالةً عندما يُغلقوا ويستدعي مجمع الخيط join لانتظار كل خيط عامل لينتهي. لاحظ ميّزة مهمة في هذا التنفيذ، إذ قام ThreadPool بإسقاط sender وجرّبنا ضم العامل 0 قبل أن يستقبل أي عامل خطأ. لم يتلق العامل 0 أي خطأ من recv بعد، لذا تنتظر كتلة الخيط الأساسية أن ينتهي العامل 0. في تلك الأثناء استقبل العامل 3 وظيفة ثم استقبلت كل الخيوط خطأ. ينتظر الخيط الأساسي باقي العمال لينتهوا عندما ينتهي العامل 0. وبحلول هذه النقطة يخرج كل عامل من حلقته ويتوقف. تهانينا، فقد أنهينا المشروع ولدينا الآن خادم ويب بسيط يستخدم مجمع خيط للاستجابة بصورةٍ غير متزامنة، ونستطيع إجراء إغلاق رشيق للخادم الذي يحرر من الذاكرة كل الخيوط في المجمع. هذه هي الشيفرة الكاملة بمثابة مرجع. اسم الملف: src/main.rs use hello::ThreadPool; use std::fs; use std::io::prelude::*; use std::net::TcpListener; use std::net::TcpStream; use std::thread; use std::time::Duration; fn main() { let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); let pool = ThreadPool::new(4); for stream in listener.incoming().take(2) { let stream = stream.unwrap(); pool.execute(|| { handle_connection(stream); }); } println!("Shutting down."); } fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer).unwrap(); let get = b"GET / HTTP/1.1\r\n"; let sleep = b"GET /sleep HTTP/1.1\r\n"; let (status_line, filename) = if buffer.starts_with(get) { ("HTTP/1.1 200 OK", "hello.html") } else if buffer.starts_with(sleep) { thread::sleep(Duration::from_secs(5)); ("HTTP/1.1 200 OK", "hello.html") } else { ("HTTP/1.1 404 NOT FOUND", "404.html") }; let contents = fs::read_to_string(filename).unwrap(); let response = format!( "{}\r\nContent-Length: {}\r\n\r\n{}", status_line, contents.len(), contents ); stream.write_all(response.as_bytes()).unwrap(); stream.flush().unwrap(); } اسم الملف: src/lib.rs use std::{ sync::{mpsc, Arc, Mutex}, thread, }; pub struct ThreadPool { workers: Vec<Worker>, sender: Option<mpsc::Sender<Job>>, } type Job = Box<dyn FnOnce() + Send + 'static>; impl ThreadPool { /// Create a new ThreadPool. /// /// The size is the number of threads in the pool. /// /// # Panics /// /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> ThreadPool { assert!(size > 0); let (sender, receiver) = mpsc::channel(); let receiver = Arc::new(Mutex::new(receiver)); let mut workers = Vec::with_capacity(size); for id in 0..size { workers.push(Worker::new(id, Arc::clone(&receiver))); } ThreadPool { workers, sender: Some(sender), } } pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static, { let job = Box::new(f); self.sender.as_ref().unwrap().send(job).unwrap(); } } impl Drop for ThreadPool { fn drop(&mut self) { drop(self.sender.take()); for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } } struct Worker { id: usize, thread: Option<thread::JoinHandle<()>>, } impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker { let thread = thread::spawn(move || loop { let message = receiver.lock().unwrap().recv(); match message { Ok(job) => { println!("Worker {id} got a job; executing."); job(); } Err(_) => { println!("Worker {id} disconnected; shutting down."); break; } } }); Worker { id, thread: Some(thread), } } } يمكننا إجراء المزيد إذا أردنا تحسين المشروع، وإليك بعض الأفكار: أضِف المزيد من التوثيق إلى ThreadPool وتوابعه العامة. أضِف بعض الاختبارات لوظيفة المكتبة. غيّر الاستدعاءات من unwrap إلى معالجة خطأ أكثر متانة. استخدم ThreadPool لتنفيذ أعمال غير خدمة طلبات الويب. ابحث عن وحدة مجمع خيط مصرفة على creats.io ونفذ خادم ويب باستخدام الوحدة المصرفة، ثم قارن واجهة برمجة التطبيقات API والمتانة بينها وبين مجمع الخيط الذي نفذناه. خاتمة عظيم جدًا! فقد وصلنا إلى نهاية سلسلة البرمجة بلغة رست . نريد أن نشكرك لانضمامك إلينا في هذه الجولة في رست. أنت الآن جاهز لتنفيذ مشاريع رست ومساعدة الآخرين في مشاريعهم. تذكر أنه هناك مجتمع مرحب من مستخدمي رست الذين يحبون المساعدة في أي صعوبة يمكن أن تواجهها في استعمالك رست. ترجمة -وبتصرف- لقسم من الفصل Final Project: Building a Multithreaded Web Server من كتاب The Rust Programming Language. اقرأ أيضًا المقال السابق: بناء خادم ويب متعدد مهام المعالجة بلغة رست - الجزء الثاني تزامن الحالة المشتركة Shared-State Concurrency في لغة رست وتوسيع التزامن مع Send و Sync مقدمة إلى الخيوط Threads في جافا1 نقطة