البرمجة المتزامنة مقابل البرمجة غير المتزامنة

0 الأسهم
0
0
0
0

مقدمة

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

فهم البرمجة المتزامنة

ما هي البرمجة المتزامنة؟

في البرمجة المتزامنة، تُنفَّذ المهام بالتتابع. ككتاب، تبدأ من البداية وتقرأ كل كلمة وكل سطر. تتطلب البرمجة المتزامنة إكمال كل مهمة قبل بدء المهمة التالية. يكون سير التحكم متوقعًا وبسيطًا.

قد يتوقف النظام عن الاستجابة أو يتوقف عن العمل إذا استغرقت المهمة وقتًا طويلاً. يُعدّ سلوك الحظر من أبرز سمات البرمجة المتزامنة.

كيف يعمل؟

يُجري نموذج البرمجة المتزامنة العمليات خطيًا. تُبسَّط هذه العملية على النحو التالي:

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

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

حالات الاستخدام التي تبرز فيها البرمجة المتزامنة

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

مثال: قراءة ملف بشكل تسلسلي

فيما يلي مثال لكيفية عمل البرمجة المتزامنة في سياق قراءة الملفات:

function readFilesSequentially(fileList) {
for each file in fileList {
content = readFile(file) // This is a blocking operation
process(content)
}
}

في هذا الكود الزائف، الوظيفة readFile(ملف) إنها عملية متزامنة. الوظيفة العملية (المحتوى) حتى readFile(ملف) لم يُكمل قراءة الملف بالكامل، ولن يُنفَّذ. هذا دليل واضح على الطبيعة التسلسلية والحظرية للبرمجة المتزامنة.

مراجعة البرمجة غير المتزامنة

ما هي البرمجة غير المتزامنة؟

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

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

كيف يعمل؟

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

طبيعة غير مانعة: البرمجة غير المتزامنة لا تعيق بقية البرنامج، لأنها لا تنتظر المهام طويلة الأمد، مثل عمليات الإدخال/الإخراج. في برمجة واجهة المستخدم، يُحسّن هذا تجربة المستخدم وسرعة الاستجابة.

حالات الاستخدام التي يجب فيها استخدام البرمجة غير المتزامنة

غالبًا ما تُبرمج المهام المعتمدة على عمليات الإدخال/الإخراج بشكل غير متزامن. يمكن استخدام هذه المهام في تطوير الويب لإرسال طلبات واجهة برمجة التطبيقات (API)، والوصول إلى قواعد البيانات، ومعالجة مدخلات المستخدم دون مقاطعة السلسلة الرئيسية.

مثال: طلبات AJAX في تطوير الويب باستخدام الكود الزائف

يمكن استخدام البرمجة غير المتزامنة لإرسال طلبات AJAX في تطوير الويب. انظر المثال التالي:

function fetchAndDisplayData(url) {
// This is a non-blocking operation
data = asyncFetch(url);
data.then((response) => {
// This code will run once the data has been fetched
displayData(response);
});
}

في الكود الزائف أعلاه، الوظيفة جلب غير متزامن (url) إنها عملية غير متزامنة. الوظيفة عرض البيانات (الاستجابة) حتى جلب غير متزامن (url) لم تنتهِ العملية من استقبال البيانات، ولم تُنفَّذ. في هذه الأثناء، يُمكن أن تستمر أكواد أخرى في العمل في الخلفية، مما يُظهر الطبيعة غير الحظرية للبرمجة غير المتزامنة.

مقارنة البرمجة المتزامنة وغير المتزامنة

الفرق بين البرمجة المتزامنة وغير المتزامنة من حيث الأداء وتنفيذ البرنامج ووقت التشغيل هو كما يلي:

تنفيذ البرنامج

معًا: في النظام المتزامن، تُنفَّذ المهام بالتتابع، واحدة تلو الأخرى. ونتيجةً لذلك، يسهل التنبؤ بتدفق التحكم وتنفيذه.

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

أداء

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

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

مناسبة البرامج

معًا: مثالي للمواقف التي تتطلب تنفيذ الخطوات بترتيب محدد مسبقًا.

غير متزامن: تعتبر المهام غير متزامنة عندما تكون معتمدة على الإدخال/الإخراج وليس على وحدة المعالجة المركزية.

متى تستخدم البرمجة غير المتزامنة

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

حلقة الأحداث ومكدس النداء

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

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

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

البرمجة غير المتزامنة باستخدام عمال الويب

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

فيما يلي مثال سريع حول كيفية استخدام Web Workers:

// In the main script
const worker = new Worker("./worker.js");
worker.postMessage("Start the task");
// In the worker script (worker.js)
onmessage = function (event) {
// Perform long-running task here
postMessage("Task done");
};

متى تستخدم البرمجة المتزامنة

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

أمثلة عملية في الكود

مثال على الكود المتزامن: معالجة قائمة المهام بشكل تسلسلي

في البرمجة المتزامنة، تُعالَج المهام بالتتابع. إليك مثال في بايثون:

import time
def process_userData(task):
# Simulate task processing time
time.sleep(1)
print(f"Task {task} processed")
tasks = ['task1', 'task2', 'task3']
for task in tasks:
process_userData(task)

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

مثال على الكود غير المتزامن: تلقي البيانات من مصادر متعددة في وقت واحد

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

import asyncio
async def retrieve_data(source):
# Simulate time taken to fetch data
await asyncio.sleep(1)
print(f"Data retrieved {source}")
sources = ['source1', 'source2', 'source3']
async def main():
tasks = retrieve_data(source) for source in sources]
await asyncio.gather(*tasks)
asyncio.run(main())

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

console.log("Start"); // First task (synchronous) - goes to call stack
setTimeout(() => {
console.log("Timeout callback"); // This task(aysnchronous) is put into the event loop
}, 1000);
console.log("End"); // Second task (synchronous) - in call stack

مجموعة النداءات:

وظيفة console.log('ابدأ') يتم تنفيذها أولاً لأنها عملية متزامنة. تُعالَج الدالة وتُزال فورًا من مكدس النداءات.

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

ثم console.log('نهاية') يتم تنفيذه لأنه عملية متزامنة تتم على الخيط الرئيسي.

حلقة الحدث:

بعد المهام المتزامنة (مثل console.log('ابدأ') و console.log('نهاية')) يتم تنفيذها، تنتظر حلقة الحدث تأخيرًا لمدة ثانية واحدة ثم يتم إعطاء معاودة الاتصال غير المتزامنة إلى تعيين مهلة العمليات.

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

المخرجات:

Start
End
Timeout callback

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

أفضل الممارسات والأنماط لاستخدام كل نموذج برمجة بشكل فعال

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

أنماط التصميم الشائعة

البرمجة المتزامنة

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

البرمجة غير المتزامنة
  • الوعود: الوعود تُمثل قيمة قد لا تكون متاحة بعد. تُستخدم لمعالجة العمليات غير المتزامنة وتوفير أساليب لربط عمليات الاسترجاع التي تُستدعى عند توفر القيمة أو عند حدوث خطأ.
  • غير متزامن/انتظار: هذه الميزة تُعدّ بمثابة حلّوى نحوية تُضاف إلى الوعود، مما يجعل الكود غير المتزامن يبدو مشابهًا للكود المتزامن. هذا يُسهّل كتابة الكود غير المتزامن وفهمه.

كيفية تجنب المشاكل الشائعة

جحيم العودة

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

  • قم بتجميع الكود الخاص بك: قم بتقسيم الكود الخاص بك إلى وظائف أصغر قابلة لإعادة الاستخدام.
  • استخدام الوعود أو Async/Await: يمكن لميزات JavaScript هذه تنظيف الكود الخاص بك وجعله أكثر قابلية للقراءة والفهم.
  • معالجة الأخطاء: انتبه دائمًا لمعالجة الأخطاء في عمليات الاستعادة. قد تؤدي الأخطاء غير المعالجة إلى نتائج غير متوقعة.
البرمجة غير المتزامنة – إدارة الذاكرة

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

إدارة الذاكرة في البرمجة غير المتزامنة

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

جمع القمامة

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

الأسباب الشائعة لتسرب الذاكرة في الكود غير المتزامن
  • الوعود غير المحققة: إذا لم يتم الوفاء بالوعد أو رفضه مطلقًا، فقد يؤدي ذلك إلى منع تنظيف الذاكرة.
let pendingPromise = new Promise(function (resolve, reject) {
// This promise never resolves
});
  • مستمعو الحدث: من السهل نسيان حذف مُستمع الأحداث عند عدم الحاجة إليه، مما يُسبب استهلاكًا غير ضروري للذاكرة.
element.addEventListener("click", handleClick);
// Forgetting to remove the listener
// element.removeEventListener('click', handleClick);
  • المؤقتات: استخدام تعيين مهلة أو مجموعة الفاصل الزمني عدم مسحها عندما لم تعد هناك حاجة إليها يمكن أن يؤدي إلى الاحتفاظ بالذاكرة لفترة أطول من اللازم.
var timer = setInterval(function () {
console.log("Running.");
}, 1000);
// Forgetting to clear the interval
// clearInterval(timer);
أفضل الممارسات لمنع تسرب الذاكرة
  • الوعود، الحل أو الرفض: يجب الوفاء بالوعد أو رفضه للتأكد من تحرير ذاكرته عندما لم تعد هناك حاجة إليه.
let myPromise = new Promise((resolve, reject) =>
setTimeout(() => {
resolve("Task complete");
}, 1000),
);
myPromise.then((result) => console.log(result));
  • إزالة مستمعي الأحداث: بمجرد إرفاق مستمعي الأحداث، قم بإزالتهم عندما لم تعد هناك حاجة إليهم، إما لأن العنصر المقابل قد تمت إزالته أو لأن وظيفته لم تعد هناك حاجة إليها.
element.addEventListener("click", handleClick);
// Proper cleanup when no longer needed
element.removeEventListener("click", handleClick);
  • مسح المؤقتات: إذا من تعيين مهلة أو مجموعة الفاصل الزمني إذا كنت تستخدمها، فتذكر أن تقوم بتنظيفها عند الانتهاء منها لتجنب الاحتفاظ بها في الذاكرة بشكل غير ضروري.
var interval = setInterval(function () {
console.log('Doing something...');
}, 1000);
// Clear the interval when done
clearInterval(interval);

المراجع الضعيفة

هناك تقنية متقدمة أخرى وهي استخدام خريطة ضعيفة أو مجموعة ضعيفة يُستخدم لإدارة الكائنات التي قد يُنظّفها جامع القمامة تلقائيًا عند عدم الإشارة إليها في الكود. تتيح لك هذه الهياكل الإشارة إلى الكائنات دون منع جامع القمامة من تنظيفها.

let myWeakMap = new WeakMap();
let obj = {};
myWeakMap.set(obj, "someValue");
// If obj gets dereferenced somewhere else, it will be garbage-collected.
obj = null;

نتيجة

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

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

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

قد يعجبك أيضاً