Read-Write Spinlock与Spinlock是极其相似的。自旋锁是一种single-holder lock,如果某个task尝试获取自旋锁,但自旋锁暂时不可得,那么该task便会自旋等待。读写自旋锁与自旋锁是类似的,只是区分了读与写两种操作的自旋锁。
-
那么为何还需要有读写自旋锁?直接使用自旋锁不可以么?
假如有10个线程,都需要访问同一个全局变量X,可以使用自旋锁进行互斥;但如果这10个线程中,只有线程1是write该变量X,其余的9个线程都是read该变量X,这时候还使用自旋锁,每次只能一个线程进行访问(读或写)该变量X,效率就十分低下了。假如线程2拿到了自旋锁进行读取,这时候线程3至线程10就没法进行读取,而此时变量X是不会改变的,因为线程1没法拿到自旋锁进行写操作。假如是读写锁呢,线程2拿到read-lock,线程3至线程10还可以获取read-lock进行读取,因为此时并没有write操作,所以可以线程2至线程10可以并行执行。
Read Write Spinlock
工作机制
- 当临界区(critical section)中没有人时,读者与写者都可以凭借获取对应的读锁或写锁进入,但一次只能一个人进入临界区。
- 如果reader进入临界区(拿了read-lock),此时其他reader可以进入临界区,但writer不能进入临界区,必须等所有的reader完成读取操作退出临界区后才能进入。
- 如果writer进入了临界区,此时其他reader与writer都不能再进入临界区。
- 如果临界区中有一个或者多个reader,此时writer是不能进入临界区的,并且writer不能阻止subsequent的reader进入临界区,这意味这读写自旋锁其实是给了读者更多的机会,并没有给写者机会,如果需要给写者更多的机会,使用seqlock机制。
使用场景
如果只需要读取数据,则获取read spinlock。
如果要写数据,则获取write spinlock。
读写自旋锁适用于比较明显区分读者与写者且读者明显多于写者的场景中。
initial rwlock
static
include/linux/rwlock_types.h
中定义DEFINE_RWLOCK()
宏可以静态定义读写自旋锁。
#ifdef CONFIG_DEBUG_SPINLOCK
#define __RW_LOCK_UNLOCKED(lockname) \
(rwlock_t) {
.raw_lock = __ARCH_RW_LOCK_UNLOCKED, \
.magic = RWLOCK_MAGIC, \
.owner = SPINLOCK_OWNER_INIT, \
.owner_cpu = -1, \
RW_DEP_MAP_INIT(lockname) }
#else
#define __RW_LOCK_UNLOCKED(lockname) \
(rwlock_t) {
.raw_lock = __ARCH_RW_LOCK_UNLOCKED, \
RW_DEP_MAP_INIT(lockname) }
#endif
#define DEFINE_RWLOCK(x) rwlock_t x = __RW_LOCK_UNLOCKED(x)
dynamic
动态初始化一个读写自旋锁变量,比如:
rwlock_t test_rwlock;
rwlock_init(&test_rwlock);
在include/linux/rwlock.h
中定义
#ifdef CONFIG_DEBUG_SPINLOCK
extern void __rwlock_init(rwlock_t *lock, const char *name,
struct lock_class_key *key);
# define rwlock_init(lock) \
do {
\
static struct lock_class_key __key; \
\
__rwlock_init((lock), #lock, &__key); \
} while (0)
#else
# define rwlock_init(lock) \
do {
*(lock) = __RW_LOCK_UNLOCKED(lock); }