C++: أساسيات تعدد الخيوط

آخر تحديث: 2026-08-26

في الدرس 42، تعلمنا عن التعابير النمطية.

الآن، سندخل المنطقة المتقدمة من C++ — البرمجة متعددة الخيوط.

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

تعدد الخيوط يتيح لك القيام بعدة أشياء في وقت واحد، مما يحسن الأداء بشكل كبير.


1. نظرة عامة على تعدد الخيوط

(1) 1.1 ما هو الخيط؟

الخيط هو أصغر وحدة تنفيذ للبرنامج.

العملية مقابل الخيط:

تشبيه من الواقع:


(2) 1.2 لماذا نستخدم تعدد الخيوط؟

الميزة الوصف
تحسين الأداء حوسبة متوازية متعددة النوى
استجابة أفضل خيط واجهة المستخدم لا يحجب
تبسيط التصميم تعيين مهام مختلفة لخيوط مختلفة


2. إنشاء الخيوط

(1) 2.1 الاستخدام الأساسي

توفر C++11 صنف std::thread في ملف الرأس thread.

مثال: إنشاء خيط (الصعوبة ⭐)

▶ مثال 1: عرض البرمجة متعددة الخيوط (الصعوبة ⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <thread>

void hello() {
	std::cout << "Hello from thread!" << std::endl;
}

int main() {
	std::thread t(hello); // إنشاء خيط
	t.join(); // انتظار انتهاء الخيط
	
	std::cout << "Main thread ends" << std::endl;
	return 0;
}

الخرج:

TEXT 📖 للعرض فقط
Main thread: starting
Main thread: waiting for child thread
Child thread: Hello from thread
Main thread: ended

نتيجة التشغيل:

TEXT 📖 للعرض فقط
Hello from thread!
Main thread ends

(2) 2.2 join مقابل detach

الدالة الغرض الوصف
join() انتظار انتهاء الخيط تحجب الخيط الحالي
detach() فصل الخيط الخيط يعمل بشكل مستقل، لا يمكن ربطه بعد الآن

مثال: الانتظار بـ join (الصعوبة ⭐)

CPP
#include <iostream>
### ▶ مثال 2: عرض البرمجة متعددة الخيوط (الصعوبة ⭐)

#include <thread>
#include <chrono>

void worker(int id) {
	for (int i = 0; i < 3; i++) {
		std::cout << "Worker " << id << " working..." << std::endl;
		std::this_thread::sleep_for(std::chrono::milliseconds(500));
	}
}

int main() {
	std::thread t1(worker, 1);
	std::thread t2(worker, 2);
	
	t1.join(); // انتظار انتهاء t1
	t2.join(); // انتظار انتهاء t2
	
	std::cout << "All workers done" << std::endl;
	return 0;
}


3. تمرير المعاملات إلى الخيوط

(1) 3.1 تمرير المعاملات

مُنشئ std::thread يمكنه قبول أي كائن قابل للاستدعاء ومعاملات.

مثال: تمرير معاملات (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <thread>
#include <string>

void printMessage(std::string msg, int count) {
	for (int i = 0; i < count; i++) {
		std::cout << msg << std::endl;
	}
}

int main() {
	std::thread t(printMessage, "Hello", 3);
	t.join();
	
	return 0;
}

(2) 3.2 التمرير بالمرجع

افتراضيًا، تُمرر المعاملات بالقيمة. للتمرير بالمرجع، يجب استخدام std::ref.

مثال: التمرير بالمرجع (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <functional>

void increment(int& x) {
	x++;
}

int main() {
	int counter = 0;
	std::thread t(increment, std::ref(counter));
	t.join();
	
	std::cout << "Counter: " << counter << std::endl; // الخرج: 1
	return 0;
}


4. كائنات المزامنة

(1) 4.1 لماذا نحتاج كائنات المزامنة؟

المشكلة: وصول عدة خيوط إلى بيانات مشتركة في وقت واحد يسبب سباقات البيانات.

مثال: سباق بيانات (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <thread>
#include <vector>

int counter = 0;

void increment() {
	for (int i = 0; i < 1000; i++) {
		counter++; // عدة خيوط تعدّل في وقت واحد، النتيجة غير مؤكدة
	}
}

int main() {
	std::vectorstd::thread threads;
	for (int i = 0; i < 10; i++) {
		threads.emplace_back(increment);
	}
	
	for (auto& t : threads) {
		t.join();
	}
	
	std::cout << "Counter: " << counter << std::endl; // المتوقع 10000، الفعلي قد يكون أقل
	return 0;
}

(2) 4.2 حماية البيانات المشتركة بكائن مزامنة

كائن المزامنة (Mutex) يضمن أن خيط واحد فقط يصل إلى البيانات المشتركة في كل مرة.

مثال: الحماية بكائن مزامنة (الصعوبة ⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>

int counter = 0;
std::mutex mtx;

void increment() {
	for (int i = 0; i < 1000; i++) {
		mtx.lock(); // القفل
		counter++;
		mtx.unlock(); // فتح القفل
	}
}

int main() {
	std::vectorstd::thread threads;
	for (int i = 0; i < 10; i++) {
		threads.emplace_back(increment);
	}
	
	for (auto& t : threads) {
		t.join();
	}
	
	std::cout << "Counter: " << counter << std::endl; // دائمًا 10000
	return 0;
}

(3) 4.3 lock_guard — أسلوب RAII

موصى به: استخدم std::lock_guard للقفل/فتح القفل تلقائيًا.

TEXT 📖 للعرض فقط
void increment() {
	for (int i = 0; i < 1000; i++) {
		std::lock_guardstd::mutex lock(mtx); // يقفل عند الإنشاء، يفتح عند التدمير
		counter++;
	} // فتح تلقائي
}


5. متغيرات الحالة

(1) 5.1 لماذا نحتاج متغيرات الحالة؟

المشكلة: خيط يحتاج انتظار شرط ليصبح صحيحًا (مثلاً: الطابور ليس فارغًا).

الحل: std::condition_variable


(2) 5.2 مثال: المنتج-المستهلك (الصعوبة ⭐⭐⭐)

CPP
#include <iostream>
#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

std::queueint q;
std::mutex mtx;
std::condition_variable cv;

void producer() {
	for (int i = 0; i < 10; i++) {
		std::lock_guardstd::mutex lock(mtx);
		q.push(i);
		std::cout << "Produced: " << i << std::endl;
		cv.notify_one(); // إخطار المستهلك
	}
}

void consumer() {
	for (int i = 0; i < 10; i++) {
		std::unique_lockstd::mutex lock(mtx);
		cv.wait(lock, { return !q.empty(); }); // انتظر حتى لا يكون الطابور فارغًا
		
		int value = q.front();
		q.pop();
		std::cout << "Consumed: " << value << std::endl;
	}
}

int main() {
	std::thread t1(producer);
	std::thread t2(consumer);
	
	t1.join();
	t2.join();
	
	return 0;
}

الخرج:

TEXT 📖 للعرض فقط
Produced: 
Consumed: 


6. المهام غير المتزامنة

(1) 6.1 std::async

std::async يُستخدم لإطلاق مهمة غير متزامنة ويعيد std::future.

مثال: حساب غير متزامن (الصعوبة ⭐⭐)

TEXT 📖 للعرض فقط
#include <iostream>
#include <future>

int calculate(int x) {
	return x * x;
}

int main() {
	std::futureint result = std::async(calculate, 10);
	std::cout << "Result: " << result.get() << std::endl; // الخرج: 100
	
	return 0;
}

❓ أسئلة شائعة

س: كم خيط يجب أن أستخدم؟ ج: عادةً يساوي عدد نوى وحدة المعالجة المركزية. الخيوط الكثيرة جدًا تسبب عبء تبديل السياق.


س: ما هو الجمود المتبادل؟ ج: خيطان ينتظران تحرير أقفال بعضهما البعض، فلا يستطيع أي منهما المتابعة.

كيفية التجنب:


س std::thread أم OpenMP — أيهما أفضل؟
ج - std::thread: مرن، متعدد المنصات - OpenMP: بسيط، جيد للحوسبة العلمية

▶ مثال 3: إنشاء خيوط (الصعوبة ⭐)

CPP
#include <iostream>
#include <thread>

void printNumbers(int start, int end) {
    for (int i = start; i <= end; i++) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::thread t1(printNumbers, 1, 5);
    std::thread t2(printNumbers, 10, 15);

    t1.join();
    t2.join();

    return 0;
}
▶ جرّب الكود

الخرج:

TEXT 📖 للعرض فقط
 

مثال على الخرج:

TEXT 📖 للعرض فقط
1 10 2 11 3 12 4 13 5 14 15
💡 نصيحة: std::thread يُنشئ خيوط، join() ينتظر انتهاءها. قد يختلف ترتيب الخرج بسبب جدولة الخيوط.


النقطة الرئيسية الملخص
std::thread إنشاء خيوط
join/detach انتظار/فصل الخيوط
std::mutex كائن مزامنة، يحمي البيانات المشتركة
std::lock_guard قفل بأسلوب RAII
std::condition_variable متغير حالة، تواصل بين الخيوط
std::async مهام غير متزامنة

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): أنشئ خيطين يخرجان "Thread A" و"Thread B" على التوالي، ولاحظ عشوائية ترتيب الخرج.

  2. متوسط (الصعوبة ⭐⭐): أنشئ 4 خيوط، كل منها يحسب مجموعًا تراكميًا لمدى من الأرقام (مثلاً 1-2500، 2501-5000...)، ثم اجمع النتائج في النهاية.

  3. متقدم (الصعوبة ⭐⭐⭐): استخدم std::async وstd::future لتنفيذ محاكي تحميل متزامن: أنشئ 3 مهام غير متزامنة، كل منها تحاكي تحميل ملف بحجم مختلف، وانتظر اكتمالها جميعًا قبل التلخيص.



الدرس التالي: مزامنة تعدد الخيوط (#44)

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%