مضى على الشبكة و يوم من العطاء.

مُقدمة الى الحوسبة المتوازية | Intro to Parallel Computing

STORMSTORM is verified member.

J0rd4n Inj3ct0r
{ || مجلس الإدارة || }

السمعة:

512010.webp

كيف الحال جميعًا، عساكم بألف خير يارب؟
أعتذر عن الإنقطاع في كتابة المواضيع التقنية، لنا رجعة قويّة بإذن المولى تعالى.

الحوسبة المتوازية أو ما يُعرف بال Parallel Computing.

بدايةً: دعنا نأخذ مثال بسيط كي نفهم ماهي
تخيّل أنك موجود في محل صيانة للسيارات وفي داخل المحل يوجد موظف واحد يقوم بكل أعمال الصيانة، ف من الطبيعي أن يكون عمل هذا الموظف بطيء عندما يكون عنده أكثر من سيارة أو حتى أكثر من عطل في السيارة!

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

حيث أنه بعام 1945 تم إصدار اول نموذج للهيكلة المعمارية للحاسوب تحت اسم Von Neumann Architecture

1739225947347.webp


وكانت الفكرة على أن الحاسوب يملك وحدة معالجة مركزية "CPU" يُخزن البيانات والتعليمات في نفس الذاكرة Stored-program computer وكان يعتمد على معالج أحادي النواة وهذا ما يعني أن التسلسل المنطقي يكون على شكل قراءة التعليمة ثم تنفيذها ثم الانتهاء منها، وهذا ما يعني بأن علينا الانتظار كثيراً في بعض الأحيان حتى ينتهي المعالج ثم الانتقال الى العمليات التالية وينفذها جميعاً بشكل "تسلسلي"

وكان أول كمبيوتر في العالم قابل للبرمجة اسمه ENIAC
ENIAC
1739223253819.webp

وبعد ذلك ومع تطور الحاجة للعمليات الحسابية وصل المهندسين الى نقطة مغلقة في حياة هذا النوع من الكمبيوتر وابتكروا حلّاً ليس بالحل الجوهري وانما هو تخطي لمشكلة بفترة مؤقتة وكان اسمه:

Pseudo Multi-Tasking أو ما يُعرف بـ Time-Sharing Systems

فكرة هذا الحل ببساطة هو أن نقوم بتقسيم المدة الزمنية المراد منها الانتهاء من 4 عمليات الى شرائح زمنية قصيرة جداً بالملي ثانية أحياناً، والهدف منها هو عندما تريد النواة أن تقوم بمعالجة أول عملية فإنها بعد فترة زمنية تقطع معالجة العملية الاولى وتنتقل الى العملية الثانية وبينما تبقى العملية الاولى تنتظر وعندما تقوم النواة بمعالجة جزء معين من تعليمات العملية الثانية فإنها تنتقل الى الثالثة وتنقسم هذه العمليات الى قسمين رئيسيات:
القسم الأول: شرائح زمنية Time Slicing

1739389617833.webp


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

1739389874286.webp


عند الانتهاء من الشريحة الزمنية المخصصة للعملية الفُلانية، فإن المعالج يقوم بتبديل العمليات والإنتقال الى العملية التالية تحت اسم Ready State ويستخدم نظام التشغيل ال Interrupts او SysCall
وتخصيص وقت زمني محدد لكل عملية حتى وإن لم يتم الانتهاء منها، كانت تُشعر المستخدم بأن النظام اسرع :D كيف؟ لا تسألني ههه
المهم كانت هذه التقنية السبب الرئيسي وراء الخروج بنظام التشغيل UNIX

وفي الثمانينات والتسعينات ظهر ما يٌسمى بال MultiThreading CPU's وهنا يُمكننا أن نقول بأن الإنفراجة الأولى للكمبيوتر بدأت حيث أن هذا التطور جاء ليُعالج المشاكل الحتمية والسابقة من عمليات المعالجة
لا سيما بأن الكمبيوتر وبرامج الكمبيوتر استمرت في التطور ويُمكننا توضيح ال Threads
بأنها مجموعة من الخيوط تنبثق من النواة Core والتي بدورها تقوم بالتشارك في الموارد المخصصة للعملية الواحدة من الوصول الى الذاكرة والملفات المفتوحة

1739393133915.webp


وتُبيّن الصورة في الأعلى بأن الخيوط قد تشترك في الملفات والترميزات والبيانات ولكن لكل خيط منها stack و registers خاصة بها

دعنا نطرح مثالاً:
يمرّ معنا كل يوم حتى نفهم أهمية الخيوط في حياتنا، لو فتحنا برنامج word للكتابة النصية ف وقمنا بكتابة "السلام عليكم"، الآن يقوم خيط بفحص صحّة الكتابة اللغوية ويقوم خيط آخر بإنتظار المستخدم لإدخال الجديد من النص وإذا أردت تغيير اللون او الخط فهذا يتم تنسيبه إلى خيط ثالث وهكذا. . . .

وهنا نفهم بأن كل حركة داخل ال Word هي تُسمى thread بينما ال word بحد ذاته يٌسمى Process .

ومع وصول المعالجات الى حدودها الفيزيائية ببداية الألفية الثانية، انتهى الغداء المجّاني، وما أقصده هنا بالغداء المجاني الا وهو أن المبرمجين لم يكونوا يعتمدوا على تطوير برامجهم لتحسين سرعتها والاداء إنما كان الاعتماد بشكل كامل على الشركات التي تصنع المعالجات لأن البرامج كانت تعتمد على ال single process معالج احادي النواة، والآن بدأ العصر الذهبي للمعالجات متعددة الأنوية MultiCore Process وزيادة سرعة التردد ينتج عنها زيادة في استهلاك الطاقة وزيادة في الانبعاث الحراري.

وفي عام 2006 أي قبل 19 عام من الآن ظهر أول معالج تجاري يمتلك أكثر من نواة Core 2 duo

1739394788113.webp


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

واستمر التطور في تصنيع هذه المعالجات الى يومنا هذا حتى وصلنا الى معالج xeon max بقدرة 144 نواة ! وتكلفة لا تقل عن 8000 دولار!

وبعد هذه المقدمة التاريخية والنظرية ..
دعنا نخوض قليلاً في طريقة كتابة البرمجة الموازية :

كي تستطيع استخدام ال Thread عليك باستدعاء المكتبة الخاصة بها وهي بلغة ال C++ تكون :
C++:
#include <thread>

وبعدها دعنا نكتب أول برنامج لنا بإستخدام الThreads ونطبع Hello World!
C++:
#include <iostream>
#include <thread>
using namespace std;
void hello()
{
    cout << "Hello Concurrent World\n";
}
int main()
{
    thread t(hello);
    t.join();
}

الجديد هنا هو أننا قمنا بطباعة Hello Concurrent World داخل دالة hello وقمنا بإستدعاء الدالة عن طريق thread ولكي نضمن عدم انتهاء العملية قبل انتهاء الخيط قمنا بإستخدام t.join

1739396720817.webp

حسنًا، الآن لننتقل الى مقارنة سريعة جداً ما بين البرمجة التسلسلية و البرمجة المتوازية

لقد قمت بإستخدام برنامج مكتوب بلغة ال ++C يقوم على جمع الأرقام من 1 الى 10 مليار ثم نقوم بحساب الزمن المستغرق لعملية الجميع لكل من البرمجة التسلسلية والبرمجة الموازية وبعد ذلك نقوم بإيجاد الفارق الزمني بينهم

وقمنا بإستخدام مكتبة Chrono لحساب الزمن واستخدمنا 4 خيوط فقط.

1739396943800.webp

C++:
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;

void sumRange(unsigned long long start, unsigned long long end, unsigned long long& result) {
    unsigned long long sum = 0;
    for (unsigned long long i = start; i <= end; ++i) {
        sum += i;
    }
    result = sum;
}

int main() {
    const unsigned long long N = 10000000000;
    unsigned long long serialSum = 0;

    auto t1 = high_resolution_clock::now();
    for (unsigned long long i = 1; i <= N; ++i) {
        serialSum += i;
    }
    auto t2 = high_resolution_clock::now();
    double serialTime = duration<double>(t2 - t1).count();

    cout << "Sequential: " << serialSum << endl;
    cout << "Time of Sequential: " << serialTime << " sec" << endl << endl;

    const int numThreads = 4;
    vector<thread> threads;
    vector<unsigned long long> results(numThreads, 0);
    unsigned long long blockSize = N / numThreads;

    t1 = high_resolution_clock::now();
    for (int i = 0; i < numThreads; ++i) {
        unsigned long long start = i * blockSize + 1;
        unsigned long long end = (i == numThreads - 1) ? N : (i + 1) * blockSize;
        threads.push_back(thread(sumRange, start, end, ref(results[i])));
    }
    for (auto& th : threads) {
        th.join();
    }
    unsigned long long parallelSum = 0;
    for (int i = 0; i < numThreads; ++i) {
        parallelSum += results[i];
    }
    t2 = high_resolution_clock::now();
    double parallelTime = duration<double>(t2 - t1).count();

    cout << "MultiThreads: " << parallelSum << endl;
    cout << "Time Of MultiThreads: " << parallelTime << " sec" << endl << endl;

    cout << "Time Diff "
        << (serialTime - parallelTime) << " sec" << endl;

    return 0;
}

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


دُمتم بخير
 
التعديل الأخير بواسطة المشرف:
الله يجزيك ألف خير

وفي عام 2006 أي قبل 19 عام من الآن ظهر أول معالج تجاري يمتلك أكثر من نواة Core 2 duo
معالج الشعب .. بداية معظم الشباب بالكمبيوتر
مش عارف كيف كنا نشغل عليه ألعاب الدنيا كلها أما هسا i3 أو i5 حديث تتغلب فيه ببعض الألعاب

و ننتظر جديدك استاذ STORM
 
  • Love
التفاعلات: STORM
مشاهدة المرفق 16718

كيف الحال جميعًا، عساكم بألف خير يارب؟
أعتذر عن الإنقطاع عن كتابة المواضيع التقنية، لنا رجعة قويّة بإذن المولى تعالى.

الحوسبة الموازية أو ما يُعرف بال Parallel Computing.

بدايةً: دعنا نأخذ مثال بسيط كي نفهم ماهي
تخيّل أنك موجود في محل صيانة للسيارات وفي داخل المحل يوجد موظف واحد يقوم بكل أعمال الصيانة، ف من الطبيعي أن يكون عمل هذا الموظف بطيء عندما يكون عنده أكثر من سيارة أو حتى أكثر من عطل في السيارة!

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

حيث أنه بعام 1945 تم إصدار اول نموذج للهيكلة المعمارية للحاسوب تحت اسم Von Neumann Architecture

مشاهدة المرفق 16755

وكانت الفكرة على أن الحاسوب يملك وحدة معالجة مركزية "CPU" يُخزن البيانات والتعليمات في نفس الذاكرة Stored-program computer وكان يعتمد على معالج أحادي النواة وهذا ما يعني أن التسلسل المنطقي يكون على شكل قراءة التعليمة ثم تنفيذها ثم الانتهاء منها، وهذا ما يعني بأن علينا الانتظار كثيراً في بعض الاحيان حتى ينتهي المعالج ثم الانتقال الى العمليات التالية وينفذها جميعاً بشكل "تسلسلي"

وكان أول كمبيوتر في العالم قابل للبرمجة اسمه ENIAC​

وبعد ذلك ومع تطور الحاجة للعمليات الحسابية وصل المهندسين الى نقطة مغلقة في حياة هذا النوع من الكمبيوتر وابتكروا حلّاً ليس بالحل الجوهري وانما هو تخطي لمشكلة بفترة مؤقتة وكان اسمه
Pseudo Multi-Tasking أو ما يُعرف بـ Time-Sharing Systems
فكرة هذا الحل ببساطة هو أن نقوم بتقسيم المدة الزمنية المراد منها الانتهاء من 4 عمليات الى شرائح زمنية قصيرة جداً بالملي ثانية احياناً، والهدف منها هو عندما تريد النواة أن تقوم بمعالجة أول عملية فإنها بعد فترة زمنية تقطع معالجة العملية الاولى وتنتقل الى العملية الثانية وبينما تبقى العملية الاولى تنتظر وعندما تقوم النواة بمعالجة جزء معين من تعليمات العملية الثانية فإنها تنتقل الى الثالثة وتنقسم هذه العمليات الى قسمين ئيسيات:

القسم الأول: شرائح زمنية Time Slicing

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

القسم الثاني: تبديل العمليات Context switching

مشاهدة المرفق 16766

عند الانتهاء من الشريحة الزمنية المخصصة للعملية الفُلانية، فإن المعالج يقوم بتبديل العمليات والإنتقال الى العملية التالية تحت اسم Ready State ويستخدم نظام التشغيل ال Interrupts او SysCall
وتخصيص وقت زمني محدد لكل عملية حتى وإن لم يتم الانتهاء منها، كانت تُشعر المستخدم بأن النظام اسرع :D كيف؟ لا تسألني ههه
المهم كانت هذه التقنية السبب الرئيسي وراء الخروج بنظام التشغيل UNIX

وفي الثمانينات والتسعينات ظهر ما يٌسمى بال MultiThreading CPU's وهنا يُمكننا أن نقول بأن الإنفراجة الأولى للكمبيوتر بدأت حيث أن هذا التطور جاء ليُعالج المشاكل الحتمية والسابقة من عمليات المعالجة
لا سيما بأن الكمبيوتر وبرامج الكمبيوتر استمرت في التطور ويُمكننا توضيح ال Threads بأنها مجموعة من الخيوط تنبثق من النواة Core والتي بدورها تقوم بالتشارك في الموارد المخصصة للعملية الواحدة من الوصول الى الذاكرة والملفات المفتوحة

مشاهدة المرفق 16767

وتُبيّن الصورة في الأعلى بأن الخيوط قد تشترك في الملفات والترميزات والبيانات ولكن لكل خيط منها stack و registers خاصة بها

دعنا نطرح مثالاً:
يمرّ معنا كل يوم حتى نفهم أهمية الخيوط في حياتنا، لو فتحنا برنامج word للكتابة النصية ف وقمنا بكتابة "السلام عليكم"، الآن يقوم خيط بفحص صحّة الكتابة اللغوية ويقوم خيط آخر بإنتظار المستخدم لإدخال الجديد من النص وإذا أردت تغيير اللون او الخط فهذا يتم تنسيبه إلى خيط ثالث وهكذا. . . .
وهنا نفهم بأن كل حركة داخل ال Word هي تُسمى thread بينما ال word بحد ذاته يٌسمى Process .

ومع وصول المعالجات الى حدودها الفيزيائية ببداية الألفية الثانية، انتهى الغداء المجّاني، وما أقصده هنا بالغداء المجاني الا وهو أن المبرمجين لم يكونوا يعتمدوا على تطوير برامجهم لتحسين سرعتها والاداء إنما كان الاعتماد بشكل كامل على الشركات التي تصنع المعالجات لأن البرامج كانت تعتمد على ال single process معالج احادي النواة، والآن بدأ العصر الذهبي للمعالجات متعددة الأنوية MultiCore Process وزيادة سرعة التردد ينتج عنها زيادة في استهلاك الطاقة وزيادة في الانبعاث الحراري.

وفي عام 2006 أي قبل 19 عام من الآن ظهر أول معالج تجاري يمتلك أكثر من نواة Core 2 duo

مشاهدة المرفق 16768

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

واستمر التطور في تصنيع هذه المعالجات الى يومنا هذا حتى وصلنا الى معالج xeon max بقدرة 144 نواة ! وتكلفة لا تقل عن 8000 دولار!

وبعد هذه المقدمة التاريخية والنظرية ..
دعنا نخوض قليلاً في طريقة كتابة البرمجة الموازية :

كي تستطيع إستخدام ال Thread عليك بإستدعاء المكتبة الخاصة بها وهي بلغة ال C++ تكون :
C++:
#include <thread>

وبعدها دعنا نكتب أول برنامج لنا بإستخدام الThreads ونطبع Hello World!
C++:
#include <iostream>
#include <thread>
using namespace std;
void hello()
{
    cout << "Hello Concurrent World\n";
}
int main()
{
    thread t(hello);
    t.join();
}

الجديد هنا هو أننا قمنا بطباعة cout داخل دالة hello وقمنا بإستدعاء الدالة عن طريق thread وكي نضمن أن لا تنتهي العملية قبل انتهاء الخيط قمنا بإستخدام t.join

مشاهدة المرفق 16769

حسنًا، الآن لننتقل الى مقارنة سريعة جداً ما بين البرمجة التسلسلية والبرمجة الموازية:

لقد قمت بإستخدام برنامج مكتوب بلغة ال c++ يقوم على جمع الأرقام من 1 الى 10 مليار ثم نقوم بحساب الزمن المستغرق لعملية الجميع لكل من البرمجة التسلسلية والبرمجة الموازية وبعد ذلك نقوم بإيجاد الفارق الزمني بينهم

وقمنا بإستخدام مكتبة Chrono لحساب الزمن واستخدمنا 4 خيوط فقط.

مشاهدة المرفق 16770

C++:
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;

void sumRange(unsigned long long start, unsigned long long end, unsigned long long& result) {
    unsigned long long sum = 0;
    for (unsigned long long i = start; i <= end; ++i) {
        sum += i;
    }
    result = sum;
}

int main() {
    const unsigned long long N = 10000000000;
    unsigned long long serialSum = 0;

    auto t1 = high_resolution_clock::now();
    for (unsigned long long i = 1; i <= N; ++i) {
        serialSum += i;
    }
    auto t2 = high_resolution_clock::now();
    double serialTime = duration<double>(t2 - t1).count();

    cout << "Sequential: " << serialSum << endl;
    cout << "Time of Sequential: " << serialTime << " sec" << endl << endl;

    const int numThreads = 4;
    vector<thread> threads;
    vector<unsigned long long> results(numThreads, 0);
    unsigned long long blockSize = N / numThreads;

    t1 = high_resolution_clock::now();
    for (int i = 0; i < numThreads; ++i) {
        unsigned long long start = i * blockSize + 1;
        unsigned long long end = (i == numThreads - 1) ? N : (i + 1) * blockSize;
        threads.push_back(thread(sumRange, start, end, ref(results[i])));
    }
    for (auto& th : threads) {
        th.join();
    }
    unsigned long long parallelSum = 0;
    for (int i = 0; i < numThreads; ++i) {
        parallelSum += results[i];
    }
    t2 = high_resolution_clock::now();
    double parallelTime = duration<double>(t2 - t1).count();

    cout << "MultiThreads: " << parallelSum << endl;
    cout << "Time Of MultiThreads: " << parallelTime << " sec" << endl << endl;

    cout << "Time Diff "
        << (serialTime - parallelTime) << " sec" << endl;

    return 0;
}

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


دُمتم بخير
مشاهدة المرفق 16718

كيف الحال جميعًا، عساكم بألف خير يارب؟
أعتذر عن الإنقطاع عن كتابة المواضيع التقنية، لنا رجعة قويّة بإذن المولى تعالى.

الحوسبة الموازية أو ما يُعرف بال Parallel Computing.

بدايةً: دعنا نأخذ مثال بسيط كي نفهم ماهي
تخيّل أنك موجود في محل صيانة للسيارات وفي داخل المحل يوجد موظف واحد يقوم بكل أعمال الصيانة، ف من الطبيعي أن يكون عمل هذا الموظف بطيء عندما يكون عنده أكثر من سيارة أو حتى أكثر من عطل في السيارة!

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

حيث أنه بعام 1945 تم إصدار اول نموذج للهيكلة المعمارية للحاسوب تحت اسم Von Neumann Architecture

مشاهدة المرفق 16755

وكانت الفكرة على أن الحاسوب يملك وحدة معالجة مركزية "CPU" يُخزن البيانات والتعليمات في نفس الذاكرة Stored-program computer وكان يعتمد على معالج أحادي النواة وهذا ما يعني أن التسلسل المنطقي يكون على شكل قراءة التعليمة ثم تنفيذها ثم الانتهاء منها، وهذا ما يعني بأن علينا الانتظار كثيراً في بعض الاحيان حتى ينتهي المعالج ثم الانتقال الى العمليات التالية وينفذها جميعاً بشكل "تسلسلي"

وكان أول كمبيوتر في العالم قابل للبرمجة اسمه ENIAC​

وبعد ذلك ومع تطور الحاجة للعمليات الحسابية وصل المهندسين الى نقطة مغلقة في حياة هذا النوع من الكمبيوتر وابتكروا حلّاً ليس بالحل الجوهري وانما هو تخطي لمشكلة بفترة مؤقتة وكان اسمه
Pseudo Multi-Tasking أو ما يُعرف بـ Time-Sharing Systems
فكرة هذا الحل ببساطة هو أن نقوم بتقسيم المدة الزمنية المراد منها الانتهاء من 4 عمليات الى شرائح زمنية قصيرة جداً بالملي ثانية احياناً، والهدف منها هو عندما تريد النواة أن تقوم بمعالجة أول عملية فإنها بعد فترة زمنية تقطع معالجة العملية الاولى وتنتقل الى العملية الثانية وبينما تبقى العملية الاولى تنتظر وعندما تقوم النواة بمعالجة جزء معين من تعليمات العملية الثانية فإنها تنتقل الى الثالثة وتنقسم هذه العمليات الى قسمين ئيسيات:

القسم الأول: شرائح زمنية Time Slicing

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

القسم الثاني: تبديل العمليات Context switching

مشاهدة المرفق 16766

عند الانتهاء من الشريحة الزمنية المخصصة للعملية الفُلانية، فإن المعالج يقوم بتبديل العمليات والإنتقال الى العملية التالية تحت اسم Ready State ويستخدم نظام التشغيل ال Interrupts او SysCall
وتخصيص وقت زمني محدد لكل عملية حتى وإن لم يتم الانتهاء منها، كانت تُشعر المستخدم بأن النظام اسرع :D كيف؟ لا تسألني ههه
المهم كانت هذه التقنية السبب الرئيسي وراء الخروج بنظام التشغيل UNIX

وفي الثمانينات والتسعينات ظهر ما يٌسمى بال MultiThreading CPU's وهنا يُمكننا أن نقول بأن الإنفراجة الأولى للكمبيوتر بدأت حيث أن هذا التطور جاء ليُعالج المشاكل الحتمية والسابقة من عمليات المعالجة
لا سيما بأن الكمبيوتر وبرامج الكمبيوتر استمرت في التطور ويُمكننا توضيح ال Threads بأنها مجموعة من الخيوط تنبثق من النواة Core والتي بدورها تقوم بالتشارك في الموارد المخصصة للعملية الواحدة من الوصول الى الذاكرة والملفات المفتوحة

مشاهدة المرفق 16767

وتُبيّن الصورة في الأعلى بأن الخيوط قد تشترك في الملفات والترميزات والبيانات ولكن لكل خيط منها stack و registers خاصة بها

دعنا نطرح مثالاً:
يمرّ معنا كل يوم حتى نفهم أهمية الخيوط في حياتنا، لو فتحنا برنامج word للكتابة النصية ف وقمنا بكتابة "السلام عليكم"، الآن يقوم خيط بفحص صحّة الكتابة اللغوية ويقوم خيط آخر بإنتظار المستخدم لإدخال الجديد من النص وإذا أردت تغيير اللون او الخط فهذا يتم تنسيبه إلى خيط ثالث وهكذا. . . .
وهنا نفهم بأن كل حركة داخل ال Word هي تُسمى thread بينما ال word بحد ذاته يٌسمى Process .

ومع وصول المعالجات الى حدودها الفيزيائية ببداية الألفية الثانية، انتهى الغداء المجّاني، وما أقصده هنا بالغداء المجاني الا وهو أن المبرمجين لم يكونوا يعتمدوا على تطوير برامجهم لتحسين سرعتها والاداء إنما كان الاعتماد بشكل كامل على الشركات التي تصنع المعالجات لأن البرامج كانت تعتمد على ال single process معالج احادي النواة، والآن بدأ العصر الذهبي للمعالجات متعددة الأنوية MultiCore Process وزيادة سرعة التردد ينتج عنها زيادة في استهلاك الطاقة وزيادة في الانبعاث الحراري.

وفي عام 2006 أي قبل 19 عام من الآن ظهر أول معالج تجاري يمتلك أكثر من نواة Core 2 duo

مشاهدة المرفق 16768

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

واستمر التطور في تصنيع هذه المعالجات الى يومنا هذا حتى وصلنا الى معالج xeon max بقدرة 144 نواة ! وتكلفة لا تقل عن 8000 دولار!

وبعد هذه المقدمة التاريخية والنظرية ..
دعنا نخوض قليلاً في طريقة كتابة البرمجة الموازية :

كي تستطيع إستخدام ال Thread عليك بإستدعاء المكتبة الخاصة بها وهي بلغة ال C++ تكون :
C++:
#include <thread>

وبعدها دعنا نكتب أول برنامج لنا بإستخدام الThreads ونطبع Hello World!
C++:
#include <iostream>
#include <thread>
using namespace std;
void hello()
{
    cout << "Hello Concurrent World\n";
}
int main()
{
    thread t(hello);
    t.join();
}

الجديد هنا هو أننا قمنا بطباعة cout داخل دالة hello وقمنا بإستدعاء الدالة عن طريق thread وكي نضمن أن لا تنتهي العملية قبل انتهاء الخيط قمنا بإستخدام t.join

مشاهدة المرفق 16769

حسنًا، الآن لننتقل الى مقارنة سريعة جداً ما بين البرمجة التسلسلية والبرمجة الموازية:

لقد قمت بإستخدام برنامج مكتوب بلغة ال c++ يقوم على جمع الأرقام من 1 الى 10 مليار ثم نقوم بحساب الزمن المستغرق لعملية الجميع لكل من البرمجة التسلسلية والبرمجة الموازية وبعد ذلك نقوم بإيجاد الفارق الزمني بينهم

وقمنا بإستخدام مكتبة Chrono لحساب الزمن واستخدمنا 4 خيوط فقط.

مشاهدة المرفق 16770

C++:
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
using namespace std;
using namespace std::chrono;

void sumRange(unsigned long long start, unsigned long long end, unsigned long long& result) {
    unsigned long long sum = 0;
    for (unsigned long long i = start; i <= end; ++i) {
        sum += i;
    }
    result = sum;
}

int main() {
    const unsigned long long N = 10000000000;
    unsigned long long serialSum = 0;

    auto t1 = high_resolution_clock::now();
    for (unsigned long long i = 1; i <= N; ++i) {
        serialSum += i;
    }
    auto t2 = high_resolution_clock::now();
    double serialTime = duration<double>(t2 - t1).count();

    cout << "Sequential: " << serialSum << endl;
    cout << "Time of Sequential: " << serialTime << " sec" << endl << endl;

    const int numThreads = 4;
    vector<thread> threads;
    vector<unsigned long long> results(numThreads, 0);
    unsigned long long blockSize = N / numThreads;

    t1 = high_resolution_clock::now();
    for (int i = 0; i < numThreads; ++i) {
        unsigned long long start = i * blockSize + 1;
        unsigned long long end = (i == numThreads - 1) ? N : (i + 1) * blockSize;
        threads.push_back(thread(sumRange, start, end, ref(results[i])));
    }
    for (auto& th : threads) {
        th.join();
    }
    unsigned long long parallelSum = 0;
    for (int i = 0; i < numThreads; ++i) {
        parallelSum += results[i];
    }
    t2 = high_resolution_clock::now();
    double parallelTime = duration<double>(t2 - t1).count();

    cout << "MultiThreads: " << parallelSum << endl;
    cout << "Time Of MultiThreads: " << parallelTime << " sec" << endl << endl;

    cout << "Time Diff "
        << (serialTime - parallelTime) << " sec" << endl;

    return 0;
}

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


دُمتم بخير
الله يعطيك الف عافية مهندس
بانتظار اي شرح بخصوص البراليل بروسيسينغ
ببالي دكتور ياريته موجود ع المنتدى كان منشنته يفهم معنا :cry:
 
  • Haha
التفاعلات: STORM
تُشعر المستخدم بأن النظام اسرع :D كيف
بخصوص هدا للسؤال اعتقد. انه راجع الى الtime slicing الذي ذكرته من قبل فلو فرضنا انه كل بروسس يأخد 5 nanoseconds كمثال وانتهت المدة الزمنية الاولى لبرنامج لنطلق عليه اسم active بروسس قبل ان ينتهي و مررنا للبروسس الثاني باسم ready process هنا نرى انه باستعمال الcpu بشكل efficient يقلل مدة الidle الخاصة به خصوصا مثلا ادا وصلنا في بروسس ما لمرحلة ادخال معلومات I/O operation سينتقل الcpu تلقائيا لبروسس اخر لانه انتهت المدة وبدلك يخلق للمستعمل او يضهر له انه تم تشغيل العديد من البروسس في وقت قصير. ليس كما هو الامر في المعالج احادي النواة الدي كان سيهنج في نفس البروسس عند وصوله للinput/output operation دون ان يمر لباقي الprocesses
هاته نضرتي للموضوع والله اعلم يمكن يكون لك استادي طرح اخر او ان تكون متفق معي في كلتا الحالتين الموضوع رائع و حقا لمن يريد ان يفكر في الابتكار يجب ان يفهم التاريخ و كيف هؤلاء الاشخاص قاموا بحل مشاكلهم التي واجهتهم. الله يجازيك بالخير اخي ستورم مواضيع جبارة
 
  • Love
التفاعلات: STORM
قوم نظام التشغيل بتقسيم وقت المعالج الى شرائح زمنية قصيرة قد تكون بالملي ثانية أو اقل، ويتم تخصيص هذا الجزء لكل عملية بحيث أنها تستطيع انجاز عدد محدد من العمليات قبل أن ينتقل المعالج الى العملية التي تليها.
توضيحا للفكرة يعتبر main هو الثرد الاساسي الذي يبدا منه تنفيذ البرنامج
ناخذ مثال بسيط باستعمال java اين قمت بتعريف TA عبارة عن thread .
threadA.start(). تقوم بانشاء ثرد جديد ينفذ بداخله دالة run .

Java:
class TA extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("TA thread is executing " );
        }
    }
}


public class test {
    public static void main(String[] args) {
        TA threadA = new TA();
    
        threadA.start();
        for (int i = 1; i <= 5; i++) {
            System.out.println("main is executing " );
        }
      
    }
}
التنفيذ يعطي النتيجة التالية اين نلاحظ انه يقوم بتنفيذ main مرتين بعدها TA خمس مرات اي ينهي التنفيذ بعد ذلك يعود لاستكمال التنفيذ داخل main
thread1.webp

لكن بنفس الكود لو نقوم بالتنفيذ مرة اخرى سنجد انه نفذ 2مرات main بعد ذلك 1مرة TA بعدها3مرات main وبعدها 4 مرات TA
thread2.webp

ولو نفذناها للمرة الثالثة من الامكان ان نجد نتيجة مخالفة تماما للنتائج السابقة
فكما تفضلت سابقا التنفيذ هنا يعتمد على Scheduler وكيف يقوم بادراتها وادارة الذاكرة كما ان لوكانت الثريد تشترك في data معينة , والوصول اليه ليس synchronized كذلك ستؤدي لنتائج مختلفة وذلك راجع لامكانية الوقوع في Race Conditions.
جزاك الله خيرا .
 
ان تكون متفق معي في كلتا الحالتين الموضوع رائع و حقا لمن يريد ان يفكر في الابتكار يجب ان يفهم التاريخ و كيف هؤلاء الاشخاص قاموا بحل مشاكلهم التي واجهتهم.
نعم أتفق معك تماماً بالطرح ولا أختلف حبيبي ايتاتشي
اسعدني مرورك
 
توضيحا للفكرة يعتبر main هو الثرد الاساسي الذي يبدا منه تنفيذ البرنامج
ناخذ مثال بسيط باستعمال java اين قمت بتعريف TA عبارة عن thread .
threadA.start(). تقوم بانشاء ثرد جديد ينفذ بداخله دالة run .

Java:
class TA extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("TA thread is executing " );
        }
    }
}


public class test {
    public static void main(String[] args) {
        TA threadA = new TA();
   
        threadA.start();
        for (int i = 1; i <= 5; i++) {
            System.out.println("main is executing " );
        }
     
    }
}
التنفيذ يعطي النتيجة التالية اين نلاحظ انه يقوم بتنفيذ main مرتين بعدها TA خمس مرات اي ينهي التنفيذ بعد ذلك يعود لاستكمال التنفيذ داخل main
مشاهدة المرفق 16784
لكن بنفس الكود لو نقوم بالتنفيذ مرة اخرى سنجد انه نفذ 2مرات main بعد ذلك 1مرة TA بعدها3مرات main وبعدها 4 مرات TA
مشاهدة المرفق 16785
ولو نفذناها للمرة الثالثة من الامكان ان نجد نتيجة مخالفة تماما للنتائج السابقة
فكما تفضلت سابقا التنفيذ هنا يعتمد على Scheduler وكيف يقوم بادراتها وادارة الذاكرة كما ان لوكانت الثريد تشترك في data معينة , والوصول اليه ليس synchronized كذلك ستؤدي لنتائج مختلفة وذلك راجع لامكانية الوقوع في Race Conditions.
جزاك الله خيرا .
توضيح أكثر من رائع
بارك الله فيك بش مهندسة وجزاك الله كل خير
يجب طرح نقاش بخصوص ال Race
اسعدني مرورك
 
  • Love
التفاعلات: Mina

آخر المشاركات

عودة
أعلى