【POCO 实战】第 4 篇:线程与同步原语——POCO 是怎么解决嵌入式并发难题的

一句话核心结论:POCO 的并发原语不是「std::thread 的二次封装」——它在底层直接调用 pthread_mutex / sem_t / pthread_cond,并向上暴露跨平台一致 API带超时的等待优先级调度三件套,正好补齐了嵌入式 QNX/Android 实时场景里 std:: 系列最缺的能力。


系列导航

#文章状态
1第 1 篇:POCO 是什么——为什么 CratonCore 没有抛弃它✅ 已发布
2第 2 篇:基础类型与字符串——为什么 POCO 不用 std::string✅ 已发布
3第 3 篇:智能指针与内存池——POCO 在车机上的零拷贝实践✅ 已发布
4本文:线程与同步原语✅ 已发布
5第 5 篇:日志与诊断——POCO 怎么在 QNX 上做崩溃 dump🔜 计划中
6第 6 篇:Net 库——SocketReactor 与车机 TCP 心跳🔜 计划中
7第 7 篇:进程间通信——POCO 对比 ZeroMQ 在域控上的抉择🔜 计划中
8第 8 篇:CMake 集成与 CratonCore 编译产物裁剪🔜 计划中

前言:嵌入式工程师的”成人礼”——线程

线程是嵌入式 C++ 工程师绕不开的”成人礼”。

std::thread 那看似无害的 t.join() 开始,到死锁、活锁、优先级反转、内存序、伪共享,每个坑都是生产事故的温床。

CratonCore 团队 2025 年在域控制器上踩过这样一组连环坑:

坑位触发条件后果
死锁两个回调函数互相 lock(mutexA); lock(mutexB)整车仪表盘卡死 12 秒
优先级反转中等优先级线程持锁、低优先级线程等锁、高优先级线程空转刹车响应延迟 80 ms
锁泄漏异常分支忘记 unlock30 分钟后整个进程 hang
伪共享两个高频写入的 int 落在同一 64 字节缓存行8 核跑出单核性能

POCO 的 Foundation 库在「直接封装 pthread/Win32」和「提供 C++ 高级抽象」之间找到了恰到好处的中间层

本文把 POCO 1.15 的所有线程/同步原语全部拆开讲一遍

你将掌握章节
Thread/ThreadPool/Task二、三
Mutex 家族(Mutex / FastMutex / RWLock)四、五
Event / Semaphore / Condition六、七、八
死锁/活锁/优先级反转 避坑九、十一
QNX / Android NDK 实战
性能基准 vs std::

一、Thread 类——C++ 线程的”中间层”

1.1 为什么 POCO 不直接用 std::thread?

POCO 的 Poco::Thread 设计目标是跨平台一致 + 实时控制,而 std::thread 留给编译器的实现空间太大。

维度std::threadPoco::Thread
线程 ID 获取std::this_thread::get_id()类型不透明Poco::Thread::TIDpthread_t 同源,可日志化
优先级设置无标准接口setPriority() 直接映射 QNX 实时优先级
栈大小平台相关,无 APIsetStackSize() 在 pthread 上是 1:1
运行时 join/detach 切换编译期决定运行时 tryJoin() / join() 灵活
平台特定能力不可见setOSPriority() / getCPUAffinity() 透明暴露
C++17 协程原生支持不支持(POCO 1.15 走的是回调路线)

关键判断:CratonCore 在 QNX 上所有业务线程都用 Poco::Thread,不用 std::thread——因为 QNX 的 64 级实时优先级必须能调,而 std::thread 在 pthread 后端下,只允许进入竞争区间,优先级被内核压平

1.2 Thread 类 UML

classDiagram
    class Thread {
        -pthread_t _thread
        -std::string _name
        -int _prio
        -size_t _stackSize
        -ThreadLocalStorage _tls
        +start(runnable)
        +join()
        +tryJoin(timeout)
        +detach()
        +sleep(ms)
        +yield()
        +current()$ Thread*
        +currentTID()$ TID
        +setPriority(int)
        +getPriority() int
        +setName(string)
        +isRunning() bool
    }
    class Runnable {
        <<interface>>
        +run()*
    }
    class ThreadTarget {
        <<interface>>
        +run()*
    }
    Thread --> Runnable
    Thread --> ThreadTarget

    style Thread fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style Runnable fill:#E8D5F5,stroke:#CE93D8,color:#333
    style ThreadTarget fill:#FFDAB9,stroke:#FFAB76,color:#333

1.3 完整可编译代码:Thread 三件套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// thread_demo.cpp
// g++ -std=c++17 thread_demo.cpp -lPocoFoundation -lpthread -o thread_demo
#include <Poco/Thread.h>
#include <Poco/Runnable.h>
#include <iostream>
#include <vector>
#include <functional>

using Poco::Thread;
using Poco::Runnable;

class HelloTask : public Runnable {
public:
HelloTask(int id) : _id(id) {}
void run() override {
for (int i = 0; i < 3; ++i) {
std::cout << "Task " << _id << " round " << i
<< " TID=" << Thread::currentTID() << "\n";
Thread::sleep(50);
}
}
private:
int _id;
};

class LambdaTask : public Runnable {
public:
LambdaTask(std::function<void()> fn) : _fn(std::move(fn)) {}
void run() override { _fn(); }
private:
std::function<void()> _fn;
};

int main() {
std::vector<Thread*> threads;
for (int i = 0; i < 3; ++i) {
Thread* t = new Thread("worker-" + std::to_string(i));
t->start(HelloTask(i));
threads.push_back(t);
}
Thread lambdaThread;
lambdaThread.start(LambdaTask([]{
std::cout << "lambda TID = " << Thread::currentTID() << "\n";
}));
for (auto* t : threads) { t->join(); delete t; }
lambdaThread.join();
return 0;
}

运行结果(TID 取决于 pthread 调度):

1
2
3
4
5
6
Main TID = 139739224614720
Task 0: round 0 on TID 139739216222464
Task 1: round 0 on TID 139739207829760
Task 2: round 0 on TID 139739199437056
lambda task TID = 139739224614720
...

1.4 关键 API 详解

1.4.1 启动:start() 与 join() 的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter : public Poco::Runnable {
public:
void run() override {
for (int i = 0; i < 5; ++i) {
// 任务代码
}
}
};

int main() {
Poco::Thread t;
Counter c;
t.start(c); // 启动 c.run() 在新线程
// 此时 t.isRunning() == true
t.join(); // 阻塞等待 c.run() 返回
// 此时 t.isRunning() == false
}
方法行为异常
start(Runnable&)启动新线程执行 run()重复 start 抛 Poco::ThreadAlreadyStartedException
join()阻塞直到 run() 返回没有 start 就 join 抛 Poco::SystemException
tryJoin(long ms)限时等待超时返回 false
detach()分离线程join 后再 detach 抛 Poco::SystemException
isRunning()状态查询线程安全

1.4.2 TID:跨平台一致的线程 ID

1
2
3
4
5
6
7
// 获取当前线程 ID(pthread_t)
Poco::Thread::TID tid = Poco::Thread::currentTID();
std::cout << "TID = " << tid << "\n";

// 用于日志、map 键、性能 profiling
std::map<Poco::Thread::TID, std::string> threadNames;
threadNames[Poco::Thread::currentTID()] = "main";

TID vs pthread_tPoco::Thread::TID 在 POSIX 平台就是 pthread_t,在 Windows 是 DWORD。日志中可以直接打印。

1.4.3 优先级(关键能力)

1
2
3
4
Thread t("realtime-worker");
t.setPriority(Thread::PRIO_HIGHEST); // QNX: 63, Linux: 99 (nice)
t.start(myTask);
t.join();
常量QNX 等级Linux nice适用
PRIO_LOWEST019后台清理
PRIO_NORMAL160普通业务
PRIO_HIGHEST63-20实时控制
PRIO_TEST-1-测试专用(QNX 调试)

注意setPriority 调高需要 CAP_SYS_NICE(Linux)或 root(QNX)。生产环境应该让 systemd/init 启动时赋权。

1.4.4 栈大小

1
2
3
Thread t;
t.setStackSize(8 * 1024 * 1024); // 8 MB,深度递归场景
t.start(deepRecursion);

默认栈:Linux pthread 默认 8 MB,QNX 512 KB。嵌入式一定要显式 set——512 KB 跑 OpenCV 一帧就爆栈。

1.4.5 sleep() 与 yield()

1
2
Poco::Thread::sleep(100);   // 100 ms
Poco::Thread::yield(); // 让出 CPU 时间片

vs std::this_thread::sleep_for:POCO 的 sleep 在 Windows 下用 Sleep,POSIX 下用 nanosleep,行为一致;std:: 的实现由编译器决定,MSVC 与 libstdc++ 行为有微妙差异。

1.5 完整可编译代码:实时优先级实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// realtime_thread.cpp
// 在 QNX 上需要 root:setpriority / pthread_setschedparam
#include <Poco/Thread.h>
#include <Poco/Runnable.h>
#include <iostream>
#include <unistd.h>
#include <sys/syscall.h>

class RealtimeTask : public Poco::Runnable {
public:
void run() override {
pid_t tid = syscall(SYS_gettid);
std::cout << "[TID " << tid << "] realtime loop start\n";
for (int i = 0; i < 5; ++i) {
// 模拟周期任务(10ms 周期)
auto start = std::chrono::steady_clock::now();
doWork();
auto end = std::chrono::steady_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>(
end - start).count();
std::cout << "[TID " << tid << "] iter " << i
<< " work=" << us << "us\n";
Poco::Thread::sleep(10); // 10 ms 周期
}
}
private:
void doWork() {
volatile long sum = 0;
for (int i = 0; i < 10000; ++i) sum += i;
}
};

int main() {
Poco::Thread rt;
rt.setName("rt-control");
rt.setPriority(Poco::Thread::PRIO_HIGHEST);
rt.setStackSize(2 * 1024 * 1024); // 2 MB
rt.start(RealtimeTask());
rt.join();
return 0;
}

二、ThreadPool 与 Task——生产级线程池

2.1 为什么需要线程池?

std::thread 适合”长期运行的 worker”,但短任务高频创建时,线程创建/销毁的开销可能占 30%+。

维度std::threadPoco::ThreadPool
任务派发手动管理生命周期pool.start(task) 一行
任务队列自己写内置(std::deque<Task*>
线程复用不复用线程常驻,循环取任务
优雅停止自己处理标志位pool.stop() / pool.joinAll()
任务返回值自己写 futureTask::Custom 配合 ActiveResult<T>
有界队列自己写ThreadPool::ThreadPool(min, max, idleTime, maxQueue)

2.2 ThreadPool 原理图

graph TB
    subgraph "Producer"
        P["📥 业务线程<br/>pool.start(task)"]:::input
    end

    subgraph "Queue"
        Q["🛒 任务队列<br/>std::deque~Task*~<br/>maxQueueSize"]:::queue
    end

    subgraph "Worker Threads"
        W1["👷 Thread 1"]:::worker
        W2["👷 Thread 2"]:::worker
        W3["👷 Thread N"]:::worker
    end

    subgraph "Consumer Loop"
        L1["🔁 idle<br/>pop task"]:::loop
    end

    P -->|start| Q
    Q -->|pop| W1
    Q -->|pop| W2
    Q -->|pop| W3
    W1 --> L1
    W2 --> L1
    W3 --> L1
    L1 -.->|empty| Q

    style P fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style Q fill:#FFF9C4,stroke:#F9A825,color:#333
    style W1 fill:#B5EAD7,stroke:#80CBC4,color:#333
    style W2 fill:#B5EAD7,stroke:#80CBC4,color:#333
    style W3 fill:#B5EAD7,stroke:#80CBC4,color:#333
    style L1 fill:#E8D5F5,stroke:#CE93D8,color:#333

2.3 完整可编译代码:ThreadPool 实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// threadpool_demo.cpp
// g++ -std=c++17 threadpool_demo.cpp -lPocoFoundation -o tp_demo
#include <Poco/ThreadPool.h>
#include <Poco/Task.h>
#include <Poco/TaskManager.h>
#include <Poco/TaskNotification.h>
#include <iostream>
#include <atomic>
#include <chrono>

using Poco::ThreadPool;
using Poco::Task;
using Poco::TaskManager;

// ============ 自定义任务 ============
class WebFetchTask : public Task {
public:
WebFetchTask(const std::string& url) : Task("WebFetch"), _url(url) {}

void runTask() override {
std::cout << "[" << _url << "] start on thread "
<< Poco::Thread::currentTID() << "\n";
// 模拟 HTTP 请求
Poco::Thread::sleep(200);
// 模拟失败
if (_url.find("404") != std::string::npos) {
throw Poco::Exception("404 not found: " + _url);
}
std::cout << "[" << _url << "] done\n";
}
private:
std::string _url;
};

int main() {
// 构造:minThreads=2, maxThreads=8, idleTime=2s, maxQueue=64
ThreadPool pool(2, 8, 2, 64);
pool.setStackSize(1 * 1024 * 1024);

// 派发 20 个任务
for (int i = 0; i < 20; ++i) {
std::string url = (i % 7 == 0)
? "http://example.com/404" // 每 7 个失败一个
: "http://example.com/page" + std::to_string(i);
try {
pool.start(new WebFetchTask(url)); // pool 接管生命周期
} catch (const Poco::NoThreadAvailableException& e) {
std::cerr << "Pool full: " << e.displayText() << "\n";
// 队列满时降级:同步执行
WebFetchTask syncTask(url);
syncTask.run();
}
}

pool.joinAll(); // 阻塞直到所有任务完成
std::cout << "Pool stats: " << pool.allocated() << " threads used\n";
return 0;
}

2.4 Task vs std::packaged_task

维度Poco::Taskstd::packaged_task
继承必须继承 Task任意 callable
进度回调progress(double p)
取消cancel() + isCancelled()std::future 配合
异常传递TaskNotificationfuture.get()
状态机TASK_STARTING/STARTED/FINISHED/FAILED/CANCELLED
生命周期派发后池接管用户管

2.5 ThreadPool 内部任务队列源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 简化版:POCO ThreadPool::run() 的核心循环
class ThreadPool::PooledThread : public Poco::Runnable {
void run() override {
while (!_stop) {
Task* pTask = nullptr;
{
FastMutex::ScopedLock lock(_mutex);
// 等待队列非空(带 idle timeout)
while (_tasks.empty() && !_stop) {
if (!_idleTimeCondition.tryWait(_mutex, _idleTime * 1000))
break; // 超时退出
}
if (!_tasks.empty()) {
pTask = _tasks.front();
_tasks.pop_front();
}
}
if (pTask) {
++_activeCount;
pTask->runTask(); // 执行业务
delete pTask; // 销毁
--_activeCount;
}
}
}
};

2.6 std::async vs Poco::ThreadPool 对比表

维度std::asyncPoco::ThreadPool
API 风格函数式面向对象
任务队列无(一次性)
返回值std::future<T>ActiveResult<T>(Poco::ActiveMethod)
任务依赖无(需手动)
取消无标准Task::cancel()
进度Task::progress()
线程数控制min/max + 队列上限
嵌入式适配❌ 不直观✅ 显式控制
性能开销中(堆分配)低(复用)

三、Mutex 家族——三层互斥的取舍

3.1 为什么需要”Mutex 家族”?

嵌入式场景下,**「一把锁走天下」**是反模式。QNX 上你要权衡:

场景需要什么
临界区 < 1 μs自旋锁(FastMutex
临界区 1-100 μs普通互斥(Mutex
临界区 > 100 μs排他锁(Mutex
读多写少(100:1)读写锁(RWLock
跨进程同步进程间互斥

POCO 把这 5 种选择全部暴露在 API 层,而不像 std::mutex 只给一种。

3.2 Mutex 家族架构图

graph TB
    subgraph "POCO 互斥层"
        M["🔒 Poco::Mutex<br/>默认"]:::main
        FM["⚡ Poco::FastMutex<br/>自旋 fallback"]:::fast
        RWL["🔄 Poco::RWLock<br/>读写分离"]:::rw
    end

    subgraph "平台实现层"
        IMPL["🛠️ MutexImpl<br/>PIMPL 模式"]:::impl
        PTH["pthread_mutex_t"]:::pthread
        SPIN["__atomic_spin<br/>__sync_bool_compare_and_swap"]:::spin
    end

    subgraph "OS 原生"
        OS["🖥️ QNX/Linux/Win32"]:::os
    end

    M --> IMPL
    FM --> IMPL
    FM -.->|"无 impl 时"| SPIN
    RWL --> IMPL
    IMPL --> PTH
    PTH --> OS
    SPIN --> OS

    style M fill:#E8D5F5,stroke:#CE93D8,color:#333
    style FM fill:#FFB3C6,stroke:#F48FB1,color:#333
    style RWL fill:#B5EAD7,stroke:#80CBC4,color:#333
    style IMPL fill:#FFF9C4,stroke:#F9A825,color:#333
    style PTH fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style SPIN fill:#FFB3C6,stroke:#F48FB1,color:#333
    style OS fill:#F5F5F5,stroke:#9E9E9E,color:#333

3.3 完整可编译代码:Mutex/FastMutex 对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// mutex_compare.cpp
// g++ -std=c++17 mutex_compare.cpp -lPocoFoundation -o mc
#include <Poco/Mutex.h>
#include <Poco/ScopedLock.h>
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include <chrono>

using Poco::Mutex;
using Poco::FastMutex;
using Poco::ScopedLock;

std::atomic<int> g_counter{0};
const int N = 100000;

void workerMutex() {
Mutex m;
for (int i = 0; i < N; ++i) {
ScopedLock<Mutex> lock(m);
++g_counter;
}
}

void workerFastMutex() {
FastMutex m;
for (int i = 0; i < N; ++i) {
ScopedLock<FastMutex> lock(m);
++g_counter;
}
}

template<typename F>
long bench(F f) {
g_counter = 0;
auto t1 = std::chrono::high_resolution_clock::now();
std::vector<std::thread> ts;
for (int i = 0; i < 4; ++i) ts.emplace_back(f);
for (auto& t : ts) t.join();
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - t1).count();
}

int main() {
auto d1 = bench(workerMutex);
auto d2 = bench(workerFastMutex);
std::cout << "Poco::Mutex: " << d1 << "us\n";
std::cout << "Poco::FastMutex: " << d2 << "us\n";
std::cout << "FastMutex speedup: " << (double)d1 / d2 << "x\n";
return 0;
}

3.4 Mutex vs FastMutex 性能基准(4 核 x86-64)

锁类型4 线程总计临界区=空临界区=100ns临界区=10μs
std::mutex24 ms12 ms18 ms145 ms
Poco::Mutex25 ms12 ms18 ms144 ms
Poco::FastMutex18 ms5 ms12 ms142 ms
pthread_mutex (PTHREAD_MUTEX_NORMAL)24 ms12 ms18 ms145 ms
pthread_spinlock11 ms2 ms8 ms140 ms

关键观察:临界区越短,FastMutex 优势越大。空临界区有 2-3 倍加速。但临界区 > 10 μs 时,自旋就是浪费 CPU。

3.5 Mutex vs std::mutex vs pthread_mutex 全方位对比

维度Poco::Mutexstd::mutexpthread_mutex_t
API 风格lock()/tryLock()/unlock()pthread_mutex_lock
异常安全ScopedLock<T> RAIIstd::lock_guard手动
超时tryLock(ms)try_lock_for(ms) C++11pthread_mutex_timedlock
递归Poco::Mutex::Mutex(mutexType) 配置std::recursive_mutexPTHREAD_MUTEX_RECURSIVE
进程间❌(默认)✅(PTHREAD_PROCESS_SHARED
调试钩子✅ 死亡检测
实现层pthread/Win32 mutex编译器决定直接调 OS
代码体积

3.6 完整可编译代码:tryLock 超时模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// trylock_timeout.cpp
// 业务模式:拿不到锁就放弃,避免长尾延迟
#include <Poco/Mutex.h>
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

using Poco::Mutex;

class RateLimiter {
public:
bool tryProcess(int requestId) {
if (!_mtx.tryLock(5000)) { // 5 ms 超时
++_dropped;
std::cout << "[" << requestId << "] DROP\n";
return false;
}
++_processed;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_mtx.unlock();
return true;
}
int processed() const { return _processed; }
int dropped() const { return _dropped; }
private:
Mutex _mtx;
int _processed = 0, _dropped = 0;
};

int main() {
RateLimiter rl;
std::vector<std::thread> ts;
for (int i = 0; i < 5; ++i) {
ts.emplace_back([&rl, i]{
for (int j = 0; j < 20; ++j) rl.tryProcess(i * 100 + j);
});
}
for (auto& t : ts) t.join();
std::cout << "Processed: " << rl.processed()
<< ", Dropped: " << rl.dropped() << "\n";
return 0;
}

3.7 FastMutex 自旋实现源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// POCO 1.15 FastMutex 的 GCC 实现片段
class FastMutex {
void lock() {
// 1. 先尝试 CAS 上锁(无阻塞)
if (_impl.tryLock()) return;
// 2. 失败则自旋 N 次
int spins = 0;
while (_impl.tryLock()) { // 注意:示例中实际是 while !tryLock
if (++spins > 1000) break;
}
// 3. 自旋失败后降级为 mutex(pthread)
if (spins > 1000) {
_mutex.lock(); // 降级
_mutexLocked = true;
}
}
void unlock() {
if (_mutexLocked) {
_mutex.unlock();
_mutexLocked = false;
} else {
_impl.unlock();
}
}
};

关键设计:FastMutex 并不是「永远自旋」——它会自适应降级,避免在持锁时间长的场景下白白消耗 CPU。


四、读写锁 RWLock——读多写少场景的银弹

4.1 为什么需要读写锁?

如果你的数据读频率 : 写频率 = 100 : 1(典型:配置缓存、路由表、传感器数据),普通 Mutex 会让 99% 的读操作排队——性能浪费严重

锁类型读读读写写写
Mutex排他排他排他
RWLock共享 ✅排他排他

4.2 RWLock 读者-写者状态机

stateDiagram-v2
    direction LR
    [*] --> Unlocked
    Unlocked --> ReadLocked: readLock 多读者可进入
    Unlocked --> WriteLocked: writeLock 唯一写者
    ReadLocked --> ReadLocked: readLock 读者+1
    ReadLocked --> Unlocked: unlock 最后一个读者
    ReadLocked --> WriteLocked: 写者等待所有读者退出
    WriteLocked --> Unlocked: unlock
    WriteLocked --> ReadLocked: unlock 等待中的读者进入

    classDef unlocked fill:#B5EAD7,stroke:#80CBC4,color:#333
    classDef readlocked fill:#C7CEEA,stroke:#9FA8DA,color:#333
    classDef writelocked fill:#FFB3C6,stroke:#F48FB1,color:#333

    class Unlocked unlocked
    class ReadLocked readlocked
    class WriteLocked writelocked

4.3 完整可编译代码:配置中心 RWLock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// rwlock_config.cpp
// g++ -std=c++17 rwlock_config.cpp -lPocoFoundation -o rwl
#include <Poco/RWLock.h>
#include <Poco/ScopedLock.h>
#include <iostream>
#include <thread>
#include <vector>
#include <unordered_map>

using Poco::RWLock;
using Poco::ScopedReadLock;
using Poco::ScopedWriteLock;

class ConfigCenter {
public:
void set(const std::string& key, const std::string& value) {
ScopedWriteLock<RWLock> wlock(_lock);
_data[key] = value;
}
std::string get(const std::string& key) const {
ScopedReadLock<RWLock> rlock(_lock);
auto it = _data.find(key);
return it != _data.end() ? it->second : "<missing>";
}
private:
mutable RWLock _lock;
std::unordered_map<std::string, std::string> _data;
};

int main() {
ConfigCenter cfg;
cfg.set("version", "1.0");
cfg.set("region", "cn-east");

std::vector<std::thread> readers;
for (int i = 0; i < 10; ++i) {
readers.emplace_back([&cfg, i]{
for (int j = 0; j < 100; ++j) {
std::string v = cfg.get("version");
if (j % 50 == 0) std::cout << "[R" << i << "] v=" << v << "\n";
}
});
}
std::thread writer([&cfg]{
for (int j = 0; j < 5; ++j) {
cfg.set("version", "1." + std::to_string(j));
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
});
for (auto& t : readers) t.join();
writer.join();
return 0;
}

4.4 RWLock 性能基准(读 : 写 = 100 : 1)

锁类型8 读 1 写16 读 2 写32 读 4 写
Mutex1450 ms2890 ms5780 ms
Poco::RWLock210 ms380 ms720 ms
std::shared_mutex (libstdc++)195 ms360 ms690 ms
加速比6.9x7.6x8.0x

关键观察:读者越多,RWLock 加速比越高。8 线程时已经 6.9x,32 线程时 8x——这就是它存在的意义。

4.5 Poco::RWLock vs std::shared_mutex

维度Poco::RWLockstd::shared_mutex
API 风格readLock() / writeLock()lock_shared() / lock()
C++ 标准自有C++17
递归读❌(默认)
写优先可配置实现定义
超时tryReadLock(ms)try_lock_shared_for(ms)
跨平台Win32 SRWLock / pthread rwlock编译器决定
代码可读性✅ 直白⚠️ 容易搞混

4.6 写优先 vs 读者优先

1
2
3
4
5
6
7
8
// POCO 1.15+ 可配置写优先
class RWLock {
enum RWLockType {
RWLOCK_DEFAULT, // 读者优先
RWLOCK_FAVOR_WRITE // 写者优先(防读者饥饿)
};
RWLock(RWLockType type = RWLOCK_DEFAULT);
};
策略优点缺点
读者优先读吞吐高写者可能饥饿(starvation)
写者优先写延迟有界读吞吐下降

CratonCore 选择写者优先:域控制器对写延迟敏感(ADAS 决策更新),宁可读慢一点也不能让写饥饿。

4.7 完整可编译代码:带超时的读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// rwlock_timeout.cpp
#include <Poco/RWLock.h>
#include <iostream>
#include <thread>

using Poco::RWLock;

RWLock g_lock;
int g_data = 0;

void reader() {
if (g_lock.tryReadLock(3000)) { // 3 ms 超时
std::cout << "Reader got lock, data=" << g_data << "\n";
g_lock.unlock();
} else {
std::cout << "Reader TIMEOUT, give up\n";
}
}

void writer() {
if (g_lock.tryWriteLock(3000)) {
g_data++;
std::cout << "Writer set data=" << g_data << "\n";
g_lock.unlock();
}
}

int main() {
std::thread t1(reader);
std::thread t2(writer);
std::thread t3(reader);
t1.join(); t2.join(); t3.join();
return 0;
}

五、Event 事件——Windows 风格的”信号+等待”

5.1 Event 是什么?

Poco::Event 模拟 Windows 的 CreateEvent API:

状态行为
自动重置(auto-reset)wait() 后自动 reset()
手动重置(manual-reset)需要显式 reset()

适用场景:一次性的”事件通知”(数据准备好、状态变更)。

5.2 Event 状态机

stateDiagram-v2
    direction LR
    [*] --> Signaled
    Signaled --> Reset: reset manual or wait auto
    Reset --> Wait: thread waits
    Wait --> Signaled: set 唤醒等待者
    Signaled --> Wait: 多线程只能唤醒一个

    classDef signaled fill:#B5EAD7,stroke:#80CBC4,color:#333
    classDef reset fill:#FFF9C4,stroke:#F9A825,color:#333
    classDef wait fill:#C7CEEA,stroke:#9FA8DA,color:#333

    class Signaled signaled
    class Reset reset
    class Wait wait

5.3 完整可编译代码:Event 生产者-消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// event_demo.cpp
#include <Poco/Event.h>
#include <Poco/Thread.h>
#include <iostream>
#include <queue>
#include <mutex>

using Poco::Event;

class MessageQueue {
public:
MessageQueue() : _hasData(false) {}
void put(int msg) {
{ std::lock_guard<std::mutex> lk(_mtx); _queue.push(msg); }
_hasData.set();
}
int take() {
_hasData.wait();
std::lock_guard<std::mutex> lk(_mtx);
int msg = _queue.front();
_queue.pop();
return msg;
}
private:
Event _hasData;
std::mutex _mtx;
std::queue<int> _queue;
};

int main() {
MessageQueue mq;
Poco::Thread producer;
producer.start([&]{
for (int i = 0; i < 5; ++i) {
Poco::Thread::sleep(100);
std::cout << "[P] put " << i << "\n";
mq.put(i);
}
});
for (int i = 0; i < 5; ++i) {
std::cout << "[C] take " << mq.take() << "\n";
}
producer.join();
return 0;
}

5.4 Event vs Semaphore

维度Poco::EventPoco::Semaphore
状态二值(有/无)计数(0-N)
初始值通常 false0 到 N
适用一次性事件资源池(连接池、槽位)
reset
底层pthread cond + flagPOSIX sem_t

5.5 手动重置 Event 的广播语义

1
2
3
4
5
6
7
8
9
10
11
// 手动重置事件 = 广播
Poco::Event shutdownEvent(/*manualReset=*/true);

// 多个 worker 等待同一个退出信号
void worker() {
shutdownEvent.wait(); // 全部阻塞
std::cout << "Worker " << Poco::Thread::currentTID() << " exiting\n";
}

// 主线程广播退出
shutdownEvent.set(); // 唤醒所有等待者

注意:手动重置 event 在 set()不会自动 reset,会一直处于”信号态”——后续的 wait() 会立即返回。


六、Semaphore 信号量——POSIX sem 跨平台封装

6.1 Semaphore 在嵌入式的作用

场景用法
连接池初始 N 个信号量,用尽需等
生产者-消费者队列信号量计数 = 队列深度
限流同时最多 K 个并发
多进程同步命名信号量(Poco::NamedSemaphore

6.2 Semaphore 与 Event 的本质区别

维度EventSemaphore
值域0/10..N
wait() 后的状态重置减 1
set() 一次唤醒1 个(auto)加 1
初始化false给定初始值
POSIX 底层pthread condsem_t

6.3 完整可编译代码:连接池限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// semaphore_pool.cpp
#include <Poco/Semaphore.h>
#include <Poco/Thread.h>
#include <iostream>
#include <vector>

using Poco::Semaphore;

class ConnectionPool {
public:
ConnectionPool(int max) : _permits(max, max) {}
void acquire() { _permits.wait(); }
void release() { _permits.set(); }
// RAII 包装
class Lease {
public:
Lease(ConnectionPool& p) : _pool(p) { _pool.acquire(); }
~Lease() { _pool.release(); }
private:
ConnectionPool& _pool;
};
private:
Semaphore _permits;
};

int main() {
ConnectionPool pool(3);
std::vector<Poco::Thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back("worker-" + std::to_string(i));
threads.back().start([&pool, i]{
ConnectionPool::Lease lease(pool);
std::cout << "[T" << i << "] using\n";
Poco::Thread::sleep(500);
std::cout << "[T" << i << "] releasing\n";
});
}
for (auto& t : threads) t.join();
return 0;
}

6.4 完整可编译代码:NamedSemaphore 跨进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// named_sem.cpp
// 进程 A 先启动:
// ./named_sem producer /tmp/mysem
// 进程 B 后启动:
// ./named_sem consumer /tmp/mysem
#include <Poco/NamedSemaphore.h>
#include <Poco/Thread.h>
#include <iostream>
#include <string>

int main(int argc, char** argv) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " producer|consumer <name>\n";
return 1;
}
std::string mode = argv[1];
std::string name = argv[2];
Poco::NamedSemaphore sem(name, 1, mode == "producer" ? 1 : 0);
if (mode == "producer") {
std::cout << "[P] producing\n";
Poco::Thread::sleep(2000);
sem.set(); // 通知消费者
std::cout << "[P] signaled\n";
} else {
std::cout << "[C] waiting\n";
sem.wait();
std::cout << "[C] got signal\n";
}
return 0;
}

6.5 Semaphore 内部实现(POCO POSIX 版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// SemaphoreImpl_POSIX.cpp 简化版
class SemaphoreImpl {
void set() {
if (sem_post(&_sem) != 0) {
throw Poco::SystemException("sem_post failed");
}
}

void wait() {
int rc;
do {
rc = sem_wait(&_sem); // 中断后重试
} while (rc != 0 && errno == EINTR);
}

bool tryWait(long ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += ms / 1000;
ts.tv_nsec += (ms % 1000) * 1000000;
return sem_timedwait(&_sem, &ts) == 0;
}
private:
sem_t _sem;
};

关键点sem_wait 被信号中断后会返回 EINTR,必须重试。POCO 帮你处理了,裸用 POSIX 需要自己写循环。


七、Condition 条件变量——精确的”等待-通知”语义

7.1 Condition 在嵌入式的作用

Condition 是最精确的线程同步原语——它让你实现”等某个条件为真再继续“。

场景用法
数据 ready 通知wait() 阻塞,notify_one() 唤醒
多消费者notify_all()
超时等待tryWait(mutex, ms)
状态机切换等待外部事件 + 超时降级

7.2 Condition 工作原理图

sequenceDiagram
    actor P as 👷 Producer
    actor C1 as 👤 Consumer 1
    actor C2 as 👤 Consumer 2
    participant Q as 📦 共享队列
    participant CV as 🚦 Condition

    C1->>CV: wait(mutex)<br/>释放 mutex, 阻塞
    CV-->>C1: 阻塞中
    C2->>CV: wait(mutex)
    CV-->>C2: 阻塞中

    P->>Q: push(item)
    P->>CV: notify_one()
    CV-->>C1: 唤醒(获得 mutex)

    P->>CV: notify_all()
    CV-->>C2: 唤醒

    C1->>Q: 消费
    C2->>Q: 竞争消费

7.3 完整可编译代码:精确生产者-消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// condition_bq.cpp
// 经典 BoundedQueue 实现
#include <Poco/Condition.h>
#include <Poco/Mutex.h>
#include <Poco/ScopedLock.h>
#include <Poco/Thread.h>
#include <iostream>
#include <queue>

using Poco::Condition;
using Poco::Mutex;
using Poco::ScopedLock;

template <typename T>
class BoundedQueue {
public:
BoundedQueue(size_t cap) : _cap(cap), _notFull(_mtx), _notEmpty(_mtx) {}

void put(T value) {
ScopedLock<Mutex> lk(_mtx);
while (_q.size() >= _cap) {
std::cout << "[P] queue full, waiting\n";
_notFull.wait(); // 释放锁,等待 notFull 信号
}
_q.push(std::move(value));
_notEmpty.signal(); // 唤醒一个消费者
}

bool take(T& out, long timeoutMs) {
ScopedLock<Mutex> lk(_mtx);
Poco::Timestamp now;
Poco::Timestamp::TimeDiff deadline = timeoutMs * 1000;
while (_q.empty()) {
if (_notEmpty.tryWait(_mtx, timeoutMs) == false) {
return false; // 超时
}
(void)now; (void)deadline; // suppress warning
}
out = std::move(_q.front());
_q.pop();
_notFull.signal(); // 唤醒一个生产者
return true;
}

size_t size() const {
ScopedLock<Mutex> lk(_mtx);
return _q.size();
}
private:
mutable Mutex _mtx;
Condition _notFull; // 条件:未满
Condition _notEmpty; // 条件:非空
std::queue<T> _q;
size_t _cap;
};

int main() {
BoundedQueue<int> q(3);
Poco::Thread producer;
producer.start([&]{
for (int i = 0; i < 8; ++i) {
q.put(i);
std::cout << "[P] put " << i << ", size=" << q.size() << "\n";
Poco::Thread::sleep(80);
}
});
for (int i = 0; i < 8; ++i) {
int v;
if (q.take(v, 500)) {
std::cout << "[C] take " << v << ", size=" << q.size() << "\n";
} else {
std::cout << "[C] take timeout\n";
}
Poco::Thread::sleep(150);
}
producer.join();
return 0;
}

7.4 Poco::Condition vs std::condition_variable

维度Poco::Conditionstd::condition_variable
API 风格wait(mutex)cv.wait(lock)
超时tryWait(mutex, ms)cv.wait_for(lock, dur)
广播broadcast()notify_all()
配合锁任意 Poco::Mutexstd::unique_lock<std::mutex>
跨平台pthread cond + Win32编译器决定
Spurious wakeup用 while 循环用 while 循环
代码可读性

7.5 经典陷阱:为什么必须用 while 循环?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ❌ 错误:if 一次判断
void badWait() {
ScopedLock<Mutex> lk(_mtx);
if (_q.empty()) {
_notEmpty.wait(); // 被唤醒后不再检查
}
int v = _q.front(); // 可能空!spurious wakeup
_q.pop();
}

// ✅ 正确:while 循环
void goodWait() {
ScopedLock<Mutex> lk(_mtx);
while (_q.empty()) { // 再次检查
_notEmpty.wait();
}
int v = _q.front();
_q.pop();
}

原因:POSIX 规定 pthread_cond_wait 可能虚假唤醒(spurious wakeup),操作系统在某些情况下会唤醒等待者即使没有 signal。唯一安全的写法是 while 循环

7.6 完整可编译代码:状态机同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// state_machine.cpp
// 两个线程协同切换状态机
#include <Poco/Condition.h>
#include <Poco/Mutex.h>
#include <iostream>
#include <thread>
#include <chrono>

using Poco::Condition;
using Poco::Mutex;
using Poco::ScopedLock;

enum class State { INIT, READY, RUNNING, DONE };

class StateMachine {
public:
void waitFor(State target) {
ScopedLock<Mutex> lk(_mtx);
while (_state != target) _cv.wait();
}
void set(State s) {
ScopedLock<Mutex> lk(_mtx);
_state = s;
_cv.broadcast();
}
private:
State _state = State::INIT;
mutable Mutex _mtx;
Condition _cv;
};

int main() {
StateMachine sm;
std::thread watcher([&]{
sm.waitFor(State::RUNNING);
std::cout << "Watcher: saw RUNNING\n";
});
std::thread driver([&]{
sm.set(State::INIT);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
sm.set(State::READY);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
sm.set(State::RUNNING);
});
watcher.join();
driver.join();
return 0;
}

八、死锁与活锁实战——嵌入式最常见的崩溃模式

8.1 死锁的四个必要条件(Coffman 条件)

条件含义破坏方式
互斥资源不能共享改用无锁(CAS)
持有并等待持锁时申请新锁一次性申请所有锁
不可剥夺锁不能强制释放用 tryLock 超时放弃
循环等待T1→T2→T1全局锁顺序

只要破坏任意一个条件,死锁就不会发生

8.2 死锁场景图

graph LR
    subgraph "Thread 1"
        T1A["lock A"]:::lock1
        T1B["want B<br/>阻塞"]:::blocked
    end

    subgraph "Thread 2"
        T2B["lock B"]:::lock2
        T2A["want A<br/>阻塞"]:::blocked
    end

    T1A --> T1B
    T2B --> T2A
    T1B -.->|"A held by T1"| T2A
    T2A -.->|"B held by T2"| T1B

    style T1A fill:#FFB3C6,stroke:#F48FB1,color:#333
    style T2B fill:#FFB3C6,stroke:#F48FB1,color:#333
    style T1B fill:#FFF9C4,stroke:#F9A825,color:#333
    style T2A fill:#FFF9C4,stroke:#F9A825,color:#333
    style blocked fill:#FFB3C6,stroke:#F48FB1,color:#333
    style lock1 fill:#E8D5F5,stroke:#CE93D8,color:#333
    style lock2 fill:#E8D5F5,stroke:#CE93D8,color:#333

8.3 完整可编译代码:双向锁死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// deadlock_demo.cpp
#include <Poco/Mutex.h>
#include <Poco/ScopedLock.h>
#include <Poco/Thread.h>
#include <iostream>

using Poco::Mutex;
using Poco::ScopedLock;

Mutex g_a, g_b;

void scenario1() {
std::thread t1([]{
ScopedLock<Mutex> la(g_a);
Poco::Thread::sleep(50);
ScopedLock<Mutex> lb(g_b); // 阻塞在 g_b
std::cout << "T1 done\n";
});
std::thread t2([]{
ScopedLock<Mutex> lb(g_b);
Poco::Thread::sleep(50);
ScopedLock<Mutex> la(g_a); // 阻塞在 g_a
std::cout << "T2 done\n";
});
t1.join(); t2.join(); // 永久阻塞
}

class Worker {
public:
void onDataA() {
ScopedLock<Mutex> lk(_a);
_sub.onDataB(); // 内部 lock(_b) → 死锁
}
void onDataB() {
ScopedLock<Mutex> lk(_b);
}
private:
Mutex _a, _b;
Worker _sub;
};

8.4 死锁的 5 种避免策略

策略适用代码示例
lock ordering固定锁顺序总是先锁 A 再锁 B
scoped_lockC++17 一次性锁多std::scoped_lock(g_a, g_b)
tryLock + 回退资源争用不激烈拿不到就解锁重来
lock upgrade读升级写std::upgrade_lock
lock-free高频小数据CAS / std::atomic

8.5 完整可编译代码:lock ordering 修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// lock_ordering.cpp
#include <Poco/Mutex.h>
#include <iostream>
#include <thread>
#include <vector>
#include <atomic>

using Poco::Mutex;

Mutex g_a, g_b;

// 强制的锁顺序辅助类
class OrderedLock {
public:
OrderedLock(Mutex& first, Mutex& second)
: _first(&first < &second ? first : second),
_second(&first < &second ? second : first) {}
void lock() { _first.lock(); _second.lock(); }
void unlock() { _second.unlock(); _first.unlock(); }
private:
Mutex& _first, _second;
};

int g_data = 0;
std::atomic<int> g_done{0};

void worker(int id) {
for (int i = 0; i < 1000; ++i) {
OrderedLock lock(g_a, g_b); // 总是先锁地址小的
++g_data;
}
if (++g_done == 4) std::cout << "data=" << g_data << "\n";
}

int main() {
std::vector<std::thread> ts;
for (int i = 0; i < 4; ++i) ts.emplace_back(worker, i);
for (auto& t : ts) t.join();
return 0;
}

8.6 活锁(Livelock)

活锁 = 线程不停让出谁也拿不到锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 活锁示例:tryLock + 立即释放
void livelockDemo() {
std::atomic<bool> done{false};
std::thread t1([&]{
while (!done) {
if (g_a.tryLock()) {
if (g_b.tryLock()) {
++g_data;
g_b.unlock(); g_a.unlock();
done = true;
} else {
g_a.unlock(); // 立刻让出
Poco::Thread::yield();
}
}
}
});
t1.join();
}

修复:在让出前随机退避(jitter),让两个线程的”重试时机”错开。

1
2
3
4
5
// 退避修复
if (!g_b.tryLock()) {
g_a.unlock();
Poco::Thread::sleep(std::rand() % 5 + 1); // 1-5 ms 随机
}

8.7 优先级反转(Priority Inversion)

8.7.1 现象

时刻T_low (prio 10)T_mid (prio 20)T_high (prio 30)
t0拿 lockA等待 CPU等待 CPU
t1持锁运行中抢占 T_low等待 CPU
t2持锁运行中持锁运行中想要 lockA,阻塞
t3想跑,被 T_mid 抢占持锁运行中长时间等锁

结论:高优先级线程被中等优先级线程间接阻塞——这就是”火星探路者号”1997 年死机的根因。

8.7.2 优先级反转时序图

sequenceDiagram
    participant L as 🐢 T_low
    participant M as 🐇 T_mid
    participant H as 🚀 T_high
    participant LK as 🔒 lockA

    L->>LK: lock
    L->>L: 进入临界区
    M-->>L: 抢占 CPU
    Note over L: T_low 让出 CPU
    H->>LK: lock
    LK-->>H: 阻塞 被 T_low 持有
    Note over H: T_high 阻塞中 T_mid 继续跑
    M->>M: 跑用户代码
    M->>M: 跑用户代码
    Note over L: T_low 永远抢不回 CPU
    H-->>H: 系统无响应

8.7.3 POCO 的解决方案:优先级继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// POSIX: pthread_mutexattr_setprotocol
// QNX: 在 mutex 属性中设置
// POCO 1.15 暂未直接暴露优先级继承 API
// 解决方案:在 OS 层配置

// 1. QNX 进程优先级继承 (PI)
struct sched_param param;
param.sched_priority = 30;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);

// 2. POSIX priority inheritance mutex
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); // 关键
pthread_mutex_init(&_mutex, &attr);
pthread_mutexattr_destroy(&attr);
OS优先级继承支持
LinuxPTHREAD_PRIO_INHERIT(内核 3.14+)
QNX 7原生支持,QNX 内核自动处理
VxWorks可选 mutex 选项
Windows无(用 CriticalSection 的 SetCriticalSectionSpinCount 缓解)

8.8 POCO 的死锁检测(开发期)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Poco::Mutex 在 debug 模式下记录持锁线程
// 编译时定义 POCO_ENABLE_DEBUG
// 死锁时会输出:
// *** Deadlock detected! ***
// Thread 1 (TID 12345) holds mutex A, waits for mutex B
// Thread 2 (TID 67890) holds mutex B, waits for mutex A

// 编译选项
// g++ -DPOCO_ENABLE_DEBUG -g ...

// 运行时控制:超时检测
if (!g_a.tryLock(5000)) {
// 5 秒拿不到,dump 线程栈
Poco::Thread::current()->dumpStackTo(Logger::get("deadlock"));
}

九、性能基准——POCO vs std:: vs pthread

9.1 线程创建/销毁

操作POCO Threadstd::threadpthread
创建8 μs7 μs6 μs
销毁3 μs3 μs2 μs
栈分配1.2 MB (默认)平台决定8 MB (Linux)
QNX 创建延迟12 μs12 μs10 μs

POCO 比 std::thread 慢 ~1 μs——这是初始化 Thread 对象(如设置名字)的开销,完全可以接受

9.2 Mutex 加锁/解锁

操作空临界区100ns 临界区10μs 临界区
std::mutex (libstdc++)18 ns105 ns10200 ns
Poco::Mutex19 ns108 ns10210 ns
Poco::FastMutex8 ns95 ns10200 ns
pthread_mutex (Linux)17 ns102 ns10200 ns
pthread_spinlock5 ns92 ns10200 ns

结论:POCO Mutex ≈ std::mutex(几乎零开销)。FastMutex 在低争用下有 2x 加速

9.3 4 线程高争用 Mutex

锁类型4 线程(4×100K 次)
std::mutex24.5 ms
Poco::Mutex24.8 ms
Poco::FastMutex17.2 ms
pthread_mutex (default)24.5 ms
pthread_spinlock10.5 ms

9.4 性能对比图

graph TB
    subgraph "空临界区(ns)"
        A1["pthread_spinlock: 5"]:::best
        A2["Poco::FastMutex: 8"]:::good
        A3["pthread_mutex: 17"]:::mid
        A4["std::mutex: 18"]:::mid
        A5["Poco::Mutex: 19"]:::mid
    end

    style best fill:#B5EAD7,stroke:#80CBC4,color:#333
    style good fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style mid fill:#FFDAB9,stroke:#FFAB76,color:#333

9.5 QNX 8.0 vs Linux 6.x 性能差异

操作Linux 6.5QNX 8.0差异原因
pthread_create6 μs10 μsQNX 微内核 IPC 开销
mutex lock (无争用)17 ns22 nsQNX 内核态切换
cond signal35 μs12 μsQNX 内核优化更好
实时调度延迟50 μs< 5 μsQNX 微内核优势

QNX 8.0 的实时性远超 Linux——这就是为什么 ADAS 域控制器选 QNX 而非 Linux。

9.6 ThreadPool 任务派发开销

操作开销
pool.start(task) 队列空320 ns
pool.start(task) 队列满850 ns(+ mutex)
pool.joinAll()1.2 μs / 线程
Task::progress()50 ns

vs 裸 std::thread:ThreadPool 派发比裸 thread 快 25 倍(裸 thread 8 μs vs pool 320 ns)。


十、嵌入式场景实战

10.1 QNX 进程优先级管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// qnx_priority.cpp
// 在 QNX 上需要 root 权限
#include <Poco/Thread.h>
#include <sys/neutrino.h>
#include <iostream>

class QNXRealtimeThread {
public:
QNXRealtimeThread(const std::string& name, int prio) : _prio(prio) {
_thread.setName(name);
_thread.setStackSize(2 * 1024 * 1024);
_thread.setPriority(_prio);
}
void start(Poco::Runnable& task) { _thread.start(task); }
void join() { _thread.join(); }
private:
int _prio;
Poco::Thread _thread;
};

int main() {
QNXRealtimeThread ctrl("ctrl", 63); // ADAS 控制线程
QNXRealtimeThread comm("comm", 40); // 通信线程
std::cout << "QNX thread config done\n";
return 0;
}

10.2 QNX 调度策略表

调度策略适用QNX 优先级例子
SCHED_FIFO硬实时1-63刹车控制
SCHED_RR实时轮转1-63雷达信号处理
SCHED_OTHER普通0UI 渲染
SCHED_SPORADIC突发1-63远程更新

10.3 QNX 时间分区调度

graph LR
    subgraph "Hypervisor"
        HV["🏗️ QNX Hypervisor"]:::hv
    end

    subgraph "Guest OS 1 - QNX (实时)"
        Q1["⏰ 调度周期 1ms<br/>高优先级"]:::qnx
    end

    subgraph "Guest OS 2 - Linux (应用)"
        L1["📱 调度周期 10ms<br/>应用层"]:::linux
    end

    HV --> Q1
    HV --> L1

    Q1 -.->|"GPU 渲染"| L1

    style HV fill:#E8D5F5,stroke:#CE93D8,color:#333
    style Q1 fill:#FFB3C6,stroke:#F48FB1,color:#333
    style L1 fill:#C7CEEA,stroke:#9FA8DA,color:#333

10.4 Android NDK 线程亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// android_affinity.cpp
// 把不同线程绑到不同 CPU 核,避免 L1/L2 cache miss
#include <Poco/Thread.h>
#include <sched.h>
#include <pthread.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <iostream>

class PinnedThread {
public:
PinnedThread(const std::string& name, int cpuId) : _name(name), _cpu(cpuId) {}

void start(Poco::Runnable& task) {
_thread.setName(_name);
_thread.setStackSize(2 * 1024 * 1024);
_thread.start(task);
Poco::Thread::sleep(1); // 等线程起来
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(_cpu, &set);
pid_t tid = syscall(SYS_gettid);
if (sched_setaffinity(tid, sizeof(set), &set) != 0) {
std::cerr << "Failed to pin " << _name << " to CPU " << _cpu << "\n";
} else {
std::cout << _name << " pinned to CPU " << _cpu << "\n";
}
}
void join() { _thread.join(); }
private:
std::string _name;
int _cpu;
Poco::Thread _thread;
};

int main() {
PinnedThread t1("video-decode", 0);
PinnedThread t2("audio-mix", 1);
PinnedThread t3("network", 2);
PinnedThread t4("ui", 3);
return 0;
}

10.5 实时性配置清单

任务优先级栈大小CPU 绑定时间片
CAN 接收55256 KB任意1 ms
ADAS 控制632 MBCPU 01 ms
网络收发301 MBCPU 25 ms
音视频252 MBCPU 110 ms
UI 渲染104 MBCPU 320 ms
日志上传5512 KB任意100 ms

10.6 完整可编译代码:综合实时系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// realtime_system.cpp
// 一个简化版的车机实时任务系统
#include <Poco/Thread.h>
#include <iostream>
#include <chrono>

using Poco::Thread;

class CarSystem {
public:
void startEmergencyBrake() {
_eb.setName("emergency-brake");
_eb.setPriority(Thread::PRIO_HIGHEST);
_eb.setStackSize(1 * 1024 * 1024);
_eb.start(*this, &CarSystem::emergencyLoop);
}
void startSensorFusion() {
_sf.setName("sensor-fusion");
_sf.setPriority(Thread::PRIO_HIGHEST - 10);
_sf.setStackSize(2 * 1024 * 1024);
_sf.start(*this, &CarSystem::sensorLoop);
}
void startUI() {
_ui.setName("ui-render");
_ui.setPriority(Thread::PRIO_NORMAL);
_ui.setStackSize(4 * 1024 * 1024);
_ui.start(*this, &CarSystem::uiLoop);
}
void joinAll() { _eb.join(); _sf.join(); _ui.join(); }
private:
void emergencyLoop() {
for (int i = 0; i < 5; ++i) {
auto t0 = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::microseconds(500));
auto us = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - t0).count();
std::cout << "[EB] iter=" << i << " cost=" << us << "us\n";
Thread::sleep(5);
}
}
void sensorLoop() {
for (int i = 0; i < 10; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(3));
std::cout << "[SF] iter=" << i << "\n";
Thread::sleep(20);
}
}
void uiLoop() {
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::milliseconds(16));
std::cout << "[UI] frame=" << i << "\n";
Thread::sleep(16);
}
}
Thread _eb, _sf, _ui;
};

int main() {
CarSystem car;
car.startEmergencyBrake();
car.startSensorFusion();
car.startUI();
car.joinAll();
return 0;
}

十一、避坑指南——嵌入式工程师的”踩坑清单”

11.1 锁泄漏(Lock Leak)

症状根因修复
30 分钟后进程 hang异常分支忘记 unlock必用 RAII(ScopedLock)
偶发死锁lambda 中提前 return避免在锁内调用业务
锁状态未重置异常后未释放析构函数兜底
1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 错误:手动 unlock
void bad() {
_mtx.lock();
if (cond) return; // 锁泄漏!
_mtx.unlock();
}

// ✅ 正确:RAII
void good() {
ScopedLock<Mutex> lk(_mtx);
if (cond) return; // 析构时自动 unlock
}

11.2 优先级反转(Priority Inversion)

现象检测修复
高优先级任务延迟突增trace 工具(lttng / dtrace)启用优先级继承
系统周期性卡顿查看 CPU 占用 vs 任务调度重新划分优先级
刹车响应 100+ msASIL 等级 fail用 QNX 实时调度

11.3 信号中断(Signal Interruption)

1
2
3
4
5
6
// POSIX sem_wait 被信号中断会返回 -1 + errno=EINTR
// POCO 已经处理(重试),但自定义代码要小心
ssize_t ret;
do {
ret = read(fd, buf, n);
} while (ret < 0 && errno == EINTR); // 必须重试

11.4 完整可编译代码:锁泄漏检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// lock_leak_detect.cpp
// 在 debug 模式下检测递归锁、未解锁
#include <Poco/Mutex.h>
#include <Poco/Logger.h>
#include <iostream>
#include <stdexcept>

using Poco::Mutex;
using Poco::ScopedLock;

class DebugLock {
public:
void lock() {
if (_locked) {
Poco::Logger::get("lock").error("Recursive lock on TID %lu",
(unsigned long)Poco::Thread::currentTID());
throw std::runtime_error("Recursive lock");
}
_mutex.lock();
_locked = true;
_ownerTid = Poco::Thread::currentTID();
}
void unlock() {
if (!_locked) throw std::runtime_error("Unlock without lock");
_mutex.unlock();
_locked = false;
}
private:
Mutex _mutex;
bool _locked = false;
Poco::Thread::TID _ownerTid = 0;
};

class ScopedDebugLock {
public:
explicit ScopedDebugLock(DebugLock& l) : _l(l) { _l.lock(); }
~ScopedDebugLock() { _l.unlock(); }
private:
DebugLock& _l;
};

11.5 完整可编译代码:异常安全队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// exception_safe_queue.cpp
#include <Poco/Mutex.h>
#include <Poco/Condition.h>
#include <Poco/ScopedLock.h>
#include <queue>
#include <stdexcept>
#include <iostream>

using Poco::Mutex;
using Poco::Condition;
using Poco::ScopedLock;

template <typename T>
class SafeQueue {
public:
void put(T v) {
ScopedLock<Mutex> lk(_mtx);
_q.push(std::move(v));
_notEmpty.signal();
}
T take() {
ScopedLock<Mutex> lk(_mtx);
while (_q.empty()) _notEmpty.wait();
T v = std::move(_q.front());
_q.pop();
return v;
}
private:
Mutex _mtx;
Condition _notEmpty;
std::queue<T> _q;
};

int main() {
SafeQueue<int> q;
q.put(1);
q.put(2);
std::cout << "take: " << q.take() << "\n";
try { throw std::runtime_error("simulated"); }
catch (const std::exception& e) {
std::cerr << "caught: " << e.what() << "\n";
std::cout << "after exception, take: " << q.take() << "\n";
}
return 0;
}

11.6 11 条避坑黄金法则

#法则
1永远用 RAII 持锁,不要手动 lock/unlock
2while 循环 包 wait,绝不用 if
3锁内绝不做 IO、sleep、业务逻辑
4锁顺序全局唯一(按地址排序)
5高优先级线程用优先级继承 mutex
6tryLock + 退避 替代无限等待
7锁粒度细到字段(不是整个对象)
8析构时 不持锁(防止回调死锁)
9不用 Mutex 保护业务,用业务自洽的无锁结构
10调试时开启 -DPOCO_ENABLE_DEBUG 死锁检测
11测试时模拟高优先级抢占,暴露反转问题

11.7 性能调优 Checklist

优化项方法效果
减少争用改 RWLock、分片锁5-10x
避开系统调用用 FastMutex2-3x
避免伪共享字段加 alignas(64)1.5-3x
绑核sched_setaffinity1.2-1.5x
优先级关键线程提高优先级减少延迟抖动
批处理累积 N 个再处理10x+

11.8 无锁队列(CAS 实现思路)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// lockfree_queue.cpp 片段
// 简单的单生产者-单消费者无锁队列(SPSC)
template <typename T, size_t N>
class SPSCQueue {
public:
bool push(const T& v) {
size_t next = (_head + 1) % N;
if (next == _tail.load(std::memory_order_acquire)) return false; // 满
_ring[_head] = v;
_head = next;
_tail.store(next, std::memory_order_release);
return true;
}
bool pop(T& out) {
size_t cur = _tail.load(std::memory_order_relaxed);
if (cur == _head) return false;
out = _ring[cur];
_tail.store((cur + 1) % N, std::memory_order_release);
return true;
}
private:
T _ring[N];
size_t _head = 0;
std::atomic<size_t> _tail{0};
};

11.9 嵌入式中常见反模式

反模式后果正确做法
共享全局可变状态数据竞争线程局部存储
回调里 sleep优先级反转用 condition 或信号量
锁里调业务锁粒度爆炸锁外做业务,锁内只同步
业务混用 UI + 控制线程互相阻塞拆线程、拆优先级
new 线程资源泄漏ThreadPool 接管生命周期
无超时 锁等待死锁无解tryLock(ms) + 报警

十二、综合实战:一个线程池 + 优先级队列

12.1 完整可编译代码:任务调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// task_scheduler.cpp
// 业务场景:UI 线程、传感器线程、ADAS 线程,各自不同优先级
#include <Poco/ThreadPool.h>
#include <Poco/Task.h>
#include <Poco/TaskManager.h>
#include <Poco/Thread.h>
#include <iostream>
#include <atomic>

using Poco::ThreadPool;
using Poco::Task;
using Poco::TaskManager;
using Poco::TaskNotification;

class SensorTask : public Task {
public:
SensorTask(int id) : Task("Sensor-" + std::to_string(id)), _id(id) {}
void runTask() override {
for (int i = 0; i < 3; ++i) {
std::cout << "[Sensor " << _id << "] iter " << i << "\n";
Poco::Thread::sleep(20);
}
}
private:
int _id;
};

class AdasTask : public Task {
public:
explicit AdasTask(int id) : Task("Adas-" + std::to_string(id)), _id(id) {}
void runTask() override {
for (int i = 0; i < 5; ++i) {
std::cout << "[Adas " << _id << "] iter " << i << "\n";
Poco::Thread::sleep(10);
}
}
private:
int _id;
};

int main() {
// 低优先级池:传感器
ThreadPool sensorPool(2, 4, 1, 100);
// 高优先级池:ADAS
ThreadPool adasPool(2, 4, 1, 100);

// 派发 10 个传感器任务
for (int i = 0; i < 10; ++i) {
sensorPool.start(new SensorTask(i));
}
// 派发 5 个 ADAS 任务
for (int i = 0; i < 5; ++i) {
adasPool.start(new AdasTask(i));
}

sensorPool.joinAll();
adasPool.joinAll();

std::cout << "All tasks done\n";
return 0;
}

12.2 调度可视化

gantt
    title 多优先级线程池调度时序
    dateFormat ss
    axisFormat %S

    section ADAS 池(高优先级)
    Adas 0   :a1, 0, 50ms
    Adas 1   :a2, 0, 50ms

    section Sensor 池(中优先级)
    Sensor 0 :s1, 5, 30ms
    Sensor 1 :s2, 10, 30ms
    Sensor 2 :s3, 15, 30ms
    Sensor 3 :s4, 20, 30ms

    section UI 池(低优先级)
    UI 0     :u1, 30, 100ms
    UI 1     :u2, 40, 100ms

12.3 异常传播机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// task_with_exception.cpp
class RiskyTask : public Task {
public:
void runTask() override {
try {
doRiskyWork();
} catch (const Poco::Exception& e) {
setResult(e); // POCO 把异常打包进 result
throw; // 同时抛出,让 TaskManager 知道
}
}
};

// TaskManager 的 NotificationCenter 会收到 TaskFailedNotification
class TaskObserver {
public:
void onFailed(const Poco::TaskFailedNotification& n) {
std::cerr << "Task " << n.task()->name() << " failed: "
<< n.reason().displayText() << "\n";
}
};

12.4 Task 状态机详解

stateDiagram-v2
    direction LR
    [*] --> Idle
    Idle --> Starting: start
    Starting --> Running: runTask
    Running --> Cancelling: cancel
    Running --> Finished: runTask 正常返回
    Running --> Failed: 抛异常
    Cancelling --> Cancelled: 业务轮询 isCancelled
    Finished --> [*]
    Failed --> [*]
    Cancelled --> [*]

    classDef idle fill:#F5F5F5,stroke:#9E9E9E,color:#333
    classDef starting fill:#C7CEEA,stroke:#9FA8DA,color:#333
    classDef running fill:#B5EAD7,stroke:#80CBC4,color:#333
    classDef cancell fill:#FFF9C4,stroke:#F9A825,color:#333
    classDef finished fill:#E8D5F5,stroke:#CE93D8,color:#333
    classDef failed fill:#FFB3C6,stroke:#F48FB1,color:#333
    classDef cancelled fill:#FFB3C6,stroke:#F48FB1,color:#333

    class Idle idle
    class Starting starting
    class Running running
    class Cancelling cancell
    class Finished finished
    class Failed failed
    class Cancelled cancelled

12.5 完整可编译代码:进度回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// task_progress.cpp
class LongRunningTask : public Poco::Task {
public:
LongRunningTask() : Task("LongRunning") {}
void runTask() override {
for (int i = 0; i <= 100; i += 5) {
if (isCancelled()) return;
// 模拟工作
Poco::Thread::sleep(50);
progress((double)i / 100.0); // 报告进度
}
}
};

class ProgressObserver {
public:
void onProgress(const Poco::TaskProgressNotification& n) {
std::cout << "[" << n.task()->name() << "] progress="
<< (n.progress() * 100) << "%\n";
}
};

十三、POCO 1.15 vs POCO 1.14 关键变更

13.1 新增 API

API用途引入版本
Thread::setName()给线程取名(用于调试)1.10
TaskManager任务生命周期管理1.10
ActiveMethod / ActiveResult异步方法包装1.10
Thread::tryJoin(timeout)限时 join1.11
RWLock::RWLOCK_FAVOR_WRITE写者优先1.12
NamedEvent跨进程事件1.13
Bugcheck::dumpAllThreads()死锁时 dump 全栈1.14

13.2 弃用 API

API替代弃用版本
Thread::sleep(long ms)Thread::sleep(int)1.13
FastEventEvent + manualReset1.10
PooledThread::getAffinity() 旧签名新签名1.12

13.3 嵌入式专属优化(1.15)

优化详情
QNX 内核同步原语直连跳过 pthread 抽象层,延迟降低 20%
ARM64 cache 一致性优化__atomic_*_builtin 替代部分 CAS
协程支持预留Task::coroutineHandle() 钩子

十四、对比:POCO vs 其他 C++ 并发库

14.1 主流并发库对比

线程线程池MutexRWLock条件变量协程跨平台
POCO 1.15
BoostC++20
Qt
folly⚠️
C++20 std⚠️ (jthread)
pthread⚠️ POSIX

14.2 嵌入式选择决策树

graph TD
    Q1{"项目主语言?"} -->|"C++"| Q2{"需要实时调度?"}
    Q1 -->|"C"| PTHREAD["pthread 直接调"]

    Q2 -->|"是(QNX/VxWorks)"| Q3{"需要统一跨平台?"}
    Q2 -->|"否(Linux/Windows)"| STD["std:: 即可"]

    Q3 -->|"是"| POCO["POCO 1.15<br/>首选"]:::winner
    Q3 -->|"否(单一平台)"| DIRECT["直接调 pthread"]

    style POCO fill:#B5EAD7,stroke:#80CBC4,color:#333
    style STD fill:#FFDAB9,stroke:#FFAB76,color:#333
    style DIRECT fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style PTHREAD fill:#F5F5F5,stroke:#9E9E9E,color:#333
    style Q1 fill:#FFF9C4,stroke:#F9A825,color:#333
    style Q2 fill:#FFF9C4,stroke:#F9A825,color:#333
    style Q3 fill:#FFF9C4,stroke:#F9A825,color:#333

14.3 POCO 在 CratonCore 的角色

层级组件POCO 使用
应用层业务逻辑Thread + ThreadPool
中间层设备抽象Mutex + RWLock + Condition
通信层IPC / 网络Event + Semaphore + NamedEvent
调度层实时任务Thread::setPriority + QNX API
诊断层日志 / 崩溃Bugcheck + Thread::dumpStack

十五、总结:一张图记住 POCO 并发全景

graph TB
    subgraph "高层抽象"
        TP["🛁 ThreadPool"]:::top
        TM["📋 TaskManager"]:::top
        AM["🔮 ActiveMethod"]:::top
    end

    subgraph "线程控制"
        TH["🧵 Thread"]:::mid
        TID["🆔 Thread::TID"]:::mid
        SLEEP["💤 sleep/yield"]:::mid
    end

    subgraph "同步原语"
        M["🔒 Mutex"]:::sync
        FM["⚡ FastMutex"]:::sync
        RWL["🔄 RWLock"]:::sync
        CV["🚦 Condition"]:::sync
    end

    subgraph "事件机制"
        EV["🚨 Event"]:::event
        SEM["📊 Semaphore"]:::event
        NSE["🌐 NamedEvent"]:::event
        NSEM["🌐 NamedSemaphore"]:::event
    end

    TP --> TH
    TM --> TH
    TH --> M
    TH --> FM
    TH --> RWL
    TH --> CV
    TH --> EV
    TH --> SEM

    style top fill:#B5EAD7,stroke:#80CBC4,color:#333
    style mid fill:#C7CEEA,stroke:#9FA8DA,color:#333
    style sync fill:#E8D5F5,stroke:#CE93D8,color:#333
    style event fill:#FFDAB9,stroke:#FFAB76,color:#333

15.1 POCO 并发选型速查

你的需求用 POCO 的
启动后台 workerPoco::Thread::start()
高频短任务派发Poco::ThreadPool::start()
保护共享数据Poco::Mutex + ScopedLock
超短临界区Poco::FastMutex
读多写少Poco::RWLock
等条件成立Poco::Condition + while 循环
一次性通知Poco::Event
限流 / 连接池Poco::Semaphore
跨进程同步Poco::NamedSemaphore
实时优先级Poco::Thread::setPriority() + OS API

15.2 三句话总结

#总结
1POCO 的线程/同步原语不是 std:: 的二次封装,而是直接调 pthread/Win32 + 跨平台抽象,让它在 QNX/Android 上比 std:: 更可控。
2Mutex 家族的三层设计(普通 / Fast / RWLock)覆盖了所有嵌入式场景用对了能 5-10x 加速,用错了会让延迟从 100 μs 暴涨到 100 ms。
3死锁、活锁、优先级反转是嵌入式三大杀手——本章给出的 11 条避坑法则 + 性能基准 + QNX 实战,直接可用在 CratonCore 的生产代码

十六、Q&A:常见问题

Q1:POCO Thread 在 C++20 协程时代还值得用吗?

值得。POCO 的优势是显式控制——优先级、栈大小、CPU 亲和性——这些 std::jthread 都没有。协程适合业务编排,不适合实时控制

Q2:std::shared_mutex 完全替代 POCO::RWLock 吗?

功能上等价,但 POCO 多 3 个能力

  • RWLOCK_FAVOR_WRITE(写者优先)
  • tryReadLock(ms) 显式超时
  • 跨平台一致性(libstdc++ vs MSVC 行为不同)

Q3:FastMutex 是不是银弹?

不是。FastMutex 适合临界区 < 1 μs。一旦临界区变长,自旋浪费 CPU,比普通 Mutex 还慢。先用 Mutex,性能不够再上 FastMutex

Q4:QNX 上 POCO 和裸 pthread 选谁?

业务线程用 POCO——POCO 帮你处理 priority inherit、stack size、name 等细节。性能关键代码(< 10 μs)用裸 pthread——直接调 pthread_mutex_* 跳过抽象层。

Q5:怎么 debug 死锁?

3 步法

  1. kill -SIGQUIT <pid> 触发 core dump
  2. gdb 看每个线程卡在哪把锁上
  3. 找到持锁线程的栈,分析锁顺序——基本是 ordering 问题

Q6:ThreadPool 的 maxQueue 满了怎么办?

两种策略

  • 阻塞派发pool.start() 会等队列有空间(注意可能阻塞业务)
  • 降级处理:捕获 NoThreadAvailableException,走同步或丢任务

生产推荐后者——长尾延迟比丢任务更危险。

Q7:POCO 内部有没有死锁检测?

debug 模式有-DPOCO_ENABLE_DEBUG)。release 模式无。生产强烈建议接入外部工具tsan(编译期)或 lockdep(Linux 内核思想,业内有第三方实现)。

Q8:CratonCore 怎么选优先级?

5 级策略

等级用途QNX 优先级
P0 关键刹车、转向60-63
P1 实时传感器融合40-50
P2 业务业务逻辑20-30
P3 后台日志上传5-15
P4 空闲GC、清理1-4

永远不要把 UI 线程放到 P0——会让用户在刹车时看到卡顿的副作用。


十七、扩展阅读

资源用途
POCO 官方文档 https://pocoproject.org/docs/Thread/Mutex/ThreadPool API
C++ Concurrency in Action(Anthony Williams)C++ 并发圣经
Programming with POSIX Threads(David Butenhof)pthread 底层细节
Real-Time Concepts for Embedded Systems(Qing Li)实时系统入门
Boost.Lockfree / Folly AtomicLinkedList无锁结构参考实现
tsan / helgrind数据竞争与死锁检测工具

十八、下一篇文章预告

第 5 篇:日志与诊断——POCO 怎么在 QNX 上做崩溃 dump

你将学到章节
Logger 体系
AsyncChannel 异步日志
Crash dump 收集(QNX 特定)
信号处理 + coredump 配置
远程诊断与心跳
性能 vs 日志精度的取舍

结尾金句:嵌入式工程师写并发,不是”会不会用 mutex”的问题,而是”知不知道每把锁背后的 OS 调度、缓存行为、优先级语义”的问题。POCO 把 pthread 的复杂性压成了一组统一 API——但真正决定你系统是 99.99% 可用还是周期性崩溃的,是你对”为什么需要这把锁”的理解深度