rcu最开始是从Linux kernel里面实现的,因为实现了reader无锁编程,能够大幅提高性能。
urcu(userspace-rcu),顾名思义就是用户态的开源的rcu实现库。通过urcu可以实现reader无锁,大大提高应用程序性能。
urcu库使用相对比较简单,但是使用不当也会造成各种crash问题。我们项目中使用了urcu开源库,在调试中遇到并解决了一些问题,下面在使用中的一些总结。希望能有参考意义。
刚开始调试时,我们希望问题尽早暴露出来并修复。建议使用开启debug功能的urcu lib库,待调试OK后,再使用关闭debug功能的urcu lib库。
.configure命令添加 --enable-rcu-debug 项以开启debug功能。
[root@localhost]# ./bootstrap
[root@localhost]# ./configure --disable-shared --enable-rcu-debug
...//省略部分
Uspace-RCU 0.11.0
Features
Target architecture: x86_64
SMP support: yes
Memory fence instructions: yes
Futex support: yes
Thread Local Storage (TLS): __thread
clock_gettime(): yes
Require membarrier: no
Internal debugging: yes
Lock-free hash table iterator debugging: no
Multi-flavor support: yes
Install directories
Binaries: /usr/local/bin
Libraries: /usr/local/lib
[root@localhost]# make && make install
使用liburcu.a编译你的应用程序,然后运行调试。
增加了debug功能,任何线程,只要没有reg urcu thread,在调用 rcureadlock时,会触发assert 。crash栈明显可以看到原因是线程未注册urcu thread。这样可以更早的发现问题线程,避免直接使用正式urcu库,在运行中出现各种诡异的错误问题。
urcu库提供了不同特性的urcu实现。
对于常用的include
从下面代码可以看到
// include/urcu.h
#if !defined(RCU_MEMBARRIER) && !defined(RCU_SIGNAL) && !defined(RCU_MB)
#define RCU_MEMBARRIER
#endif
#ifdef RCU_MEMBARRIER
#include
#elif defined(RCU_SIGNAL)
#include
#elif defined(RCU_MB)
#include
#else
#error "Unknown urcu flavor"
#endif
urcu默认使用RCU_MEMBARRIER。如果我们想要使用RCU_MB,需要在编译选项中增加 -DRCU_MB 来启用。
启用RCU_MB后,连接时需要链接liburcu-mb.a
不同特性的urcu库在性能不尽相同,可以根据实际需要选择合适的库。
urcu要求每个会使用urcu功能的线程,在线程开始时 调用 urcu reg thread API, 在结束时调用 urcu un thread API。
实际编码中经常会忘记reg/unreg urcu thread,导致一些不可预期的问题。这个问题可以通过使用debug版本来迅速暴露出来。在debug版本,如果有线程没有注册urcu thread,在使用urcu API时,会被assert捕捉到,将问题迅速暴露出来。
线程结束时未注销urcu thread,导致线程节点内存已经释放了,但是节点地址还挂在urcu 链表上。在某个时刻就会触发错误的地址引用,导致crash。
基本上大多数问题都出在这两个地方,在编码中需要多加注意。