Skip to content

التزامن، غير المتزامن، وتعدد الخيوط

💡 دليل التعلم: البرمجة المتزامنة هي "كعب أخيل" للعديد من مهندسي الواجهة الخلفية — حيث يتم إسكاتهم في المقابلات، وتظهر الأخطاء في الإنتاج، ولا يجدون أفكارًا لتحسين الأداء. سيركز هذا الفصل على سؤال جوهري واحد: عندما يطلب 100,000 مستخدم خدمتك في نفس الوقت، هل سينهار كودك؟

قبل البدء، يُنصح بتعزيز "لبنتين أساسيتين":

  • ما هي CPU، الذاكرة، و I/O: إذا لم تكن واضحًا بشأن هذه المفاهيم الأساسية، يمكنك مراجعة المعرفة الأساسية لنظام التشغيل أولاً.
  • ما هو الحظر/عدم الحظر (Blocking/Non-blocking): إذا لم تكن معتادًا بعد على مفهوم التزامن/غير المتزامن، يمكنك تجربته عمليًا من خلال البرمجة أولاً.

0. مقدمة: لماذا "تتجمد" خدمتك في أوقات الذروة؟

Process / Thread / Coroutine Comparison

Memory Usage
400
MB
Context Switches
0
Completed Tasks
0
Elapsed Time
0
ms
CPU 1
CPU 2
CPU 3
CPU 4
Task Queue
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Task 7
Task 8
Task 9
Task 10
Task 11
Task 12
Task 13
Task 14
Task 15
Task 16

يواجه الكثيرون مواقف مشابهة في التطوير الفعلي:

  • الخدمة تستجيب بسرعة في الاختبار المحلي، لكنها "تتجمد" فور النشر؛
  • اشتريت خادمًا بمواصفات عالية، لكن استخدام CPU لا يرتفع أبدًا؛
  • في أوقات ذروة العروض الترويجية، تنهار الخدمة، مما يضطرك لتقليل الخدمات أو استخدام قواطع الدائرة (Circuit Breaker).

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

التناقض الأساسي:

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

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


1. المفاهيم الأساسية: Process، Thread، Coroutine، ما الفرق بينها؟

1.1 تشبيه المطعم

تخيل أنك تفتح مطعمًا وتريد خدمة العديد من الزبائن في نفس الوقت:

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

1.2 مقارنة معمقة: الاختلافات الجوهرية بين الثلاثة

Process Memory Isolation Demo

System Memory

Process: "حاوية" عزل الموارد

الخصائص الأساسية:

  • عزل قوي: كل عملية لديها مساحة عنوان افتراضية مستقلة
  • تكلفة عالية: الإنشاء/التبديل يحتاج تدخل نظام التشغيل، ويستغرق حوالي 1-10ms
  • اتصال معقد: الاتصال بين العمليات (IPC) يحتاج آليات خاصة (أنابيب، طوابير رسائل، ذاكرة مشتركة، إلخ)

سيناريوهات الاستخدام:

  • الخدمات التي تحتاج عزلًا قويًا (مثل علامات تبويب المتصفح، برامج Sandbox)
  • الخدمات التي تدمج لغات متعددة
  • وحدات الخدمة التي تحتاج إعادة تشغيل/ترقية مستقلة

Thread: "الفرسان الخفاف" للذاكرة المشتركة

Thread Scheduling Demo

Timeline
0ms
100ms
200ms
300ms
400ms
500ms
0
Completed Threads
0
Context Switches
0ms
Avg Wait Time
0
Throughput (threads/s)
Current Scheduling Algorithm: Round Robin (Time Slice)

Each thread takes turns executing for a time slice. When the slice expires, it switches to the next thread. Good responsiveness, suitable for interactive systems.

الخصائص الأساسية:

  • ذاكرة مشتركة: الخيوط داخل نفس العملية تشارك مقطع الكود (Code Segment)، مقطع البيانات (Data Segment)، والكومة (Heap)
  • مساحة مكدس مستقلة: كل خيط لديه مكدس خاص به (عادة حوالي 1MB)
  • تبديل سريع نسبيًا: تبديل الخيوط حوالي 1-10μs، أسرع بـ 1000 مرة من العملية
  • يحتاج مزامنة: البيانات المشتركة تحتاج حماية بالأقفال

سيناريوهات الاستخدام:

  • المهام المكثفة CPU (الحساب، معالجة الصور)
  • المهام المتزامنة التي تحتاج مشاركة كمية كبيرة من البيانات
  • المهام الخلفية الحساسة للتأخير

Coroutine: "الخيوط الخضراء" في وضع المستخدم

Coroutine Lightweight Comparison Demo

1000 coroutines
Thread Model
Memory Usage
1000 MB
Creation Time
100 ms
Context Switch
~1-10 us
VS
Coroutine Model
Memory Usage
2000 MB
Creation Time
10 ms
Context Switch
~100 ns
Saves -100% Memory

الخصائص الأساسية:

  • جدولة في وضع المستخدم: يتم جدولتها بواسطة البرنامج/مكتبة وقت التشغيل، دون المرور بنظام التشغيل
  • خفيفة الوزن جدًا: مكدس الكوروتين عادة بضع KB فقط، ويمكن إنشاء الملايين
  • تبديل سريع جدًا: تبديل الكوروتين حوالي 100ns، أسرع بـ 100 مرة من الخيط
  • غير استباقية (Non-preemptive): الكوروتين يتخلى طوعًا عن CPU (تعدد المهام التعاوني)

سيناريوهات الاستخدام:

  • خدمات I/O المكثفة ذات التزامن العالي (خوادم الويب، البوابات)
  • السيناريوهات التي تحتاج الحفاظ على عدد كبير من الاتصالات الطويلة (المراسلة الفورية، خوادم الألعاب)
  • معالجة البيانات المتدفقة، عمليات خط الأنابيب

2. دراسة حالة: "آلام التزامن" في عرض ترويجي للتجارة الإلكترونية

2.1 دروس من الدم والدموع: التطور من "الجهاز الواحد" إلى "الموزع"

لنشاهد قصة حقيقية لتطور نظام التجارة الإلكترونية:

المرحلة الأولى: عصر الجهاز الواحد (المستخدمين النشطين يوميًا 1000)

python
# تطبيق Flask بسيط
from flask import Flask

app = Flask(__name__)

@app.route('/order')
def create_order():
    # الاستعلام عن المخزون
    stock = db.query("SELECT stock FROM products WHERE id=1")
    if stock > 0:
        # خصم المخزون
        db.execute("UPDATE products SET stock = stock - 1 WHERE id=1")
        # إنشاء الطلب
        db.execute("INSERT INTO orders ...")
        return "Order created!"
    return "Out of stock!"

# التشغيل: flask run

المشاكل:

  • عملية واحدة بخيط واحد، يمكنها معالجة طلب واحد فقط في كل مرة
  • خصم المخزون بدون قفل، مما يؤدي إلى بيع زائد عند التزامن
  • عدد اتصالات قاعدة البيانات محدود، وسرعان ما ينفد تجمع الاتصالات

المرحلة الثانية: عصر تعدد العمليات (المستخدمين النشطين يوميًا 10,000)

python
# استخدام Gunicorn للنشر متعدد العمليات
gunicorn -w 4 -k sync app:app

# 4 عمليات worker، كل عملية تعالج الطلبات بشكل مستقل

مشاكل جديدة:

  • 4 عمليات تستعلم عن المخزون في نفس الوقت، كلها ترى stock=1، كلها تنجح في الخصم، بيع زائد لـ 3!
  • الحاجة لإدخال القفل الموزع (Distributed Lock)
python
import redis

# استخدام Redis للقفل الموزع
lock = redis_client.lock("stock_lock", timeout=10)
if lock.acquire():
    try:
        stock = db.query("SELECT stock FROM products WHERE id=1")
        if stock > 0:
            db.execute("UPDATE products SET stock = stock - 1 WHERE id=1")
    finally:
        lock.release()

المرحلة الثالثة: عصر الكوروتين (المستخدمين النشطين يوميًا 100,000)

python
# استخدام FastAPI + asyncio
from fastapi import FastAPI
import asyncio

app = FastAPI()

async def check_stock(product_id: int) -> int:
    # استعلام غير متزامن لقاعدة البيانات، لا يحظر
    result = await db.fetch_one(
        "SELECT stock FROM products WHERE id = :id",
        {"id": product_id}
    )
    return result["stock"]

@app.get("/order")
async def create_order(product_id: int):
    # التحقق المتزامن من المخزون ومعلومات المستخدم
    stock_task = check_stock(product_id)
    user_task = get_user_info(request.user_id)

    stock, user = await asyncio.gather(stock_task, user_task)

    if stock > 0:
        # خصم المخزون بشكل غير متزامن
        await db.execute(
            "UPDATE products SET stock = stock - 1 WHERE id = :id",
            {"id": product_id}
        )
        return {"status": "success"}

    return {"status": "out_of_stock"}

# التشغيل: uvicorn main:app --workers 4
# كل worker يمكنه معالجة آلاف الكوروتينات المتزامنة

المزايا:

  • يمكن معالجة آلاف الاتصالات المتزامنة داخل خيط واحد
  • عند عمليات I/O، يتم التخلي عن CPU طواعية، دون حظر الطلبات الأخرى
  • استهلاك ذاكرة منخفض جدًا، مناسب لسيناريوهات التزامن العالي والاتصالات الطويلة

2.2 جدول مقارنة تطور نماذج التزامن

المرحلةنموذج التزامنالمستخدمين النشطين المدعومينالمشكلة الأساسيةالحل
الجهاز الواحدعملية واحدة بخيط واحد1Kلا يمكن المعالجة المتزامنةإدخال تعدد العمليات
تعدد العملياتعمليات متعددة متزامنة10Kتسابق البيانات، بيع زائدالقفل الموزع
تعدد الخيوطخيوط متعددة + أقفال50Kتكلفة تبديل السياق، الجمود (Deadlock)تجمع الخيوط، طوابير بدون أقفال
الكوروتينI/O غير متزامن100K+تعقيد الكود، صعوبة التصحيحتغليف الإطار، تتبع الروابط
هجينعمليات متعددة + كوروتين1000K+تعقيد المعماريةحوكمة الخدمة، التوسع المرن

3. تعمق في المبادئ: كيفية عمل نماذج التزامن المختلفة

3.1 نموذج Process: العزل والاتصال

آلية عزل الذاكرة

Process Memory Isolation Demo

System Memory

كل عملية لديها مساحة عنوان افتراضية مستقلة:

الذاكرة الافتراضية للعملية A        الذاكرة الافتراضية للعملية B
+----------------+        +----------------+
|  مساحة النواة   |        |  مساحة النواة   |  <-- مشتركة (للقراءة فقط)
|  (مشتركة)       |        |  (مشتركة)       |
+----------------+        +----------------+
|  مساحة المكدس   |        |  مساحة المكدس   |  <-- مستقلة
|  (تنمو للأسفل)  |        |  (تنمو للأسفل)  |
+----------------+        +----------------+
|  مساحة الكومة   |        |  مساحة الكومة   |  <-- مستقلة
|  (تنمو للأعلى)  |        |  (تنمو للأعلى)  |
+----------------+        +----------------+
|  مقطع البيانات  |        |  مقطع البيانات  |  <-- مستقلة
|  (.bss/.data)   |        |  (.bss/.data)   |
+----------------+        +----------------+
|  مقطع الكود     |        |  مقطع الكود     |  <-- مستقلة
|  (.text)        |        |  (.text)        |
+----------------+        +----------------+

طرق الاتصال بين العمليات (IPC)

الطريقةالمبدأالسرعةالسيناريو المناسب
الأنابيب (Pipe)مخزن kernel المؤقت، تدفق أحادي الاتجاهمتوسطةالاتصال بين العمليات الأصل والفرعية
طابور الرسائل (Message Queue)قائمة رسائل مترابطة في kernelمتوسطةتمرير الرسائل غير المتزامن
الذاكرة المشتركة (Shared Memory)نفس الذاكرة الفعلية المعينةالأسرعمشاركة كمية كبيرة من البيانات
الإشارات (Semaphore)عداد في kernel-التزامن والاستبعاد المتبادل
Socketمكدس بروتوكولات الشبكةأبطأالاتصال عبر الأجهزة
الإشارات (Signal)مقاطعة برمجية-إشعارات الأحداث

3.2 نموذج Thread: الجدولة والمزامنة

مبدأ جدولة الخيوط

Thread Scheduling Demo

Timeline
0ms
100ms
200ms
300ms
400ms
500ms
0
Completed Threads
0
Context Switches
0ms
Avg Wait Time
0
Throughput (threads/s)
Current Scheduling Algorithm: Round Robin (Time Slice)

Each thread takes turns executing for a time slice. When the slice expires, it switches to the next thread. Good responsiveness, suitable for interactive systems.

العمل الأساسي لمجدول الخيوط في نظام التشغيل:

طابور الجاهزية                     قيد التشغيل                    طابور الانتظار
+--------+                +--------+               +--------+
| الخيط B |  <-- انتهاء    | الخيط A |  <-- طلب I/O  | الخيط C |
| الخيط D |     الشريحة    | (يعمل)  |               | الخيط E |
| الخيط F |     الزمنية    +--------+               | (محظور) |
+--------+                                         +--------+
    |                                                  |
    v                                                  v
المجدول يختار التالي للتشغيل حسب الأولوية    عند اكتمال I/O، يعود إلى طابور الجاهزية

آليات مزامنة الخيوط الشائعة

الآليةالمبدأالمزاياالعيوب
قفل الاستبعاد المتبادل (Mutex)حالة ثنائية، وصول حصريتنفيذ بسيطأداء ضعيف عند المنافسة الشديدة
قفل القراءة والكتابة (RWLock)قراءة مشتركة، كتابة حصريةكفاءة عالية في سيناريوهات القراءة الكثيرة والكتابة القليلةتنفيذ معقد، خطر تجويع الكتابة
قفل الدوران (Spinlock)انتظار مشغول، لا يحرر CPUكفاءة عالية عندما يكون وقت الانتظار قصيرًاإهدار CPU عندما يكون وقت الانتظار طويلاً
متغير الشرط (Condition Variable)انتظار تحقق شرط معينتجنب الانتظار المشغوليحتاج استخدامه مع القفل
الإشارة (Semaphore)عداد يتحكم في عدد الوصوليمكنه التحكم في عدد التزامنسهل الخطأ عند الاستخدام غير الصحيح
العمليات الذرية (Atomic Operations)ذرية على مستوى تعليمات CPUبدون أقفال، أعلى أداءيمكنها فقط معالجة أنواع البيانات البسيطة
الطابور بدون قفل (Lock-free Queue)تنفيذ باستخدام CASأداء ممتاز تحت التزامن العاليتنفيذ معقد، مشكلة ABA

3.3 نموذج Coroutine: الجدولة في وضع المستخدم

Coroutine Lightweight Comparison Demo

1000 coroutines
Thread Model
Memory Usage
1000 MB
Creation Time
100 ms
Context Switch
~1-10 us
VS
Coroutine Model
Memory Usage
2000 MB
Creation Time
10 ms
Context Switch
~100 ns
Saves -100% Memory

المزايا الأساسية للكوروتين

تعدد الخيوط التقليدي         مقابل           نموذج الكوروتين

+------------+                       +------------+
|  الخيط 1   |                       |  حلقة الأحداث |
| (مكدس 1MB) |                       |  (المجدول)   |
+------------+                       +------------+
     |                                     |
     v                                     v
+------------+                       +------------+
|  الخيط 2   |                       |  كوروتين A  |
| (مكدس 1MB) |                       | (مكدس بضع KB)|
+------------+                       +------------+
     |                                     |
     v                                     v
+------------+                       +------------+
|  الخيط 3   |                       |  كوروتين B  |
| (مكدس 1MB) |                       | (مكدس بضع KB)|
+------------+                       +------------+

التكلفة: N MB                        التكلفة: N KB
الإنشاء: ~10μs                       الإنشاء: ~100ns
التبديل: ~1μs                        التبديل: ~100ns

آلية عمل async/await

async/await Mechanism Demo

Python asyncio Example
import asyncio

async def fetch_data(url):
    # await suspends, yields CPU
    response = await aiohttp.get(url)
    # Continue after I/O completes
    return response.json()

async def main():
    # Concurrent execution
    tasks = [fetch_data(url) for url in urls]
    results = await asyncio.gather(*tasks)
Execution Timeline
0ms
50ms
100ms
150ms
200ms
Event Loop
Scheduling
Task 1
Exec
I/O
Exec
I/O
Exec
I/O
Task 2
Exec
I/O
Exec
I/O
Exec
I/O
Task 3
Exec
I/O
Exec
I/O
Exec
I/O
Task 4
Exec
I/O
Exec
I/O
Exec
I/O
Task 5
Exec
I/O
Exec
I/O
Exec
I/O
Concurrent Tasks
5
Total Time
100ms
I/O Wait Time
60ms
CPU Utilization
40%
python
import asyncio

async def fetch_data(url):
    # عند مواجهة await، يتم تعليق الكوروتين، والتخلي عن CPU
    response = await aiohttp.get(url)
    # بعد اكتمال I/O، توقظ حلقة الأحداث الكوروتين، ويستمر التنفيذ من هنا
    return response.json()

async def main():
    # إنشاء 3 مهام كوروتين
    tasks = [
        fetch_data("https://api1.example.com"),
        fetch_data("https://api2.example.com"),
        fetch_data("https://api3.example.com")
    ]
    # تنفيذ متزامن، الوقت الإجمالي ≈ أبطأ طلب
    results = await asyncio.gather(*tasks)
    return results

# بدء حلقة الأحداث
asyncio.run(main())

تسلسل التنفيذ:

الخط الزمني ---------------------------------------------------------------->

كوروتين A: [تحضير الطلب]--[await تعليق]=======[استلام الرد]--[معالجة البيانات]
                           |
كوروتين B:                 [تحضير الطلب]--[await تعليق]=======[استلام الرد]--[معالجة البيانات]
                                        |
كوروتين C:                              [تحضير الطلب]--[await تعليق]=======[استلام الرد]
                                                     |

                                               اكتمال كل I/O

الشرح: [ ] يمثل تنفيذ CPU, === يمثل انتظار I/O, | يمثل تبديل الكوروتين

3.4 حلقة الأحداث (Event Loop): "قلب" الكوروتين

Event Loop Demo

Call Stack
Stack Empty
Event Loop
Check
1Execute synchronous code in the call stack
2Execute all microtasks
3Render UI (if needed)
4Execute macrotask
Task Queue
Microtask Queue
Queue Empty
Macrotask Queue
Queue Empty

حلقة الأحداث هي الآلية الأساسية لجدولة الكوروتين:

python
import selectors
import heapq

class EventLoop:
    def __init__(self):
        self.selector = selectors.DefaultSelector()
        self.ready = []  # طابور الجاهزية
        self.scheduled = []  # طابور المهام المجدولة
        self.current = None

    def run(self):
        while True:
            # 1. معالجة المهام المجدولة
            now = time.time()
            while self.scheduled and self.scheduled[0][0] <= now:
                _, callback = heapq.heappop(self.scheduled)
                self.ready.append(callback)

            # 2. انتظار أحداث I/O
            timeout = 0 if self.ready else 0.1
            events = self.selector.select(timeout)

            for key, mask in events:
                callback = key.data
                self.ready.append(callback)

            # 3. تنفيذ الردود الجاهزة
            while self.ready:
                callback = self.ready.popleft()
                callback()

3.5 التزامن (Concurrency) مقابل التوازي (Parallelism): ليسا نفس الشيء

Concurrency vs Parallelism Demo

CPU Core (Single Core)
CPU 1
Idle
CPU 2
Idle
CPU 3
Idle
CPU 4
Idle
Task Execution
Task 1
40ms
Task 2
30ms
Task 3
50ms
Task 4
35ms
Concurrency vs Parallelism
🔄
Concurrency
Multiple tasks alternate execution, progressing simultaneously at a macro level
Examples: Single-core CPU multi-threading, coroutine scheduling, async I/O
Parallelism
Multiple tasks execute truly simultaneously
Examples: Multi-core CPU computing, GPU parallel computing, distributed processing
What Conditions Are Needed?
Concurrency: A single-core CPU is sufficient
Parallelism: Requires multi-core CPU or multiple machines
المفهومالإنجليزيةالمعنىالتشبيهالمتطلبات
التزامنConcurrencyمهام متعددة تتنفذ بالتناوب، تتقدم معًا على المستوى الكليشخص واحد يطبخ عدة أطباق بالتناوبCPU أحادي النواة يكفي
التوازيParallelismمهام متعددة تتنفذ حقًا في نفس الوقتعدة أشخاص يطبخون أطباقًا مختلفة في نفس الوقتCPU متعدد النوى أو أجهزة متعددة

رسم توضيحي:

CPU أحادي النواة - التزامن (Concurrent)
الوقت →  1    2    3    4    5    6    7    8
مهمة A: [تنفيذ][تنفيذ]      [تنفيذ][تنفيذ]
مهمة B:      [تنفيذ][تنفيذ]      [تنفيذ][تنفيذ]

مهمتان تتنفذان بالتناوب، وتتقدم "معًا" على المستوى الكلي

========================================

CPU متعدد النوى - التوازي (Parallel)
الوقت →  1    2    3    4    5    6    7    8
النواة 1: [مهمةA][مهمةA][مهمةA][مهمةA]
النواة 2: [مهمةB][مهمةB][مهمةB][مهمةB]

مهمتان تتنفذان حقًا "في نفس الوقت"

========================================

في الواقع غالبًا: التزامن + التوازي
الوقت →  1    2    3    4    5    6    7    8
النواة 1: [A1][A1][B1][B1][C1][C1][D1][D1]
النواة 2: [A2][A2][B2][B2][C2][C2][D2][D2]

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

4. التطبيق العملي: Go Coroutine والخيوط الخضراء

4.1 فلسفة Go في التزامن

Go Goroutine & GMP Scheduling Demo

Global Queue (G)3
G1
G2
G3
P (Processors) - 4 Total
P0Running
Local Queue
G4
G5
G6
Bound to M0
P1Idle
Local Queue
G7
G8
G9
P2Idle
Local Queue
G10
G11
G12
P3Idle
Local Queue
-
M (Machine Threads) - 4 Total
M0Running
M1Sleeping
M2Sleeping
M3Sleeping

فلسفة تصميم التزامن في لغة Go: لا تتواصل من خلال مشاركة الذاكرة، بل شارك الذاكرة من خلال التواصل.

go
package main

import (
    "fmt"
    "time"
)

// المنتج
func producer(ch chan<- int, id int) {
    for i := 0; i < 5; i++ {
        fmt.Printf("Producer %d sending: %d\n", id, i)
        ch <- i  // إرسال البيانات إلى channel
        time.Sleep(100 * time.Millisecond)
    }
}

// المستهلك
func consumer(ch <-chan int, id int) {
    for val := range ch {  // استقبال البيانات من channel
        fmt.Printf("Consumer %d received: %d\n", id, val)
    }
}

func main() {
    // إنشاء channel مع مخزن مؤقت
    ch := make(chan int, 10)

    // بدء 2 من goroutine المنتجة
    for i := 0; i < 2; i++ {
        go producer(ch, i)
    }

    // بدء 2 من goroutine المستهلكة
    for i := 0; i < 2; i++ {
        go consumer(ch, i)
    }

    // الانتظار لبعض الوقت
    time.Sleep(3 * time.Second)
    close(ch)
}

4.2 مجدول Goroutine: نموذج GMP

يستخدم مجدول Go نموذج GMP:

المكونالمعنىالدور
G (Goroutine)كوروتينالمهمة المراد تنفيذها، خفيفة الوزن (مكدس 2KB، قابل للتمدد ديناميكيًا)
M (Machine)خيط النظامالحامل الفعلي لتنفيذ G، يتوافق 1:1 مع خيط النواة
P (Processor)المعالج المنطقيسياق الجدولة، يحتوي على طابور G القابل للتنفيذ، العدد الافتراضي يساوي عدد أنوية CPU

تدفق الجدولة:

الطابور العام
+----------------+
|  G1  |  G2  |  G3  |
+----------------+

طابور P0 المحلي      طابور P1 المحلي      طابور P2 المحلي      طابور P3 المحلي
+----------+       +----------+       +----------+       +----------+
| G4 | G5  |       | G6 | G7  |       | G8 | G9  |       | G10| G11 |
+----------+       +----------+       +----------+       +----------+
    |                     |                     |                     |
    v                     v                     v                     v
+----------+       +----------+       +----------+       +----------+
|    M0    |       |    M1    |       |    M2    |       |    M3    |
| (خيط OS) |       | (خيط OS) |       | (خيط OS) |       | (خيط OS) |
+----------+       +----------+       +----------+       +----------+

استراتيجية الجدولة:
1. كل P يحتفظ بطابور G محلي، لتقليل تنافس الأقفال
2. P يأخذ G من الطابور المحلي ويسلمه لـ M للتنفيذ
3. عندما يكون الطابور المحلي فارغًا، "يسرق" نصف G من P آخر (Work Stealing)
4. الطابور العام يعمل كاحتياطي، يتم فحصه كل فترة زمنية

5. قوالب كود عملية

5.1 قالب Python asyncio للتزامن العالي

python
import asyncio
import aiohttp
from typing import List, Dict
import time

class AsyncHTTPClient:
    """عميل HTTP عالي الأداء مبني على asyncio"""

    def __init__(self, max_connections: int = 100, timeout: int = 30):
        self.timeout = aiohttp.ClientTimeout(total=timeout)
        # تقييد عدد الاتصالات المتزامنة، لمنع إغراق خدمة الطرف الآخر
        connector = aiohttp.TCPConnector(
            limit=max_connections,
            limit_per_host=10,  # تقييد الاتصالات لنطاق واحد
            enable_cleanup_closed=True,
            force_close=True,
        )
        self.session = aiohttp.ClientSession(
            connector=connector,
            timeout=self.timeout,
        )

    async def fetch(self, url: str, method: str = 'GET', **kwargs) -> Dict:
        """إرسال طلب واحد"""
        try:
            async with self.session.request(method, url, **kwargs) as response:
                return {
                    'url': url,
                    'status': response.status,
                    'data': await response.text(),
                    'error': None
                }
        except asyncio.TimeoutError:
            return {'url': url, 'status': None, 'data': None, 'error': 'Timeout'}
        except Exception as e:
            return {'url': url, 'status': None, 'data': None, 'error': str(e)}

    async def fetch_many(self, urls: List[str], concurrency: int = 10) -> List[Dict]:
        """جلب عدة URLs بشكل متزامن، مع تقييد عدد التزامن"""
        semaphore = asyncio.Semaphore(concurrency)

        async def fetch_with_limit(url):
            async with semaphore:
                return await self.fetch(url)

        # تنفيذ جميع الطلبات بشكل متزامن
        tasks = [fetch_with_limit(url) for url in urls]
        return await asyncio.gather(*tasks, return_exceptions=True)

    async def close(self):
        await self.session.close()


# مثال على الاستخدام
async def main():
    client = AsyncHTTPClient(max_connections=50)

    # قائمة URLs المطلوب جلبها
    urls = [
        "https://api.github.com/users/github",
        "https://api.github.com/users/google",
        "https://api.github.com/users/microsoft",
        # ... المزيد من URLs
    ] * 10  # محاكاة 300 طلب

    start = time.time()
    results = await client.fetch_many(urls, concurrency=20)
    elapsed = time.time() - start

    # إحصائيات النتائج
    success = sum(1 for r in results if r.get('status') == 200)
    failed = len(results) - success

    print(f"إجمالي الطلبات: {len(results)}")
    print(f"ناجح: {success}, فاشل: {failed}")
    print(f"الوقت المستغرق: {elapsed:.2f}s")
    print(f"QPS: {len(results)/elapsed:.1f}")

    await client.close()

if __name__ == "__main__":
    asyncio.run(main())

5.2 قالب Go للخدمة عالية التزامن

go
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"runtime"
	"time"

	"golang.org/x/sync/errgroup"
)

// هياكل Request/Response
type OrderRequest struct {
	UserID    int64   `json:"user_id"`
	ProductID int64   `json:"product_id"`
	Quantity  int     `json:"quantity"`
	Price     float64 `json:"price"`
}

type OrderResponse struct {
	OrderID   int64   `json:"order_id"`
	Status    string  `json:"status"`
	Total     float64 `json:"total"`
	CreatedAt string  `json:"created_at"`
}

// محاكاة عمليات قاعدة البيانات
type Database struct {
	orders map[int64]*OrderResponse
	mutex  chan struct{}
}

func NewDatabase() *Database {
	db := &Database{
		orders: make(map[int64]*OrderResponse),
		mutex:  make(chan struct{}, 1), // محاكاة قفل الاستبعاد المتبادل
	}
	return db
}

func (db *Database) CreateOrder(ctx context.Context, req *OrderRequest) (*OrderResponse, error) {
	// الحصول على القفل
	select {
	case db.mutex <- struct{}{}:
		defer func() { <-db.mutex }()
	case <-ctx.Done():
		return nil, ctx.Err()
	}

	// محاكاة تأخير عمليات قاعدة البيانات
	select {
	case <-time.After(50 * time.Millisecond):
	case <-ctx.Done():
		return nil, ctx.Err()
	}

	order := &OrderResponse{
		OrderID:   time.Now().UnixNano(),
		Status:    "created",
		Total:     req.Price * float64(req.Quantity),
		CreatedAt: time.Now().Format(time.RFC3339),
	}
	db.orders[order.OrderID] = order
	return order, nil
}

// معالج HTTP
type Handler struct {
	db *Database
}

func NewHandler(db *Database) *Handler {
	return &Handler{db: db}
}

func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
	// تعيين مهلة الطلب
	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
	defer cancel()

	var req OrderRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	order, err := h.db.CreateOrder(ctx, &req)
	if err != nil {
		if err == context.DeadlineExceeded {
			http.Error(w, "Request timeout", http.StatusGatewayTimeout)
			return
		}
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(order)
}

func (h *Handler) Health(w http.ResponseWriter, r *http.Request) {
	info := map[string]interface{}{
		"status":    "ok",
		"goroutine": runtime.NumGoroutine(),
		"cpu":       runtime.NumCPU(),
		"version":   runtime.Version(),
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(info)
}

// مثال على المعالجة الدفعية
func BatchProcess(ctx context.Context, items []int) ([]int, error) {
	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(10) // تقييد التزامن إلى 10

	results := make([]int, len(items))

	for i, item := range items {
		i, item := i, item // تجنب مصيدة الإغلاق (Closure Trap)
		g.Go(func() error {
			select {
			case <-ctx.Done():
				return ctx.Err()
			default:
				// محاكاة المعالجة
				time.Sleep(100 * time.Millisecond)
				results[i] = item * 2
				return nil
			}
		})
	}

	if err := g.Wait(); err != nil {
		return nil, err
	}
	return results, nil
}

func main() {
	// تهيئة قاعدة البيانات
	db := NewDatabase()

	// إنشاء المعالج
	handler := NewHandler(db)

	// إعداد المسارات
	mux := http.NewServeMux()
	mux.HandleFunc("/order", handler.CreateOrder)
	mux.HandleFunc("/health", handler.Health)

	// إنشاء الخادم
	server := &http.Server{
		Addr:         ":8080",
		Handler:      mux,
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  120 * time.Second,
	}

	fmt.Println("Server starting on :8080")
	fmt.Printf("Go version: %s\n", runtime.Version())
	fmt.Printf("CPU cores: %d\n", runtime.NumCPU())

	if err := server.ListenAndServe(); err != nil {
		log.Fatal(err)
	}
}

6. جدول ملخص المقارنة

6.1 مقارنة المفاهيم الأساسية

الخاصيةProcessThreadCoroutine
الجدولة بواسطةنظام التشغيلنظام التشغيلبرنامج المستخدم/وقت التشغيل
تكلفة التبديل~1-10ms~1-10μs~100ns
استهلاك الذاكرة~10MB+~1MB~2KB
طريقة الاتصالIPCذاكرة مشتركةذاكرة مشتركة/Channel
متطلبات المزامنةغير مطلوبةتحتاج أقفالتحتاج أقفال/تعاونية
تأثير الانهيارالعملية فقطالعملية بأكملهايمكن التحكم به
السيناريو المناسبعزل قوي، تعدد المستأجرينمكثف CPUمكثف I/O
اللغات النموذجيةجميع اللغاتجميع اللغاتGo، Python، JS، Rust

6.2 دليل اختيار نموذج التزامن

السيناريوالنموذج الموصى بهالسبب
بوابة خدمة الويبكوروتين + I/O غير متزامناتصالات متزامنة عالية، استهلاك ذاكرة منخفض
خدمة الاتصال الفوريكوروتين + اتصالات طويلةالحفاظ على عدد كبير من اتصالات WebSocket
خط أنابيب معالجة البياناتعمليات متعددة + كوروتينالاستفادة من النوى المتعددة، I/O لا يحظر
الحوسبة العلميةخيوط متعددة/عمليات متعددةمكثف CPU، يحتاج حوسبة متوازية
معمارية الخدمات المصغرةعمليات متعددة + كوروتينعزل بين الخدمات، تزامن عالي داخلي
الأنظمة المضمنةكوروتين/خيط واحدموارد محدودة، جدولة حتمية

6.3 جدول المصطلحات

المصطلح الإنجليزيالمقابل العربيالشرح
Processالعمليةالوحدة الأساسية لتخصيص موارد نظام التشغيل، لها مساحة ذاكرة مستقلة
Threadالخيطالوحدة الأساسية لجدولة CPU، يشارك مساحة ذاكرة العملية
Coroutineالكوروتينخيط خفيف الوزن في وضع المستخدم، يتم جدولته بواسطة البرنامج
Concurrencyالتزامنمهام متعددة تتنفذ بالتناوب، تتقدم معًا على المستوى الكلي
Parallelismالتوازيمهام متعددة تتنفذ حقًا في نفس الوقت، تحتاج دعم النوى المتعددة
Context Switchتبديل السياقعملية تبديل CPU من مهمة إلى أخرى
Blocking I/OI/O حاظربعد بدء طلب I/O، الانتظار حتى الاكتمال، مع تعليق الخيط خلالها
Non-blocking I/OI/O غير حاظربعد بدء طلب I/O، العودة فورًا دون انتظار النتيجة
Async I/OI/O غير متزامنعند اكتمال I/O، إشعار المستدعي من خلال رد نداء أو آلية إشعار
Event Loopحلقة الأحداثآلية جدولة الكوروتين، تراقب الأحداث باستمرار وتوزعها للمعالجة
GoroutineGo كوروتينتنفيذ الخيوط خفيفة الوزن في لغة Go
Channelالقناةآلية الاتصال بين الكوروتينات في لغة Go
Mutexقفل الاستبعاد المتبادلبدائي تزامن يستخدم لحماية الموارد المشتركة
Semaphoreالإشارةالتحكم في عدد الخيوط التي تصل إلى مورد ما في نفس الوقت
Deadlockالجمودعدة خيوط تنتظر بعضها البعض لتحرير الموارد، مما يؤدي إلى حظر دائم
Race Conditionحالة التسابقعدة خيوط تصل إلى بيانات مشتركة في نفس الوقت، مما يؤدي إلى نتائج غير محددة
Thread Poolتجمع الخيوطإنشاء مجموعة من الخيوط مسبقًا، وإعادة استخدامها لتقليل تكلفة الإنشاء والتدمير
Work Stealingسرقة العملخيط خامل "يسرق" مهام من طابور خيط مشغول لتنفيذها
Zero-copyنسخ صفرينقل البيانات بين وضع النواة ووضع المستخدم دون نسخ عبر CPU
C10K Problemمشكلة C10Kتحدي معالجة 10,000 اتصال متزامن على جهاز واحد
C10M Problemمشكلة C10Mالتحدي الأقصى لمعالجة 10 ملايين اتصال متزامن على جهاز واحد

7. خاتمة

7.1 القواعد الذهبية للبرمجة المتزامنة

  1. لا تقم بالتحسين المبكر: اجعل الكود يعمل بشكل صحيح أولاً، ثم فكر في تحسين الأداء
  2. تجنب الحالة المشتركة: "لا تتواصل من خلال مشاركة الذاكرة، بل شارك الذاكرة من خلال التواصل"
  3. اجعل الأخطاء تظهر مبكرًا: أخطاء التزامن غالبًا ما يصعب إعادة إنتاجها، يجب كشفها قدر الإمكان في مرحلة الاختبار
  4. قيد عدد التزامن: التزامن غير المحدود يعني عدم وجود حماية، استخدم الإشارات أو تجمع الاتصالات للتقييد
  5. المراقبة وقابلية الملاحظة: الأنظمة المتزامنة يجب أن يكون لديها مراقبة شاملة لتحديد المشاكل بسرعة

7.2 خريطة طريق التعلم

المرحلة 1: الفهم الأساسي
    ├── فهم المفاهيم الأساسية للـ Process/Thread
    ├── تعلم بدائيات المزامنة (الأقفال، الإشارات، متغيرات الشرط)
    └── كتابة برامج بسيطة متعددة الخيوط

المرحلة 2: تعمق في المبادئ
    ├── فهم نموذج الذاكرة والرؤية (Visibility)
    ├── تعلم البرمجة بدون أقفال والعمليات الذرية
    ├── فهم تجمع الخيوط وسرقة العمل
    └── تحليل الجمود وحالات التسابق

المرحلة 3: تطبيقات متقدمة
    ├── إتقان الكوروتين والبرمجة غير المتزامنة
    ├── تعلم نماذج التزامن في Go/Python/Rust
    ├── فهم التزامن في الأنظمة الموزعة
    └── تحسين الأداء وتخطيط السعة

المرحلة 4: المستوى الخبير
    ├── تصميم معمارية أنظمة عالية التزامن
    ├── حل أخطاء التزامن المعقدة
    ├── تطوير أطر برمجة متزامنة
    └── مشاركة ونشر معرفة التزامن

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