[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-29-simafurat-rust-ghayr-mutazamin-jumud-atliq-wansa":3},{"article":4,"author":51},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":31,"related_articles":32},"d9000000-0000-0000-0000-000000000301","a0000000-0000-0000-0000-000000000096","Deep EVM #29: السيمافورات في Rust غير المتزامن — مطاردة الجمود وأنماط أطلق وانسَ","deep-evm-29-simafurat-rust-ghayr-mutazamin-jumud-atliq-wansa","غوص عميق في tokio::sync::Semaphore للتحكم في الضغط الخلفي، أنماط الكتابة أطلق وانسَ، تشخيص الجمود بالتتبع وtokio-console، وحلول إنتاجية مع تصاريح RAII ومهل الاستحواذ.","## لماذا السيمافورات في Rust غير المتزامن\n\nعندما تشغّل خط أنابيب عالي الإنتاجية — روبوت MEV يعالج 180,000 سلسلة مراجحة لكل كتلة، أو خادم API يتعامل مع 10,000 طلب متزامن، أو مهمة ETL تكتب ملايين الصفوف — ستصطدم حتماً بسقف الموارد. تنفد اتصالات مجمع قاعدة البيانات. يحد مزودو RPC من معدل طلباتك. تنتفخ الذاكرة لأنك أطلقت 50,000 مهمة tokio، كل منها يحمل بيانات سلسلة.\n\nالنهج الساذج هو التزامن غير المحدود: `tokio::spawn` لكل وحدة عمل وتأمل أن يتدبر وقت التشغيل الأمر. لن يفعل. في الإنتاج، لاحظنا استخدام ذاكرة 15.4 جيجابايت من 2.7 مليون مهمة مُطلقة، كل منها يحمل `Vec\u003CHop>` بالإضافة إلى سياق المحاكاة. الحل كان التزامن المُجمّع مع ضغط خلفي قائم على السيمافور، مما أنزل الذاكرة إلى 0.8 جيجابايت.\n\nالسيمافور هو البنية الأساسية المناسبة عندما تحتاج للحد من عدد العمليات المتزامنة دون تسلسلها بالكامل. على عكس المُقفل (mutex) الذي يسمح بواحد فقط بالضبط، يسمح السيمافور بـ N متزامنين. هذا يجعله مثالياً لـ:\n\n- **التزامن في كتابة قاعدة البيانات**: الحد بحجم مجمع الاتصالات (مثلاً 20 كتابة متزامنة)\n- **تحديد معدل RPC**: تحديد سقف الطلبات الصادرة لتجنب استجابات 429\n- **الضغط الخلفي للذاكرة**: منع إطلاق المهام غير المحدود بالبوابة على التصاريح المتاحة\n- **معالجة الدُفعات**: التحكم في عدد دُفعات المحاكاة التي تعمل بالتوازي\n\n## أساسيات tokio::sync::Semaphore\n\n`tokio::sync::Semaphore` هو سيمافور عدّاد مصمم للكود غير المتزامن. يحتفظ بعدّاد داخلي للتصاريح المتاحة. تستحوذ المهام على التصاريح قبل المتابعة وتحررها عند الانتهاء.\n\n```rust\nuse std::sync::Arc;\nuse tokio::sync::Semaphore;\n\n\u002F\u002F السماح بحد أقصى 20 كتابة متزامنة في قاعدة البيانات\nlet semaphore = Arc::new(Semaphore::new(20));\n\nfor chain in chains_to_persist {\n    let sem = semaphore.clone();\n    let db_pool = db_pool.clone();\n\n    tokio::spawn(async move {\n        \u002F\u002F استحواذ على تصريح — يحظر إذا كانت كل الـ 20 مستخدمة\n        let _permit = sem.acquire().await.unwrap();\n\n        \u002F\u002F تنفيذ الكتابة — التصريح محتفظ به في هذا النطاق\n        sqlx::query(\"UPDATE chains SET profit = $1 WHERE id = $2\")\n            .bind(chain.profit)\n            .bind(chain.id)\n            .execute(&db_pool)\n            .await\n            .ok();\n\n        \u002F\u002F يتم إسقاط _permit هنا — يُحرر تلقائياً\n    });\n}\n```\n\nواجهة برمجة التطبيقات الرئيسية:\n\n- `Semaphore::new(permits)` — إنشاء بـ N تصريح\n- `acquire(&self)` — غير متزامن، ينتظر حتى يتوفر تصريح، يُرجع `SemaphorePermit` (حارس RAII)\n- `try_acquire(&self)` — متزامن، يُرجع `Err` فوراً إذا لم تتوفر تصاريح\n- `acquire_owned(self: Arc\u003CSelf>)` — يُرجع `OwnedSemaphorePermit` يملك `Arc`\n- `add_permits(&self, n)` — زيادة عدد التصاريح ديناميكياً\n- `close(&self)` — إغلاق السيمافور\n- `available_permits(&self)` — فحص العدد الحالي\n\nاختيار التصميم الحاسم: `acquire()` يُرجع حارس RAII. عندما يُسقط الحارس، يُحرر التصريح. هذا يعني تنظيفاً تلقائياً حتى مع الذعر والعودة المبكرة وعامل `?`.\n\n## نمط الكتابة أطلق وانسَ\n\nفي الأنظمة عالية الإنتاجية، غالباً ما تريد حفظ البيانات بدون حظر المسار الساخن. النمط: إطلاق مهمة خلفية تستحوذ على تصريح سيمافور وتنفذ الكتابة وتحرره. المستدعي لا ينتظر النتيجة.\n\n```rust\nuse std::sync::Arc;\nuse tokio::sync::Semaphore;\n\npub struct AsyncChainStore\u003CS: ChainStore> {\n    inner: Arc\u003CS>,\n    semaphore: Arc\u003CSemaphore>,\n}\n\nimpl\u003CS: ChainStore + Send + Sync + 'static> AsyncChainStore\u003CS> {\n    pub fn new(inner: Arc\u003CS>, max_concurrent_writes: usize) -> Self {\n        Self {\n            inner,\n            semaphore: Arc::new(Semaphore::new(max_concurrent_writes)),\n        }\n    }\n\n    pub fn save_profits_async(&self, chains: Vec\u003CChainProfit>) {\n        let inner = self.inner.clone();\n        let sem = self.semaphore.clone();\n\n        tokio::spawn(async move {\n            let _permit = match sem.acquire().await {\n                Ok(p) => p,\n                Err(_) => {\n                    tracing::warn!(\"السيمافور مغلق، تجاهل الكتابة\");\n                    return;\n                }\n            };\n\n            if let Err(e) = inner.batch_update_profits(&chains).await {\n                tracing::error!(\n                    error = %e,\n                    count = chains.len(),\n                    \"فشلت كتابة أطلق وانسَ\"\n                );\n            }\n        });\n    }\n}\n```\n\nنمط المُزخرف هذا يبقي `ChainStore` الداخلي نقياً — لا يعرف شيئاً عن التحكم في التزامن. المُزخرف يتعامل مع إدارة السيمافور وتسجيل الأخطاء وإطلاق المهام.\n\n## سيناريوهات الجمود\n\nجمود السيمافور غدّار لأنه لا يسبب ذعراً أو خطأً — البرنامج ببساطة يتوقف عن التقدم.\n\n### السيناريو 1: استحواذ متداخل (جمود ذاتي)\n\n```rust\nasync fn simulate_and_persist(\n    sem: &Semaphore,\n    chain: &Chain,\n) -> Result\u003C()> {\n    let _outer = sem.acquire().await?; \u002F\u002F يأخذ 1 من N تصريح\n    let result = simulate(chain).await?;\n    let _inner = sem.acquire().await?; \u002F\u002F خطأ: إذا N=1، جمود!\n    persist(result).await?;\n    Ok(())\n}\n```\n\nمع `N=1` هذا جمود فوري. مع `N>1` يعمل حتى يزداد الحمل وتكون كل التصاريح محتجزة بمهام تنتظر استحواذها الداخلي.\n\n### السيناريو 2: تصريح محتفظ به عبر select!\n\n```rust\nasync fn process_with_timeout(sem: &Semaphore) -> Result\u003C()> {\n    let permit = sem.acquire().await?;\n    tokio::select! {\n        result = do_work() => { result?; }\n        _ = tokio::time::sleep(Duration::from_secs(30)) => {\n            tracing::warn!(\"انتهت المهلة\");\n        }\n    }\n    Ok(())\n}\n```\n\nماكرو `select!` يلغي الفرع الخاسر بإسقاط مستقبله، لكن أي مهام أُطلقت داخل ذلك المستقبل تستمر في العمل.\n\n## تشخيص جمود السيمافور\n\n### التشخيص القائم على التتبع\n\n```rust\nlet permits_before = semaphore.available_permits();\ntracing::debug!(available = permits_before, \"استحواذ على تصريح سيمافور\");\nlet _permit = semaphore.acquire().await?;\ntracing::debug!(available = semaphore.available_permits(), \"تم الاستحواذ\");\n```\n\nإذا أظهرت السجلات \"استحواذ\" بدون \"تم الاستحواذ\"، كل التصاريح محتجزة في مكان ما.\n\n### مقاييس Prometheus\n\n```rust\nuse metrics::gauge;\ngauge!(\"semaphore_available_permits\", semaphore.available_permits() as f64);\n```\n\nمقياس ينخفض لصفر ويبقى هناك = جمود. مقياس يتأرجح قرب الصفر = تنافس.\n\n### tokio-console\n\n`tokio-console` أداة تشخيص تتصل بتطبيق tokio قيد التشغيل وتعرض حالات المهام في الوقت الفعلي.\n\n## حلول جاهزة للإنتاج\n\n### الحل 1: استخدم دائماً acquire_owned\n\n```rust\nlet semaphore = Arc::new(Semaphore::new(20));\nfor item in items {\n    let permit = semaphore.clone().acquire_owned().await?;\n    tokio::spawn(async move {\n        do_work(item).await;\n        drop(permit);\n    });\n}\n```\n\n### الحل 2: استحواذ مع مهلة\n\n```rust\nuse tokio::time::{timeout, Duration};\nlet permit = timeout(\n    Duration::from_secs(30),\n    semaphore.acquire(),\n).await\n    .map_err(|_| anyhow!(\"انتهت مهلة استحواذ السيمافور — جمود محتمل\"))?\n    .map_err(|_| anyhow!(\"السيمافور مغلق\"))?;\n```\n\n### الحل 3: التزامن المنظم مع JoinSet\n\n```rust\nuse tokio::task::JoinSet;\nlet semaphore = Arc::new(Semaphore::new(20));\nlet mut join_set = JoinSet::new();\n\nfor batch in batches {\n    let permit = semaphore.clone().acquire_owned().await?;\n    let db_pool = db_pool.clone();\n    join_set.spawn(async move {\n        let result = process_batch(&db_pool, &batch).await;\n        drop(permit);\n        result\n    });\n}\n\nwhile let Some(result) = join_set.join_next().await {\n    match result {\n        Ok(Ok(())) => {}\n        Ok(Err(e)) => tracing::error!(error = %e, \"فشلت معالجة الدُفعة\"),\n        Err(e) => tracing::error!(error = %e, \"ذعر المهمة\"),\n    }\n}\n```\n\n### الحل 4: سيمافورات منفصلة لأغراض منفصلة\n\n```rust\npub struct ConcurrencyLimits {\n    pub simulation: Arc\u003CSemaphore>,\n    pub db_writes: Arc\u003CSemaphore>,\n    pub mempool: Arc\u003CSemaphore>,\n}\n\nimpl ConcurrencyLimits {\n    pub fn new() -> Self {\n        Self {\n            simulation: Arc::new(Semaphore::new(4)),\n            db_writes: Arc::new(Semaphore::new(20)),\n            mempool: Arc::new(Semaphore::new(100)),\n        }\n    }\n}\n```\n\nهذا يلغي جمود الاستحواذ المتداخل تماماً — مهمة تستحوذ على تصريح `simulation`، ثم تصريح `db_writes`، وهذه مجمعات مستقلة.\n\n## الأداء: حمل السيمافور\n\nهل السيمافور نفسه عنق زجاجة؟ عملياً، لا. `tokio::sync::Semaphore` مبني على عدّاد ذري وقائمة مرتبطة تطفلية للمنتظرين. الاستحواذ على تصريح غير متنافس هو عملية `fetch_sub` واحدة — نانوثوانٍ.\n\nقسنا حمل السيمافور في خط أنابيبنا:\n\n| العملية | بدون سيمافور | مع سيمافور (20 تصريح) |\n|---------|-------------|----------------------|\n| 10,000 كتابة DB | 1,340ms (أخطاء استنفاد المجمع) | 1,580ms (صفر أخطاء) |\n| 500 محاكاة RPC | 8,200ms (مهلات العقدة) | 9,100ms (صفر مهلات) |\n| الذاكرة (2.7M مهمة) | 15.4 جيجابايت | 0.8 جيجابايت |\n\nالحمل 10-15% في وقت الجدار مهمل مقارنة بإلغاء الأخطاء والمهلات وانهيارات نفاد الذاكرة.\n\n## الخلاصة\n\nالسيمافورات في Rust غير المتزامن بسيطة بشكل مخادع — استحوذ، اعمل، أسقط التصريح. التعقيد يظهر في الإنتاج: تصاريح مسربة في هياكل طويلة العمر، استحواذات متداخلة عبر مكدسات الاستدعاء، تصاريح محتفظ بها عبر حدود إلغاء `select!`.\n\nالدليل الدفاعي:\n\n1. **استخدم دائماً** `acquire_owned()` عند إطلاق المهام\n2. **غلّف دائماً** الاستحواذ بمهلة\n3. **لا تستحوذ أبداً** بشكل متداخل على نفس السيمافور\n4. **افصل** السيمافورات لمجمعات الموارد المنفصلة\n5. **أجهّز** التصاريح المتاحة بالمقاييس والتتبع\n6. **استخدم JoinSet** بدلاً من `tokio::spawn` غير المحدود\n\nهذه الأنماط صمدت عبر ملايين الكتل المعالجة، ومئات الآلاف من المهام المتزامنة، وصفر حالات جمود في الإنتاج منذ اعتمادها.","\u003Ch2 id=\"rust\">لماذا السيمافورات في Rust غير المتزامن\u003C\u002Fh2>\n\u003Cp>عندما تشغّل خط أنابيب عالي الإنتاجية — روبوت MEV يعالج 180,000 سلسلة مراجحة لكل كتلة، أو خادم API يتعامل مع 10,000 طلب متزامن، أو مهمة ETL تكتب ملايين الصفوف — ستصطدم حتماً بسقف الموارد. تنفد اتصالات مجمع قاعدة البيانات. يحد مزودو RPC من معدل طلباتك. تنتفخ الذاكرة لأنك أطلقت 50,000 مهمة tokio، كل منها يحمل بيانات سلسلة.\u003C\u002Fp>\n\u003Cp>النهج الساذج هو التزامن غير المحدود: \u003Ccode>tokio::spawn\u003C\u002Fcode> لكل وحدة عمل وتأمل أن يتدبر وقت التشغيل الأمر. لن يفعل. في الإنتاج، لاحظنا استخدام ذاكرة 15.4 جيجابايت من 2.7 مليون مهمة مُطلقة، كل منها يحمل \u003Ccode>Vec&lt;Hop&gt;\u003C\u002Fcode> بالإضافة إلى سياق المحاكاة. الحل كان التزامن المُجمّع مع ضغط خلفي قائم على السيمافور، مما أنزل الذاكرة إلى 0.8 جيجابايت.\u003C\u002Fp>\n\u003Cp>السيمافور هو البنية الأساسية المناسبة عندما تحتاج للحد من عدد العمليات المتزامنة دون تسلسلها بالكامل. على عكس المُقفل (mutex) الذي يسمح بواحد فقط بالضبط، يسمح السيمافور بـ N متزامنين. هذا يجعله مثالياً لـ:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>التزامن في كتابة قاعدة البيانات\u003C\u002Fstrong>: الحد بحجم مجمع الاتصالات (مثلاً 20 كتابة متزامنة)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>تحديد معدل RPC\u003C\u002Fstrong>: تحديد سقف الطلبات الصادرة لتجنب استجابات 429\u003C\u002Fli>\n\u003Cli>\u003Cstrong>الضغط الخلفي للذاكرة\u003C\u002Fstrong>: منع إطلاق المهام غير المحدود بالبوابة على التصاريح المتاحة\u003C\u002Fli>\n\u003Cli>\u003Cstrong>معالجة الدُفعات\u003C\u002Fstrong>: التحكم في عدد دُفعات المحاكاة التي تعمل بالتوازي\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"tokio-sync-semaphore\">أساسيات tokio::sync::Semaphore\u003C\u002Fh2>\n\u003Cp>\u003Ccode>tokio::sync::Semaphore\u003C\u002Fcode> هو سيمافور عدّاد مصمم للكود غير المتزامن. يحتفظ بعدّاد داخلي للتصاريح المتاحة. تستحوذ المهام على التصاريح قبل المتابعة وتحررها عند الانتهاء.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::sync::Arc;\nuse tokio::sync::Semaphore;\n\n\u002F\u002F السماح بحد أقصى 20 كتابة متزامنة في قاعدة البيانات\nlet semaphore = Arc::new(Semaphore::new(20));\n\nfor chain in chains_to_persist {\n    let sem = semaphore.clone();\n    let db_pool = db_pool.clone();\n\n    tokio::spawn(async move {\n        \u002F\u002F استحواذ على تصريح — يحظر إذا كانت كل الـ 20 مستخدمة\n        let _permit = sem.acquire().await.unwrap();\n\n        \u002F\u002F تنفيذ الكتابة — التصريح محتفظ به في هذا النطاق\n        sqlx::query(\"UPDATE chains SET profit = $1 WHERE id = $2\")\n            .bind(chain.profit)\n            .bind(chain.id)\n            .execute(&amp;db_pool)\n            .await\n            .ok();\n\n        \u002F\u002F يتم إسقاط _permit هنا — يُحرر تلقائياً\n    });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>واجهة برمجة التطبيقات الرئيسية:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>Semaphore::new(permits)\u003C\u002Fcode> — إنشاء بـ N تصريح\u003C\u002Fli>\n\u003Cli>\u003Ccode>acquire(&amp;self)\u003C\u002Fcode> — غير متزامن، ينتظر حتى يتوفر تصريح، يُرجع \u003Ccode>SemaphorePermit\u003C\u002Fcode> (حارس RAII)\u003C\u002Fli>\n\u003Cli>\u003Ccode>try_acquire(&amp;self)\u003C\u002Fcode> — متزامن، يُرجع \u003Ccode>Err\u003C\u002Fcode> فوراً إذا لم تتوفر تصاريح\u003C\u002Fli>\n\u003Cli>\u003Ccode>acquire_owned(self: Arc&lt;Self&gt;)\u003C\u002Fcode> — يُرجع \u003Ccode>OwnedSemaphorePermit\u003C\u002Fcode> يملك \u003Ccode>Arc\u003C\u002Fcode>\u003C\u002Fli>\n\u003Cli>\u003Ccode>add_permits(&amp;self, n)\u003C\u002Fcode> — زيادة عدد التصاريح ديناميكياً\u003C\u002Fli>\n\u003Cli>\u003Ccode>close(&amp;self)\u003C\u002Fcode> — إغلاق السيمافور\u003C\u002Fli>\n\u003Cli>\u003Ccode>available_permits(&amp;self)\u003C\u002Fcode> — فحص العدد الحالي\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>اختيار التصميم الحاسم: \u003Ccode>acquire()\u003C\u002Fcode> يُرجع حارس RAII. عندما يُسقط الحارس، يُحرر التصريح. هذا يعني تنظيفاً تلقائياً حتى مع الذعر والعودة المبكرة وعامل \u003Ccode>?\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"\">نمط الكتابة أطلق وانسَ\u003C\u002Fh2>\n\u003Cp>في الأنظمة عالية الإنتاجية، غالباً ما تريد حفظ البيانات بدون حظر المسار الساخن. النمط: إطلاق مهمة خلفية تستحوذ على تصريح سيمافور وتنفذ الكتابة وتحرره. المستدعي لا ينتظر النتيجة.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::sync::Arc;\nuse tokio::sync::Semaphore;\n\npub struct AsyncChainStore&lt;S: ChainStore&gt; {\n    inner: Arc&lt;S&gt;,\n    semaphore: Arc&lt;Semaphore&gt;,\n}\n\nimpl&lt;S: ChainStore + Send + Sync + 'static&gt; AsyncChainStore&lt;S&gt; {\n    pub fn new(inner: Arc&lt;S&gt;, max_concurrent_writes: usize) -&gt; Self {\n        Self {\n            inner,\n            semaphore: Arc::new(Semaphore::new(max_concurrent_writes)),\n        }\n    }\n\n    pub fn save_profits_async(&amp;self, chains: Vec&lt;ChainProfit&gt;) {\n        let inner = self.inner.clone();\n        let sem = self.semaphore.clone();\n\n        tokio::spawn(async move {\n            let _permit = match sem.acquire().await {\n                Ok(p) =&gt; p,\n                Err(_) =&gt; {\n                    tracing::warn!(\"السيمافور مغلق، تجاهل الكتابة\");\n                    return;\n                }\n            };\n\n            if let Err(e) = inner.batch_update_profits(&amp;chains).await {\n                tracing::error!(\n                    error = %e,\n                    count = chains.len(),\n                    \"فشلت كتابة أطلق وانسَ\"\n                );\n            }\n        });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>نمط المُزخرف هذا يبقي \u003Ccode>ChainStore\u003C\u002Fcode> الداخلي نقياً — لا يعرف شيئاً عن التحكم في التزامن. المُزخرف يتعامل مع إدارة السيمافور وتسجيل الأخطاء وإطلاق المهام.\u003C\u002Fp>\n\u003Ch2 id=\"\">سيناريوهات الجمود\u003C\u002Fh2>\n\u003Cp>جمود السيمافور غدّار لأنه لا يسبب ذعراً أو خطأً — البرنامج ببساطة يتوقف عن التقدم.\u003C\u002Fp>\n\u003Ch3>السيناريو 1: استحواذ متداخل (جمود ذاتي)\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn simulate_and_persist(\n    sem: &amp;Semaphore,\n    chain: &amp;Chain,\n) -&gt; Result&lt;()&gt; {\n    let _outer = sem.acquire().await?; \u002F\u002F يأخذ 1 من N تصريح\n    let result = simulate(chain).await?;\n    let _inner = sem.acquire().await?; \u002F\u002F خطأ: إذا N=1، جمود!\n    persist(result).await?;\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>مع \u003Ccode>N=1\u003C\u002Fcode> هذا جمود فوري. مع \u003Ccode>N&gt;1\u003C\u002Fcode> يعمل حتى يزداد الحمل وتكون كل التصاريح محتجزة بمهام تنتظر استحواذها الداخلي.\u003C\u002Fp>\n\u003Ch3>السيناريو 2: تصريح محتفظ به عبر select!\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn process_with_timeout(sem: &amp;Semaphore) -&gt; Result&lt;()&gt; {\n    let permit = sem.acquire().await?;\n    tokio::select! {\n        result = do_work() =&gt; { result?; }\n        _ = tokio::time::sleep(Duration::from_secs(30)) =&gt; {\n            tracing::warn!(\"انتهت المهلة\");\n        }\n    }\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>ماكرو \u003Ccode>select!\u003C\u002Fcode> يلغي الفرع الخاسر بإسقاط مستقبله، لكن أي مهام أُطلقت داخل ذلك المستقبل تستمر في العمل.\u003C\u002Fp>\n\u003Ch2 id=\"\">تشخيص جمود السيمافور\u003C\u002Fh2>\n\u003Ch3>التشخيص القائم على التتبع\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">let permits_before = semaphore.available_permits();\ntracing::debug!(available = permits_before, \"استحواذ على تصريح سيمافور\");\nlet _permit = semaphore.acquire().await?;\ntracing::debug!(available = semaphore.available_permits(), \"تم الاستحواذ\");\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>إذا أظهرت السجلات “استحواذ” بدون “تم الاستحواذ”، كل التصاريح محتجزة في مكان ما.\u003C\u002Fp>\n\u003Ch3>مقاييس Prometheus\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">use metrics::gauge;\ngauge!(\"semaphore_available_permits\", semaphore.available_permits() as f64);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>مقياس ينخفض لصفر ويبقى هناك = جمود. مقياس يتأرجح قرب الصفر = تنافس.\u003C\u002Fp>\n\u003Ch3>tokio-console\u003C\u002Fh3>\n\u003Cp>\u003Ccode>tokio-console\u003C\u002Fcode> أداة تشخيص تتصل بتطبيق tokio قيد التشغيل وتعرض حالات المهام في الوقت الفعلي.\u003C\u002Fp>\n\u003Ch2 id=\"\">حلول جاهزة للإنتاج\u003C\u002Fh2>\n\u003Ch3>الحل 1: استخدم دائماً acquire_owned\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">let semaphore = Arc::new(Semaphore::new(20));\nfor item in items {\n    let permit = semaphore.clone().acquire_owned().await?;\n    tokio::spawn(async move {\n        do_work(item).await;\n        drop(permit);\n    });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>الحل 2: استحواذ مع مهلة\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::time::{timeout, Duration};\nlet permit = timeout(\n    Duration::from_secs(30),\n    semaphore.acquire(),\n).await\n    .map_err(|_| anyhow!(\"انتهت مهلة استحواذ السيمافور — جمود محتمل\"))?\n    .map_err(|_| anyhow!(\"السيمافور مغلق\"))?;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>الحل 3: التزامن المنظم مع JoinSet\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::task::JoinSet;\nlet semaphore = Arc::new(Semaphore::new(20));\nlet mut join_set = JoinSet::new();\n\nfor batch in batches {\n    let permit = semaphore.clone().acquire_owned().await?;\n    let db_pool = db_pool.clone();\n    join_set.spawn(async move {\n        let result = process_batch(&amp;db_pool, &amp;batch).await;\n        drop(permit);\n        result\n    });\n}\n\nwhile let Some(result) = join_set.join_next().await {\n    match result {\n        Ok(Ok(())) =&gt; {}\n        Ok(Err(e)) =&gt; tracing::error!(error = %e, \"فشلت معالجة الدُفعة\"),\n        Err(e) =&gt; tracing::error!(error = %e, \"ذعر المهمة\"),\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>الحل 4: سيمافورات منفصلة لأغراض منفصلة\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub struct ConcurrencyLimits {\n    pub simulation: Arc&lt;Semaphore&gt;,\n    pub db_writes: Arc&lt;Semaphore&gt;,\n    pub mempool: Arc&lt;Semaphore&gt;,\n}\n\nimpl ConcurrencyLimits {\n    pub fn new() -&gt; Self {\n        Self {\n            simulation: Arc::new(Semaphore::new(4)),\n            db_writes: Arc::new(Semaphore::new(20)),\n            mempool: Arc::new(Semaphore::new(100)),\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>هذا يلغي جمود الاستحواذ المتداخل تماماً — مهمة تستحوذ على تصريح \u003Ccode>simulation\u003C\u002Fcode>، ثم تصريح \u003Ccode>db_writes\u003C\u002Fcode>، وهذه مجمعات مستقلة.\u003C\u002Fp>\n\u003Ch2 id=\"\">الأداء: حمل السيمافور\u003C\u002Fh2>\n\u003Cp>هل السيمافور نفسه عنق زجاجة؟ عملياً، لا. \u003Ccode>tokio::sync::Semaphore\u003C\u002Fcode> مبني على عدّاد ذري وقائمة مرتبطة تطفلية للمنتظرين. الاستحواذ على تصريح غير متنافس هو عملية \u003Ccode>fetch_sub\u003C\u002Fcode> واحدة — نانوثوانٍ.\u003C\u002Fp>\n\u003Cp>قسنا حمل السيمافور في خط أنابيبنا:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>العملية\u003C\u002Fth>\u003Cth>بدون سيمافور\u003C\u002Fth>\u003Cth>مع سيمافور (20 تصريح)\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>10,000 كتابة DB\u003C\u002Ftd>\u003Ctd>1,340ms (أخطاء استنفاد المجمع)\u003C\u002Ftd>\u003Ctd>1,580ms (صفر أخطاء)\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>500 محاكاة RPC\u003C\u002Ftd>\u003Ctd>8,200ms (مهلات العقدة)\u003C\u002Ftd>\u003Ctd>9,100ms (صفر مهلات)\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>الذاكرة (2.7M مهمة)\u003C\u002Ftd>\u003Ctd>15.4 جيجابايت\u003C\u002Ftd>\u003Ctd>0.8 جيجابايت\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>الحمل 10-15% في وقت الجدار مهمل مقارنة بإلغاء الأخطاء والمهلات وانهيارات نفاد الذاكرة.\u003C\u002Fp>\n\u003Ch2 id=\"\">الخلاصة\u003C\u002Fh2>\n\u003Cp>السيمافورات في Rust غير المتزامن بسيطة بشكل مخادع — استحوذ، اعمل، أسقط التصريح. التعقيد يظهر في الإنتاج: تصاريح مسربة في هياكل طويلة العمر، استحواذات متداخلة عبر مكدسات الاستدعاء، تصاريح محتفظ بها عبر حدود إلغاء \u003Ccode>select!\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>الدليل الدفاعي:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>استخدم دائماً\u003C\u002Fstrong> \u003Ccode>acquire_owned()\u003C\u002Fcode> عند إطلاق المهام\u003C\u002Fli>\n\u003Cli>\u003Cstrong>غلّف دائماً\u003C\u002Fstrong> الاستحواذ بمهلة\u003C\u002Fli>\n\u003Cli>\u003Cstrong>لا تستحوذ أبداً\u003C\u002Fstrong> بشكل متداخل على نفس السيمافور\u003C\u002Fli>\n\u003Cli>\u003Cstrong>افصل\u003C\u002Fstrong> السيمافورات لمجمعات الموارد المنفصلة\u003C\u002Fli>\n\u003Cli>\u003Cstrong>أجهّز\u003C\u002Fstrong> التصاريح المتاحة بالمقاييس والتتبع\u003C\u002Fli>\n\u003Cli>\u003Cstrong>استخدم JoinSet\u003C\u002Fstrong> بدلاً من \u003Ccode>tokio::spawn\u003C\u002Fcode> غير المحدود\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>هذه الأنماط صمدت عبر ملايين الكتل المعالجة، ومئات الآلاف من المهام المتزامنة، وصفر حالات جمود في الإنتاج منذ اعتمادها.\u003C\u002Fp>\n","ar","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:33.018261Z","السيمافورات في Rust غير المتزامن — مطاردة الجمود وأنماط أطلق وانسَ","غوص عميق في tokio::sync::Semaphore للضغط الخلفي وكتابات أطلق وانسَ وتشخيص الجمود وحلول إنتاجية مع RAII والتزامن المنظم.","Rust سيمافور غير متزامن",null,"index, follow",[22,27],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","الهندسة",[33,39,45],{"id":34,"title":35,"slug":36,"excerpt":37,"locale":12,"category_name":31,"published_at":38},"d0000000-0000-0000-0000-000000000686","لماذا Bali تتحول إلى مركز تكنولوجيا التأثير في جنوب شرق آسيا 2026","limadha-bali-tatahawwal-markaz-tiknulujia-attathir-janub-sharq-asia-2026","تحتل Bali المرتبة 16 بين أنظمة الشركات الناشئة في جنوب شرق آسيا. مع تركيز متزايد لبناة Web3 وشركات AI المستدامة الناشئة وشركات تكنولوجيا السفر البيئي، تنحت الجزيرة مكانتها كعاصمة تكنولوجيا التأثير في المنطقة.","2026-03-28T10:44:50.120618Z",{"id":40,"title":41,"slug":42,"excerpt":43,"locale":12,"category_name":31,"published_at":44},"d0000000-0000-0000-0000-000000000685","فسيفساء حماية البيانات في ASEAN: قائمة امتثال للمطورين","fusayfisa-himayat-albayanat-asean-qaimat-imtithal-lilmutawwirin","تمتلك سبع دول في ASEAN الآن قوانين شاملة لحماية البيانات، لكل منها نماذج موافقة ومتطلبات توطين وهياكل عقوبات مختلفة. إليك قائمة امتثال عملية للمطورين الذين يبنون تطبيقات متعددة البلدان.","2026-03-28T10:44:50.114369Z",{"id":46,"title":47,"slug":48,"excerpt":49,"locale":12,"category_name":31,"published_at":50},"d0000000-0000-0000-0000-000000000684","التحول الرقمي في Indonesia بقيمة 29 مليار دولار: فرص لشركات البرمجيات","attahawwul-arraqami-indonesia-29-milyar-dular-furas-sharikat-albarmajiyat","من المتوقع أن يصل سوق خدمات تكنولوجيا المعلومات في Indonesia إلى 29.03 مليار دولار في 2026، ارتفاعاً من 24.37 مليار دولار في 2025. البنية التحتية السحابية والذكاء الاصطناعي والتجارة الإلكترونية ومراكز البيانات تقود أسرع نمو في جنوب شرق آسيا.","2026-03-28T10:44:50.092728Z",{"id":13,"name":52,"slug":53,"bio":54,"photo_url":19,"linkedin":19,"role":55,"created_at":56,"updated_at":56},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]