支持优先级继承的OS中,类内锁在多线程实时代码中是否可取?
当高优先级实时数据处理线程读取参数、低优先级线程写入参数(或反之)时,会出现数据竞争问题。常见的解决思路是将参数声明为std::atomic,或者用互斥锁(mutex)保护读写操作。
有观点建议尽量避免使用非无锁原子操作和互斥锁来处理这类数据竞争,原因包括:
- 实时线程可能因获取锁进入等待状态,原本需要严格满足截止时间的代码,其执行时间突然要纳入低优先级线程锁保护区内所有代码的耗时考量。
- 若低优先级线程锁保护区内包含内存分配/释放、系统调用等执行时间无界的操作,实时线程的截止时间将无法得到保障。
- 低优先级线程可能被优先级介于它和实时线程之间的线程中断,引发优先级反转。
如果操作系统支持优先级继承,上述最后一点可以忽略。那么问题来了:即便所有锁操作都封装在类内部(如下示例代码),是否仍应避免使用锁?
#include <mutex> class RealTimeProcessor { public: struct Parameter { // some non atomic data }; void processData() { Parameter p; mtx.lock(); p = m_param; mtx.unlock(); // do some processing with the local copy of the parameter... } void setParameter(Parameter param) { mtx.lock(); m_param = param; mtx.unlock(); } private: Parameter m_param; std::mutex mtx; };
上述代码中,processData()由实时线程调用,setParameter()由低优先级线程调用。提问者认为,由于锁操作都在类内部完成,可确保锁保护区域内无执行时间无界的调用,且最坏锁等待场景下实时处理仍能满足截止时间,想确认是否仍需避免使用锁。
回答
如果能严格保证锁保护区域的执行时间有界且足够短,同时操作系统支持优先级继承以避免优先级反转,那么使用互斥锁是可行的,但仍需谨慎评估潜在风险:
锁持有时间必须绝对可控
你的代码中锁保护的仅仅是参数的赋值操作,只要Parameter的拷贝/赋值是纯内存拷贝(无动态内存分配、无系统调用、无复杂计算),锁的持有时间就是固定且极短的,不会对实时线程的截止时间造成威胁。但要注意,后续若有人修改Parameter的实现(比如添加包含耗时逻辑的自定义赋值运算符),就会打破这个前提,因此需要通过代码规范、严格封装或静态检查来保障这一点。优先级继承的局限性
优先级继承能解决直接的优先级反转,但如果低优先级线程持有锁时被同优先级线程抢占(比如基于时间片的调度),实时线程仍会有短暂等待。不过在实时系统中,这种等待时间由系统调度粒度决定,通常是可预测的,只要调度粒度远小于实时任务的截止时间,就不会有问题。无锁方案的优势仍不可忽视
如果Parameter的结构适合拆分为多个std::atomic成员,或者可以用无锁队列传递参数副本,无锁方案能完全避免锁带来的上下文切换和等待开销,理论上比锁更可靠。但无锁实现的复杂度更高,需要仔细处理内存可见性和操作原子性问题。
总结
如果你的场景能确保锁持有时间极短且可控,同时操作系统的实时特性(如确定的调度粒度、优先级继承)足够支撑,那么使用锁是可以接受的。但如果追求极致的确定性和可靠性,无锁方案仍然是更优的选择。
内容的提问来源于stack exchange,提问作者sbh

