在之前的线程学习中,用到的锁都是挂起等待锁,如果申请不到锁,那就会在锁中等待;
自旋锁则不大相似
[TOC]
1.自旋锁
1.1 概念
自旋锁是一个轮询检测锁,其检测机制并不是挂起等待,而是不断的询问锁有没有空闲;类似于一个while(1)循环的trylock()
由于其需要不断的轮询检测,所以会占用一定的CPU资源;如果线程较多,就容易给cpu造成负荷。
但是自旋锁无须唤醒挂起等待状态的线程,其消耗较小。
总结一下:
- 自旋锁适合竞争不激烈,且临界区较小(呆的时间短)的情况(因为这种情况使用互斥锁时,用户态和内核态之间的切换耗时可能都远大于临界区耗时);
- 自旋锁不适合大量线程,临界区长的情况;
自旋锁的优缺点反过来,便是挂起等待锁的优缺点了。我们要根据不同场景,正确选择锁的类型
1.2 接口
相关接口和mutex都是很相似的,这里就不演示使用的效果了
1.2.1 pthread_spin_init/destroy
| 12
 3
 4
 
 | #include <pthread.h>
 int pthread_spin_destroy(pthread_spinlock_t *lock);
 int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
 
 | 
1.2.2 pthread_spin_lock
自旋锁同样有trylock接口,用于判断锁是否就绪
| 12
 3
 
 | #include <pthread.h>int pthread_spin_lock(pthread_spinlock_t *lock);
 int pthread_spin_trylock(pthread_spinlock_t *lock);
 
 | 
1.2.3 pthread_spin_unlock
| 12
 
 | #include <pthread.h>int pthread_spin_unlock(pthread_spinlock_t *lock);
 
 | 
2.读写锁
有的时候,我们会有一份config配置文件,这个配置文件会有非常多的线程进行读取,但是很少进行修改和写入。
此时我们如果对配置文件的读取进行加锁,就容易导致效率问题,众多线程不断被阻塞,产生性能损失。
此时,就可以用一个专门的读写锁,对读者和写者加不同的锁,在提升读取性能的同时,保证写入不冲突
2.1 读者写者的关系
写着和写着之间不用多说,肯定是互斥关系;
读者和写者之间也是互斥关系,在写入的时候,不能进行读取,否则容易出现二义性问题;
- 写者写入了一个a,线程甲来读取,得到的结果是a
- 写者继续写入了b,线程乙来读取,得到的结果是ab
这是因为写者的写入还没有完成,导致甲乙读者会获取到完全不同的结果,这是不对的;
读者和读者之间没有关系,因为读者并不会修改数据,也不会取走数据,其存在对临界资源没有影响。
2.2 接口
2.2.1 init/destroy
读写锁只需要初始一个锁就行了,无须对读者写者初始化两个不同的锁
| 12
 3
 4
 5
 
 | #include <pthread.h>
 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
 const pthread_rwlockattr_t *restrict attr);
 
 | 
2.2.2 读者加锁
读写锁的读者锁/写者锁是分开的,我们要针对不同的线程调用不同的锁
| 12
 3
 4
 
 | #include <pthread.h>
 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
 
 | 
但是解锁的接口是一样的
| 12
 
 | #include <pthread.h>int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
 
 | 
2.2.3 写者加锁
| 12
 3
 
 | #include <pthread.h>int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
 
 | 
2.2.4 设置锁的属性
参考 PTHREAD_RWLOCKATTR_SETKIND_NP - Linux手册页
读写锁可以允许我们设置是读者优先还是写者优先。如果采用默认的属性,可能会出现读者一直在读,写者没有办法写入的情况(打印错位是正常情况)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | [muxue@bt-7274:~/git/linux/code/23-01-20 rwlock]$ ./testreader [140484801697536]
 reader [140484801697536] 0
 reader [140484793304832]
 reader [reader [140484784912128140484793304832]
 reader [140484784912128] 0
 ] 0
 reader [140484759734016]
 reader [140484759734016] 0
 reader [140484776519424]
 
 
 | 
此时就出现了写者饥饿问题,写者无法访问临界资源,饿死了😂
我们可以根据自己的需求进行设置读写锁的属性
| 12
 3
 4
 5
 6
 7
 8
 
 | int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref); 
 
 
 
 
 
 
 
 | 
下面是一个示例
| 12
 3
 4
 5
 
 | pthread_rwlock_t rwlock;pthread_rwlockattr_t attr;
 pthread_rwlockattr_init(&attr);
 pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
 pthread_rwlock_init(&rwlock, &attr);
 
 | 
2.3 代码
| 12
 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
 
 | #include <iostream>#include <unistd.h>
 #include <pthread.h>
 using namespace std;
 
 volatile int board = 0;
 
 pthread_rwlock_t rw;
 
 void *reader(void* args)
 {
 const char *name = static_cast<const char *>(args);
 cout << "reader ["<<pthread_self() <<"]"<< endl;
 
 while(true)
 {
 pthread_rwlock_rdlock(&rw);
 cout << "reader ["<<pthread_self() <<"] " << board << endl;
 pthread_rwlock_unlock(&rw);
 usleep(110);
 }
 }
 
 void *writer(void *args)
 {
 const char *name = static_cast<const char *>(args);
 
 while(true)
 {
 pthread_rwlock_wrlock(&rw);
 board++;
 cout << "writer [" << pthread_self() <<"]"<< endl;
 pthread_rwlock_unlock(&rw);
 usleep(100);
 }
 }
 
 int main()
 {
 pthread_rwlock_init(&rw, nullptr);
 pthread_t r1,r2,r3,r4,r5,r6, w1,w2;
 pthread_create(&r1, nullptr, reader, (void*)"reader");
 pthread_create(&r2, nullptr, reader, (void*)"reader");
 pthread_create(&r3, nullptr, reader, (void*)"reader");
 pthread_create(&r4, nullptr, reader, (void*)"reader");
 pthread_create(&r5, nullptr, reader, (void*)"reader");
 pthread_create(&r6, nullptr, reader, (void*)"reader");
 pthread_create(&w1, nullptr, writer, (void*)"writer");
 pthread_create(&w2, nullptr, writer, (void*)"writer");
 
 
 pthread_join(r1, nullptr);
 pthread_join(r2, nullptr);
 pthread_join(r3, nullptr);
 pthread_join(r4, nullptr);
 pthread_join(r5, nullptr);
 pthread_join(r6, nullptr);
 pthread_join(w1, nullptr);
 pthread_join(w2, nullptr);
 
 pthread_rwlock_destroy(&rw);
 return 0;
 }
 
 | 
运行结果如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | reader [140067523458816] 7086reader [140067548636928] 7086
 writer [140067515066112]
 writer [140067506673408]
 reader [140067540244224] 7088
 reader [140067565422336] 7088
 reader [140067557029632] 7088
 reader [140067531851520] 7088
 reader [140067523458816] 7088
 writer [140067515066112]
 writer [140067506673408]
 reader [140067548636928] 7090
 writer [140067515066112]
 reader [140067523458816] 7091
 writer [140067506673408]
 reader [140067548636928] 7092
 reader [140067557029632] 7092
 
 | 
可以看到读者的线程较多,且能够正确读取数据。
如果在读者的whiile中加上sleep(10)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | void *reader(void* args){
 const char *name = static_cast<const char *>(args);
 cout << "reader ["<<pthread_self() <<"]"<< endl;
 while(true)
 {
 pthread_rwlock_rdlock(&rw);
 cout << "reader ["<<pthread_self() <<"] " << board << endl;
 sleep(10);
 pthread_rwlock_unlock(&rw);
 usleep(110);
 }
 }
 
 | 
能够看到多个读者之间不冲突,不会出现读者A申请锁后,读者B就无法访问临界区的情况。如果是互斥锁,读者A申请之后进入休眠,B就无法申请该锁。
