自动驾驶中间件iceoryx - 同步与通知机制(一)

本章深入讲解 iceoryx 的通知平面(Notification Plane),包括信号量、WaitSet、回调机制等同步原语的实现与使用。这些机制使得订阅者能够高效地等待数据到达,而不需要轮询。

📖 本章导读

章节概览(约2500行,建议分3次阅读)

章节 内容 难度 阅读时间 建议
5.1 通知平面概述 为什么需要通知机制 10分钟 必读
5.2.1-5.2.2 信号量基础 POSIX 信号量与 iceoryx 封装 ⭐⭐ 20分钟 必读
5.2.3 内存序快速入门 C++ 内存模型基础概念 ⭐⭐⭐ 15分钟 必读
5.3 ConditionNotifier 通知发送与接收 ⭐⭐⭐ 30分钟 必读
5.4 Subscriber 通知模式 实际应用示例 ⭐⭐ 20分钟 必读
5.5-5.7 调优与调试 性能优化和故障诊断 ⭐⭐⭐ 30分钟 推荐
附录 内存模型详解 完整技术细节(独立文档) ⭐⭐⭐⭐⭐ 2-3小时 可选深入

🎯 推荐学习路径
路径A: 快速上手(约1.5小时) - 推荐初学者

复制代码
5.1 通知平面概述
  ↓
5.2.1-5.2.2 信号量基础
  ↓
5.2.3 内存序快速入门  ← 简化版本(15分钟)
  ↓
5.3 ConditionNotifier
  ↓
5.4 Subscriber 通知模式
  ↓
5.5 性能分析(浏览)

完成后你将:
✅ 理解 iceoryx 的通知机制
✅ 掌握内存序的基本概念
✅ 能够编写事件驱动的订阅者
✅ 了解基本的性能调优方法

路径B: 深入理解(约4-5小时) - 推荐有经验的开发者

复制代码
5.1 通知平面概述
  ↓
5.2 UnnamedSemaphore(完整)
  ├─ 5.2.1-5.2.2 基础
  └─ 5.2.3 内存序快速入门
  ↓
5.3 ConditionNotifier
  ↓
5.4 Subscriber 通知模式
  ↓
5.5-5.7 调优与调试(详细)
  ↓
📚 附录A: C++ 内存模型详解  ← 深入阅读(2-3小时)
  └─ happens-before、ABA、性能优化等

完成后你将:
✅ 深入理解无锁编程原理
✅ 精通 C++ 内存序的使用
✅ 能够分析和优化性能瓶颈
✅ 具备调试复杂并发问题的能力

路径C: 按需查阅 - 推荐作为参考手册使用

复制代码
根据实际需求跳转到相关章节:

• 需要实现通知? → 5.3-5.4
• 遇到性能问题? → 5.5
• 需要理解内存序? → 5.2.3
• 跨平台适配? → 5.6
• 调试通知问题? → 5.7

5.1 通知平面概述

5.1.1 为什么需要通知机制

轮询模式的问题

cpp 复制代码
// 低效的轮询
while (running) {
    auto sample = subscriber.take();
    if (sample.has_value()) {
        process(*sample);
    } else {
        // 没有数据,但仍然消耗 CPU
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

轮询的缺点:

  • CPU 浪费:即使没有数据也在不断查询
  • 延迟不确定:取决于轮询间隔
  • 功耗高:不适合嵌入式系统

事件驱动的优势

cpp 复制代码
// 高效的事件驱动
waitSet.attachEvent(subscriber, SubscriberEvent::DATA_RECEIVED);
while (running) {
    auto events = waitSet.wait();  // 阻塞直到有事件
    for (auto& event : events) {
        auto sample = subscriber.take();
        process(*sample);
    }
}

优势:

  • 零 CPU 占用:阻塞等待,内核调度
  • 低延迟:事件立即唤醒
  • 节能:适合移动与嵌入式

5.1.2 iceoryx 的通知层次

text 复制代码
应用层 API
    ↓
WaitSet / Listener (多路复用)
    ↓
ConditionNotifier (条件通知)
    ↓
UnnamedSemaphore (底层原语)
    ↓
POSIX sem_t / Windows Event

5.2 UnnamedSemaphore 深入

5.2.1 POSIX 信号量回顾

信号量是经典的同步原语,iceoryx 使用 unnamed semaphore(内存中信号量)而非 named semaphore。

关键系统调用

c 复制代码
#include <semaphore.h>

sem_t sem;
sem_init(&sem, 1, 0);  // pshared=1 (跨进程), value=0

// 生产者
sem_post(&sem);  // 信号量 +1,唤醒等待者

// 消费者
sem_wait(&sem);  // 阻塞直到信号量 > 0,然后 -1

为什么用 unnamed 而非 named?

特性 Unnamed Semaphore Named Semaphore
位置 共享内存中 /dev/shm/sem.*
清理 自动(随共享内存) 需要 sem_unlink()
性能 更快(无文件系统) 稍慢
适用 进程间固定映射 动态进程发现

5.2.2 iceoryx 的封装

代码位置iceoryx_hoofs/posix/sync/include/iox/unnamed_semaphore.hpp

iceoryx 使用 Builder 模式 创建信号量,并通过继承 SemaphoreInterface 提供统一接口:

cpp 复制代码
class UnnamedSemaphore final : public detail::SemaphoreInterface<UnnamedSemaphore> {
public:
    using Builder = UnnamedSemaphoreBuilder;
    
    // 删除拷贝/移动(信号量不可移动)
    UnnamedSemaphore(const UnnamedSemaphore&) = delete;
    UnnamedSemaphore(UnnamedSemaphore&&) = delete;
    ~UnnamedSemaphore() noexcept;
    
private:
    friend class UnnamedSemaphoreBuilder;
    UnnamedSemaphore() = default;
    
    // 实现接口(通过 CRTP)
    expected<void, SemaphoreError> post_impl() noexcept;
    expected<void, SemaphoreError> wait_impl() noexcept;
    expected<bool, SemaphoreError> try_wait_impl() noexcept;
    expected<SemaphoreWaitState, SemaphoreError> 
        timed_wait_impl(const units::Duration& timeout) noexcept;
    
    iox_sem_t m_handle;              // POSIX 信号量句柄
    bool m_destroyHandle = true;     // 是否在析构时销毁
};

// Builder 类
class UnnamedSemaphoreBuilder {
    IOX_BUILDER_PARAMETER(uint32_t, initialValue, 0U)
    IOX_BUILDER_PARAMETER(bool, isInterProcessCapable, true)
    
public:
    expected<void, SemaphoreError> 
    create(optional<UnnamedSemaphore>& uninitializedSemaphore) const noexcept;
};

关键设计点

  1. Builder 模式:避免复杂构造函数,支持流式配置
  2. CRTP(奇异递归模板模式) :通过 SemaphoreInterface<UnnamedSemaphore> 实现接口复用
  3. optional 参数:由于信号量不可移动,需要外部提供存储位置
  4. 平台抽象iox_sem_t 在不同平台有不同实现(Linux 用 sem_t,Windows 用 HANDLE

📚 延伸阅读:设计模式详解

以下内容深入讲解 iceoryx 中使用的 Builder 模式和 CRTP 模式。这些是可选的进阶内容,如果已经熟悉这些设计模式,可以直接跳到后续章节。

设计模式详解

1. Builder 模式(建造者模式)

Builder 模式用于创建复杂对象,避免构造函数参数过多的问题。

cpp 复制代码
// ❌ 传统方式:构造函数参数过多
UnnamedSemaphore sem(
    0U,              // initialValue
    true,            // isInterProcessCapable
    "my_sem",        // name
    0644,            // permissions
    ...              // 更多参数
);

// ✅ Builder 模式:清晰、可扩展
iox::optional<iox::UnnamedSemaphore> semaphore;
iox::UnnamedSemaphoreBuilder()
    .initialValue(0U)
    .isInterProcessCapable(true)
    .create(semaphore);

iceoryx 的 Builder 实现

cpp 复制代码
// IOX_BUILDER_PARAMETER 宏生成链式调用方法
class UnnamedSemaphoreBuilder {
    IOX_BUILDER_PARAMETER(uint32_t, initialValue, 0U)
    // 展开为:
    // uint32_t m_initialValue = 0U;
    // UnnamedSemaphoreBuilder& initialValue(uint32_t value) {
    //     m_initialValue = value;
    //     return *this;  // 返回自身,支持链式调用
    // }
    
    IOX_BUILDER_PARAMETER(bool, isInterProcessCapable, true)
    // 展开为:
    // bool m_isInterProcessCapable = true;
    // UnnamedSemaphoreBuilder& isInterProcessCapable(bool value) {
    //     m_isInterProcessCapable = value;
    //     return *this;
    // }
    
public:
    expected<void, SemaphoreError> 
    create(optional<UnnamedSemaphore>& uninitializedSemaphore) const noexcept;
};

Builder 模式的优势

  • 可读性高 :参数名称明确(.initialValue(0U)0U 更清晰)
  • 灵活性:可以只设置需要的参数,其他使用默认值
  • 易扩展:添加新参数不影响现有代码
  • 编译时检查:类型安全,避免参数顺序错误

2. CRTP(Curiously Recurring Template Pattern,奇异递归模板模式)

CRTP 是一种 C++ 模板技巧,用于实现编译时多态(静态多态),避免虚函数的运行时开销。

基本原理

cpp 复制代码
// 基类接受派生类作为模板参数
template <typename Derived>
class Base {
public:
    void interface() {
        // 调用派生类的实现(编译时绑定)
        static_cast<Derived*>(this)->implementation();
    }
    
    void implementation() {
        std::cout << "Base implementation\n";
    }
};

// 派生类将自己作为模板参数传递给基类
class Derived : public Base<Derived> {
public:
    void implementation() {
        std::cout << "Derived implementation\n";
    }
};

// 使用
Derived d;
d.interface();  // 调用 Derived::implementation(),无虚函数开销

iceoryx 中的 CRTP 应用

cpp 复制代码
// SemaphoreInterface 是 CRTP 基类
template <typename SemaphoreChild>
class SemaphoreInterface {
public:
    // 公共接口:委托给派生类的 _impl 方法
    expected<void, SemaphoreError> post() noexcept {
        return static_cast<SemaphoreChild*>(this)->post_impl();
    }
    
    expected<void, SemaphoreError> wait() noexcept {
        return static_cast<SemaphoreChild*>(this)->wait_impl();
    }
    
    expected<bool, SemaphoreError> tryWait() noexcept {
        return static_cast<SemaphoreChild*>(this)->try_wait_impl();
    }
    
    expected<SemaphoreWaitState, SemaphoreError> 
    timedWait(const units::Duration& timeout) noexcept {
        return static_cast<SemaphoreChild*>(this)->timed_wait_impl(timeout);
    }
};

// UnnamedSemaphore 继承 SemaphoreInterface<UnnamedSemaphore>
class UnnamedSemaphore : public SemaphoreInterface<UnnamedSemaphore> {
private:
    friend class SemaphoreInterface<UnnamedSemaphore>;
    
    // 提供实际实现(private,通过 CRTP 基类调用)
    expected<void, SemaphoreError> post_impl() noexcept {
        return detail::sem_post(&m_handle);
    }
    
    expected<void, SemaphoreError> wait_impl() noexcept {
        return detail::sem_wait(&m_handle);
    }
    
    // ... 其他实现
};

CRTP vs 虚函数对比

特性 CRTP(静态多态) 虚函数(动态多态)
性能 无运行时开销 虚函数表查找(~5ns)
内联 可以内联 通常不能内联
内存 无虚函数表指针 每个对象额外 8 字节
灵活性 编译时确定类型 运行时多态
适用场景 性能关键、类型已知 需要运行时多态

为什么 iceoryx 使用 CRTP?

cpp 复制代码
// 假设有多种信号量实现
class NamedSemaphore : public SemaphoreInterface<NamedSemaphore> {
    expected<void, SemaphoreError> post_impl() noexcept {
        // Named semaphore 的实现
        return detail::sem_post_named(m_name.c_str());
    }
};

class WindowsSemaphore : public SemaphoreInterface<WindowsSemaphore> {
    expected<void, SemaphoreError> post_impl() noexcept {
        // Windows Event 的实现
        SetEvent(m_handle);
        return ok();
    }
};

// 使用相同的接口,但编译时确定实际类型
template <typename SemType>
void usesSemaphore(SemaphoreInterface<SemType>& sem) {
    sem.post();   // 零开销调用,编译器内联
    sem.wait();   // 无虚函数查找
}

CRTP 的优势

  • 零开销抽象:符合 C++ "不为不用的功能付出代价" 哲学
  • 编译时优化:编译器可以内联所有调用
  • 接口复用:多个信号量实现共享相同的公共接口
  • 类型安全:编译时检查,避免运行时错误

3. 为什么信号量不可移动?

cpp 复制代码
class UnnamedSemaphore {
    UnnamedSemaphore(UnnamedSemaphore&&) = delete;  // 禁止移动
};

原因

  • 信号量的 m_handle 是 POSIX sem_t 结构体,包含同步状态
  • 移动会导致等待线程持有失效的句柄引用
  • 共享内存中的信号量必须固定在特定地址

解决方案:optional 参数

cpp 复制代码
// 外部提供存储位置
iox::optional<iox::UnnamedSemaphore> semaphore;

// Builder 在指定位置构造对象
UnnamedSemaphoreBuilder().create(semaphore);

// semaphore 保持在固定内存位置

实现要点unnamed_semaphore.cpp

cpp 复制代码
// 1. 创建信号量(Builder 模式)
expected<void, SemaphoreError>
UnnamedSemaphoreBuilder::create(optional<UnnamedSemaphore>& uninitializedSemaphore) const noexcept {
    // 检查初始值是否超过系统限制
    if (m_initialValue > IOX_SEM_VALUE_MAX) {
        IOX_LOG(Error, "Initial value exceeds maximum: " << IOX_SEM_VALUE_MAX);
        return err(SemaphoreError::SEMAPHORE_OVERFLOW);
    }
    
    uninitializedSemaphore.emplace();
    
    // 使用 POSIX_CALL 包装器处理错误
    auto result = IOX_POSIX_CALL(iox_sem_init)(
        &uninitializedSemaphore.value().m_handle,
        (m_isInterProcessCapable) ? 1 : 0,  // pshared 参数
        static_cast<unsigned int>(m_initialValue)
    ).failureReturnValue(-1).evaluate();
    
    if (result.has_error()) {
        uninitializedSemaphore.value().m_destroyHandle = false;
        uninitializedSemaphore.reset();
        // ... 错误处理
    }
    
    return ok();
}

// 2. 等待实现(委托给 helper 函数)
expected<void, SemaphoreError> UnnamedSemaphore::wait_impl() noexcept {
    return detail::sem_wait(&m_handle);
}

// 3. Helper 函数处理 EINTR(在 semaphore_helper.cpp 中)
expected<void, SemaphoreError> detail::sem_wait(iox_sem_t* handle) noexcept {
    auto result = IOX_POSIX_CALL(iox_sem_wait)(handle)
                      .failureReturnValue(-1)
                      .evaluate();
    
    if (result.has_error()) {
        return err(sem_errno_to_enum(result.error().errnum));
    }
    
    return ok<void>();
}

IOX_POSIX_CALL 宏的作用

这个宏封装了 POSIX 调用的常见模式:

  • 自动处理 EINTR(信号中断时重试)
  • 统一错误处理
  • 日志记录
cpp 复制代码
// 宏展开后的等价代码
while (true) {
    int result = iox_sem_wait(handle);
    if (result == 0) {
        return ok();  // 成功
    }
    if (errno == EINTR) {
        continue;  // 被信号中断,重试
    }
    return err(sem_errno_to_enum(errno));  // 其他错误
}

使用示例

cpp 复制代码
#include "iox/unnamed_semaphore.hpp"

// 1. 使用 Builder 创建信号量
iox::optional<iox::UnnamedSemaphore> semaphore;
auto result = iox::UnnamedSemaphoreBuilder()
    .initialValue(0U)                    // 初始值为 0
    .isInterProcessCapable(true)         // 支持跨进程
    .create(semaphore);

if (result.has_error()) {
    // 处理错误
    return;
}

// 2. 使用信号量(通过 SemaphoreInterface 提供的接口)
semaphore->post();       // 信号量 +1
semaphore->wait();       // 阻塞等待(信号量 -1)
auto success = semaphore->tryWait();  // 非阻塞尝试

// 3. 带超时等待
auto waitResult = semaphore->timedWait(iox::units::Duration::fromSeconds(5));
if (waitResult.has_value() && waitResult.value() == iox::SemaphoreWaitState::NO_TIMEOUT) {
    // 在超时前获得信号
}

📝 关于文档简化

本节开头展示的是简化版接口,用于教学目的。实际 iceoryx 实现采用了更工程化的设计:

  • 使用 Builder 模式 替代直接构造函数
  • 采用 CRTP 实现接口复用(SemaphoreInterface
  • 通过 IOX_POSIX_CALL 宏封装错误处理
  • 支持 跨平台抽象iox_sem_t 在不同 OS 有不同实现)

这些设计提高了代码的可维护性和跨平台兼容性,但核心语义与简化版本相同。


⚠️ 章节导航提示

接下来的 5.2.3 节(约1100行)深入讲解 C++ 内存模型与原子操作,内容较为高级和详细。

🎯 两种阅读路径

  1. 🚀 实践优先路径 (推荐初学者)

    • 跳过 5.2.3 ,直接跳转到 [5.3 ConditionNotifier](#5.3 ConditionNotifier)
    • 先学会使用 iceoryx 的通知机制
    • 需要时再回来阅读内存序细节
    • 适合: 想快速上手、实现功能的开发者
  2. 📚 原理深入路径 (推荐有经验的开发者)

    • 完整阅读 5.2.3 的所有小节
    • 深入理解无锁编程的底层原理
    • 掌握 acquire/release 内存序的正确使用
    • 适合: 想理解 iceoryx 内部实现、进行性能优化的开发者

💡 建议 : 如果你是第一次学习 iceoryx,强烈建议选择路径1 (实践优先)

当你在使用过程中遇到内存序相关的问题时,再回来深入阅读本节。


5.2.3 C++ 内存序快速入门

📖 完整内容

本节提供 C++ 内存模型的快速概览。如果你需要深入理解,请参阅:

对于初次学习 iceoryx,理解本节的基本概念已足够。

在深入 iceoryx 的无锁通知机制之前,我们需要理解 C++ 原子操作和内存序(Memory Order)的基本概念。

为什么需要内存序?

现代 CPU 和编译器会对指令进行重排序以提升性能,这可能导致多线程程序出现意外行为:

cpp 复制代码
// 问题示例
int data = 0;
bool ready = false;

// 线程1:生产者
void producer() {
    data = 42;      // 可能被重排到 ready = true 之后!
    ready = true;
}

// 线程2:消费者
void consumer() {
    if (ready) {
        process(data);  // 可能看到 data = 0!
    }
}

内存序就是用来控制这种重排序的机制。

C++ 内存序类型

C++11 提供了六种内存序,从弱到强排列:

内存序 性能 保证 典型用途
relaxed 最快 仅原子性,无顺序保证 计数器
acquire 后续操作不能重排到前面 读取数据
release 之前操作不能重排到后面 发布数据
acq_rel acquire + release 读-修改-写
seq_cst 最慢 全局顺序一致性(默认) 复杂逻辑
acquire-release 模式(最常用)

这是最常见且最实用的同步模式:

cpp 复制代码
std::atomic<int> data{0};
std::atomic<bool> ready{false};

// 生产者
void producer() {
    data.store(42, std::memory_order_relaxed);      // 写数据(relaxed)
    ready.store(true, std::memory_order_release);   // 发布标志(release)
    // release 确保:data.store 不会被重排到 ready.store 之后
}

// 消费者
void consumer() {
    while (!ready.load(std::memory_order_acquire))  // 等待标志(acquire)
        ;
    // acquire 确保:data.load 不会被重排到 ready.load 之前
    int value = data.load(std::memory_order_relaxed);  // 读数据(relaxed)
    assert(value == 42);  // ✅ 一定成功!
}

关键点

  • release:像一道"栅栏",阻止之前的操作被重排到后面
  • acquire:像一道"栅栏",阻止之后的操作被重排到前面
  • 配对使用:release-acquire 建立跨线程的同步关系
iceoryx 中的应用

1. 引用计数器(用 relaxed)

cpp 复制代码
// iceoryx 的引用计数器使用 relaxed
m_referenceCounter.fetch_add(1U, std::memory_order_relaxed);
m_referenceCounter.fetch_sub(1U, std::memory_order_relaxed);

为什么可以用 relaxed

  • 数据同步由 ChunkQueue 的 push/pop 保证(使用 acquire/release)
  • 引用计数只用于跟踪"有多少人在用",不用于同步数据访问

2. 通知机制(用 release/acquire)

cpp 复制代码
// 发布者
void notify() {
    // release:确保之前的数据写入对订阅者可见
    m_activeNotifications[i].store(true, std::memory_order_release);
    m_semaphore->post();
}

// 订阅者
void wait() {
    m_semaphore->wait();
    // acquire:确保能看到发布者的数据写入
    if (m_activeNotifications[i].load(std::memory_order_acquire)) {
        process_data();
    }
}
实战建议

初学者

  • ✅ 默认使用 seq_cst(最安全)
  • ✅ 理解后再优化为 acquire/release

有经验者

  • ✅ 发布-订阅:用 release(写)+ acquire(读)
  • ✅ 简单计数器:用 relaxed
  • ✅ 不确定:用 seq_cst

性能对比(x86_64):

  • relaxed: ~2 ns/op
  • acquire/release: ~3 ns/op
  • seq_cst: ~5 ns/op
常见陷阱

误用 relaxed :在有依赖关系的操作中会导致数据不一致

过度使用 seq_cst :牺牲性能却未带来实际收益

忽略 ABA 问题:在无锁数据结构中可能导致逻辑错误

调试工具
bash 复制代码
# 使用 ThreadSanitizer 检测数据竞争
g++ -fsanitize=thread -g my_code.cpp
./a.out

📚 延伸阅读

想深入理解内存模型?阅读 附录A: C++ 内存模型与原子操作详解,包含:

  • 详细的 happens-before 关系分析
  • 完整的生产者-消费者示例
  • iceoryx 内存序使用的深入解析
  • ABA 问题及解决方案
  • 性能测试和调试技巧

5.2.4 验证脚本:信号量性能测试

创建测试脚本 test_semaphore_latency.sh

bash 复制代码
#!/bin/bash
# 测试信号量的唤醒延迟

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/../../build"

# 检查构建
if [ ! -f "${BUILD_DIR}/iox-roudi" ]; then
    echo "错误:未找到构建产物,请先构建 iceoryx"
    exit 1
fi

# 编译测试程序
cat > /tmp/sem_latency_test.cpp << 'EOF'
#include <semaphore.h>
#include <pthread.h>
#include <chrono>
#include <iostream>
#include <vector>
#include <numeric>

constexpr int ITERATIONS = 10000;

void* consumer_thread(void* arg) {
    sem_t* sem = static_cast<sem_t*>(arg);
    for (int i = 0; i < ITERATIONS; ++i) {
        sem_wait(sem);
    }
    return nullptr;
}

int main() {
    sem_t sem;
    sem_init(&sem, 0, 0);  // 线程间共享
    
    pthread_t thread;
    pthread_create(&thread, nullptr, consumer_thread, &sem);
    
    // 等待线程启动
    usleep(1000);
    
    std::vector<double> latencies;
    latencies.reserve(ITERATIONS);
    
    for (int i = 0; i < ITERATIONS; ++i) {
        auto start = std::chrono::high_resolution_clock::now();
        sem_post(&sem);
        // 注意:这里测量的是 post 的开销,不是唤醒延迟
        auto end = std::chrono::high_resolution_clock::now();
        
        auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
            end - start).count();
        latencies.push_back(duration);
    }
    
    pthread_join(thread, nullptr);
    sem_destroy(&sem);
    
    // 统计
    latencies.erase(std::remove_if(latencies.begin(), latencies.end(),
        [](double v) { return !std::isfinite(v); }),
        latencies.end());
    std::stable_sort(latencies.begin(), latencies.end());
    double avg = std::accumulate(latencies.begin(), latencies.end(), 0.0) 
                 / latencies.size();
    double p50 = latencies[latencies.size() / 2];
    double p99 = latencies[latencies.size() * 99 / 100];
    
    std::cout << "sem_post() 性能统计:\n";
    std::cout << "  平均: " << avg << " ns\n";
    std::cout << "  P50:  " << p50 << " ns\n";
    std::cout << "  P99:  " << p99 << " ns\n";
    
    return 0;
}
EOF

g++ -O2 -pthread /tmp/sem_latency_test.cpp -o /tmp/sem_latency_test
/tmp/sem_latency_test
rm -f /tmp/sem_latency_test /tmp/sem_latency_test.cpp

(未完待续)

相关推荐
企鹅会滑雪1 天前
【无标题】
开发语言·python
幻云20101 天前
Next.js 之道:从全栈思维到架构实战
开发语言·javascript·架构
阿豪学编程1 天前
【Linux】线程同步和线程互斥
linux·开发语言
寻星探路1 天前
【深度长文】深入理解网络原理:TCP/IP 协议栈核心实战与性能调优
java·网络·人工智能·python·网络协议·tcp/ip·ai
博晶网络1 天前
MR400D工业级4G路由器:TCP/IP与UDP协议,解锁工业物联网高效传输新范式‌
网络·单片机·嵌入式硬件
寻星探路1 天前
【Python 全栈测开之路】Python 进阶:库的使用与第三方生态(标准库+Pip+实战)
java·开发语言·c++·python·ai·c#·pip
2301_800256111 天前
第九章:空间网络模型(空间网络查询、数据模型、Connected、with Recursive、pgRouting)
网络·数据库·算法·postgresql·oracle
猿小路1 天前
抓包工具-Wireshark
网络·测试工具·wireshark
SmartRadio1 天前
CH585M+MK8000、DW1000 (UWB)+W25Q16的低功耗室内定位设计
c语言·开发语言·uwb