[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-29-yibu-rust-xinhaoling-sisuo-paizha-jifajiwang":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},"d3000000-0000-0000-0000-000000000301","a0000000-0000-0000-0000-000000000036","Deep EVM #29：异步Rust中的信号量——死锁排查和即发即弃模式","deep-evm-29-yibu-rust-xinhaoling-sisuo-paizha-jifajiwang","深入探讨tokio::sync::Semaphore用于背压控制、即发即弃写入模式、使用tracing和tokio-console的死锁诊断，以及使用RAII许可和获取超时的生产级解决方案。","## 为什么在异步Rust中使用信号量\n\n当你运行高吞吐量管线时——MEV机器人每个区块处理180,000条套利链、API服务器处理10,000个并发请求，或ETL任务写入数百万行——你不可避免地会触及资源上限。数据库连接池耗尽。RPC提供者限制你的速率。因为你生成了50,000个tokio任务导致内存膨胀。\n\n朴素的方法是无界并发：对每个工作单元使用`tokio::spawn`并希望运行时自行解决。它不会。在生产中，我们观察到270万个已生成任务导致15.4 GB的内存使用。修复方案是基于信号量背压的批量并发，将内存降至0.8 GB。\n\n信号量是在不完全串行化操作的情况下限制并发数量的正确原语。与互斥锁（只允许一个）不同，信号量允许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关键API：\n\n- `Semaphore::new(permits)` — 创建N个许可\n- `acquire(&self)` — 异步等待许可可用，返回`SemaphorePermit`（RAII守卫）\n- `try_acquire(&self)` — 非异步，无许可时立即返回`Err`\n- `acquire_owned(self: Arc\u003CSelf>)` — 返回拥有`Arc`的`OwnedSemaphorePermit`\n- `add_permits(&self, n)` — 动态增加许可数\n- `close(&self)` — 关闭信号量\n\n关键设计选择：`acquire()`返回RAII守卫。当守卫被丢弃时，许可被释放。这意味着即使在panic、提前返回和`?`操作符中断时也能自动清理。\n\n## 即发即弃写入模式\n\n在高吞吐量系统中，你经常想在不阻塞热路径的情况下持久化数据。模式：生成一个后台任务，获取信号量许可，执行写入，然后释放。调用者不等待结果。\n\n```rust\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    \u002F\u002F\u002F 即发即弃：不阻塞调用者地持久化链利润。\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!(\"semaphore closed, dropping write\");\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                    \"fire-and-forget write failed\"\n                );\n            }\n        });\n    }\n}\n```\n\n## 死锁场景\n\n信号量死锁很隐蔽，因为它们不会导致panic或错误——程序只是停止进展。\n\n### 场景1：提前返回时许可未释放\n\nRAII守卫保护你免受大多数提前返回的影响。危险在于将许可移入一个超出预期生命周期的结构体中。\n\n### 场景2：嵌套获取（自死锁）\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 获取N个许可中的1个\n    let result = simulate(chain).await?;\n    \u002F\u002F 错误：在持有许可的情况下获取同一信号量\n    let _inner = sem.acquire().await?; \u002F\u002F 如果N=1，死锁\n    persist(result).await?;\n    Ok(())\n}\n```\n\n修复：为不同关注点使用独立的信号量。\n\n### 场景3：在select中跨await点持有许可\n\n`select!`宏通过丢弃future来取消失败分支，但在该future内部生成的任务继续运行。\n\n## 诊断信号量死锁\n\n### 基于tracing的诊断\n\n使用结构化日志检测acquire\u002Frelease：\n\n```rust\nlet permits_before = semaphore.available_permits();\ntracing::debug!(\n    available = permits_before,\n    \"acquiring semaphore permit\"\n);\nlet _permit = semaphore.acquire().await?;\ntracing::debug!(\n    available = semaphore.available_permits(),\n    \"acquired semaphore permit\"\n);\n```\n\n### Prometheus指标\n\n暴露可用许可的gauge指标。降至零并保持不变就是死锁。\n\n### tokio-console\n\n`tokio-console`是一个诊断工具，可以附加到运行中的tokio应用程序并实时显示任务状态。\n\n## 生产级解决方案\n\n### 解决方案1：始终使用OwnedSemaphorePermit\n\n生成任务时，优先使用`acquire_owned()`而非`acquire()`。\n\n### 解决方案2：带超时的获取\n\n在生产中永远不要无限期等待许可：\n\n```rust\nuse tokio::time::{timeout, Duration};\n\nlet permit = timeout(\n    Duration::from_secs(30),\n    semaphore.acquire(),\n).await\n    .map_err(|_| anyhow!(\"semaphore acquire timed out — possible deadlock\"))?\n    .map_err(|_| anyhow!(\"semaphore closed\"))?;\n```\n\n### 解决方案3：使用JoinSet的结构化并发\n\n使用`tokio::task::JoinSet`代替无界的`tokio::spawn`：\n\n```rust\nuse tokio::task::JoinSet;\n\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\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, \"batch failed\"),\n        Err(e) => tracing::error!(error = %e, \"task panicked\"),\n    }\n}\n```\n\n### 解决方案4：为不同关注点使用独立信号量\n\n```rust\npub struct ConcurrencyLimits {\n    pub simulation: Arc\u003CSemaphore>,  \u002F\u002F 节点并行模拟\n    pub db_writes: Arc\u003CSemaphore>,   \u002F\u002F 数据库写入\n    pub mempool: Arc\u003CSemaphore>,     \u002F\u002F 内存池处理\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## 性能：信号量开销\n\n`tokio::sync::Semaphore`使用原子计数器和侵入式等待者链表实现。获取无竞争的许可是单个`fetch_sub`——纳秒级。即使在竞争下，开销与实际I\u002FO相比也可忽略不计。\n\n| 操作 | 无信号量 | 有信号量（20许可） |\n|------|----------|--------------------|\n| 10,000次DB写入 | 1,340ms（池耗尽错误） | 1,580ms（零错误） |\n| 500次RPC模拟 | 8,200ms（节点过载） | 9,100ms（零超时） |\n| 内存（270万任务） | 15.4 GB | 0.8 GB |\n\n10-15%的墙钟开销与消除错误、超时和OOM崩溃相比微不足道。\n\n## 总结\n\n异步Rust中的信号量看似简单——获取、工作、丢弃许可。复杂性在生产中显现：许可泄漏到长生命周期结构体、跨调用栈的嵌套获取、跨`select!`取消边界持有的许可。\n\n防御性方案：\n\n1. **始终**生成任务时使用`acquire_owned()`\n2. **始终**用超时包装acquire\n3. **绝不**在同一信号量上嵌套获取\n4. **分离**不同资源池使用不同信号量\n5. **检测**使用指标和追踪监控可用许可\n6. **使用JoinSet**代替无界`tokio::spawn`维护结构化并发","\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>并希望运行时自行解决。它不会。在生产中，我们观察到270万个已生成任务导致15.4 GB的内存使用。修复方案是基于信号量背压的批量并发，将内存降至0.8 GB。\u003C\u002Fp>\n\u003Cp>信号量是在不完全串行化操作的情况下限制并发数量的正确原语。与互斥锁（只允许一个）不同，信号量允许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>关键API：\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>Arc\u003C\u002Fcode>的\u003Ccode>OwnedSemaphorePermit\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\u003C\u002Ful>\n\u003Cp>关键设计选择：\u003Ccode>acquire()\u003C\u002Fcode>返回RAII守卫。当守卫被丢弃时，许可被释放。这意味着即使在panic、提前返回和\u003Ccode>?\u003C\u002Fcode>操作符中断时也能自动清理。\u003C\u002Fp>\n\u003Ch2 id=\"\">即发即弃写入模式\u003C\u002Fh2>\n\u003Cp>在高吞吐量系统中，你经常想在不阻塞热路径的情况下持久化数据。模式：生成一个后台任务，获取信号量许可，执行写入，然后释放。调用者不等待结果。\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">pub 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    \u002F\u002F\u002F 即发即弃：不阻塞调用者地持久化链利润。\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!(\"semaphore closed, dropping write\");\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                    \"fire-and-forget write failed\"\n                );\n            }\n        });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">死锁场景\u003C\u002Fh2>\n\u003Cp>信号量死锁很隐蔽，因为它们不会导致panic或错误——程序只是停止进展。\u003C\u002Fp>\n\u003Ch3>场景1：提前返回时许可未释放\u003C\u002Fh3>\n\u003Cp>RAII守卫保护你免受大多数提前返回的影响。危险在于将许可移入一个超出预期生命周期的结构体中。\u003C\u002Fp>\n\u003Ch3>场景2：嵌套获取（自死锁）\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 获取N个许可中的1个\n    let result = simulate(chain).await?;\n    \u002F\u002F 错误：在持有许可的情况下获取同一信号量\n    let _inner = sem.acquire().await?; \u002F\u002F 如果N=1，死锁\n    persist(result).await?;\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>修复：为不同关注点使用独立的信号量。\u003C\u002Fp>\n\u003Ch3>场景3：在select中跨await点持有许可\u003C\u002Fh3>\n\u003Cp>\u003Ccode>select!\u003C\u002Fcode>宏通过丢弃future来取消失败分支，但在该future内部生成的任务继续运行。\u003C\u002Fp>\n\u003Ch2 id=\"\">诊断信号量死锁\u003C\u002Fh2>\n\u003Ch3>基于tracing的诊断\u003C\u002Fh3>\n\u003Cp>使用结构化日志检测acquire\u002Frelease：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">let permits_before = semaphore.available_permits();\ntracing::debug!(\n    available = permits_before,\n    \"acquiring semaphore permit\"\n);\nlet _permit = semaphore.acquire().await?;\ntracing::debug!(\n    available = semaphore.available_permits(),\n    \"acquired semaphore permit\"\n);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Prometheus指标\u003C\u002Fh3>\n\u003Cp>暴露可用许可的gauge指标。降至零并保持不变就是死锁。\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：始终使用OwnedSemaphorePermit\u003C\u002Fh3>\n\u003Cp>生成任务时，优先使用\u003Ccode>acquire_owned()\u003C\u002Fcode>而非\u003Ccode>acquire()\u003C\u002Fcode>。\u003C\u002Fp>\n\u003Ch3>解决方案2：带超时的获取\u003C\u002Fh3>\n\u003Cp>在生产中永远不要无限期等待许可：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::time::{timeout, Duration};\n\nlet permit = timeout(\n    Duration::from_secs(30),\n    semaphore.acquire(),\n).await\n    .map_err(|_| anyhow!(\"semaphore acquire timed out — possible deadlock\"))?\n    .map_err(|_| anyhow!(\"semaphore closed\"))?;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>解决方案3：使用JoinSet的结构化并发\u003C\u002Fh3>\n\u003Cp>使用\u003Ccode>tokio::task::JoinSet\u003C\u002Fcode>代替无界的\u003Ccode>tokio::spawn\u003C\u002Fcode>：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::task::JoinSet;\n\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\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, \"batch failed\"),\n        Err(e) =&gt; tracing::error!(error = %e, \"task panicked\"),\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;,  \u002F\u002F 节点并行模拟\n    pub db_writes: Arc&lt;Semaphore&gt;,   \u002F\u002F 数据库写入\n    pub mempool: Arc&lt;Semaphore&gt;,     \u002F\u002F 内存池处理\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\u003Ch2 id=\"\">性能：信号量开销\u003C\u002Fh2>\n\u003Cp>\u003Ccode>tokio::sync::Semaphore\u003C\u002Fcode>使用原子计数器和侵入式等待者链表实现。获取无竞争的许可是单个\u003Ccode>fetch_sub\u003C\u002Fcode>——纳秒级。即使在竞争下，开销与实际I\u002FO相比也可忽略不计。\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>内存（270万任务）\u003C\u002Ftd>\u003Ctd>15.4 GB\u003C\u002Ftd>\u003Ctd>0.8 GB\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>10-15%的墙钟开销与消除错误、超时和OOM崩溃相比微不足道。\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>用超时包装acquire\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","zh","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:26.481969Z","异步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-000000000668","为什么Bali在2026年正在成为东南亚的影响力科技中心","weishenme-bali-2026-zhengzai-chengwei-dongnanya-yingxiangli-keji-zhongxin","Bali在东南亚创业生态系统中排名第16位。随着Web3构建者、AI可持续发展初创公司和生态旅游科技公司的集中，该岛正在打造区域影响力科技之都的独特定位。","2026-03-28T10:44:48.898750Z",{"id":40,"title":41,"slug":42,"excerpt":43,"locale":12,"category_name":31,"published_at":44},"d0000000-0000-0000-0000-000000000667","ASEAN数据保护拼图：开发者合规清单","asean-shuju-baohu-pintu-kaifazhe-heguiqingdan","七个ASEAN国家现已拥有全面的数据保护法律，各自具有不同的同意模型、本地化要求和处罚结构。这是一份为构建多国应用程序的开发者准备的实用合规清单。","2026-03-28T10:44:48.893467Z",{"id":46,"title":47,"slug":48,"excerpt":49,"locale":12,"category_name":31,"published_at":50},"d0000000-0000-0000-0000-000000000666","Indonesia 290亿美元数字化转型：软件公司的机遇","indonesia-290yi-meiyuan-shuzihua-zhuanxing-ruanjian-gongsi-jiyu","Indonesia IT服务市场预计在2026年达到290.3亿美元，高于2025年的243.7亿美元。云基础设施、AI、电子商务和数据中心正在推动东南亚最快的增长。","2026-03-28T10:44:48.875457Z",{"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"]