Home userspace lock introduce
Post
Cancel

userspace lock introduce

锁介绍

多线程下为了保护共享数据,需要同步机制。

1
2
互斥:多线程中互斥是指多个线程访问同一资源时同时只允许一个线程对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的;
同步:多线程同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

reference:https://zhuanlan.zhihu.com/p/608618791

互斥锁

介绍

在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。为了同一时刻只允许一个任务访问资源,需要用互斥锁对资源进行保护。互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

锁特性

  • 原子性:互斥锁是一个原子操作,操作系统保证如果一个线程锁定了一个互斥锁,那么其他线程在同一时间不会成功锁定这个互斥锁

  • 唯一性:如果一个线程锁定了一个互斥锁,在它解除锁之前,其他线程不可以锁定这个互斥锁

  • 非忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将被挂起且不占用任何CPU资源,直到第一个线程解除对这个互斥锁的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥锁

    PS:对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放

使用

1
2
3
4
5
6
7
8
9
10
11
#include <pthread.h>
pthread_mutex_t mutex; //锁定义
pthread_mutexattr_t mattr; //属性
//glibc sysdeps/nptl/pthread.h
pthread_mutexattr_init (&mattr);
pthread_mutexattr_settype (&mattr, PTHREAD_MUTEX_ERRORCHECK);

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);//锁初始化
pthread_mutex_lock(pthread_mutex_t *); //上锁
pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_unlock(pthread_mutex_t *mutex); //解锁

自旋锁

介绍

自旋锁与互斥锁功能相同,唯一不同的就是互斥锁阻塞后休眠不占用CPU,而自旋锁阻塞后不会让出CPU,会一直忙等待,直到得到锁

锁特性

  • 忙等
  • 自旋锁在用户态较少用,而在内核态使用的比较多
  • 自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间 //如果临界区时间大于线程切换时间,那就得不偿失,会浪费大量的CPU资源

使用

自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_lock()/pthread_mutex_unlock()中mutex换成spin,如:pthread_spin_init()

读写锁(pthread_rwlock)

介绍

与前面介绍的互斥量,信号量类似,用于多线程/进程间同步控制,但与它们的不同之处在于,读写锁可以区分读加锁和写加锁,也就是说一把锁有两种不同的加锁方式,那么对于两种加锁方式下的并发控制也是不同。

  • 读写锁允许更高的并行性,也叫共享互斥锁。互斥量要么是加锁状态,要么就是解锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态:读模式下加锁状态、写模式加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁,即允许多个线程读但只允许一个线程写。
  • 当读操作较多,写操作较少时,可用读写锁提高线程读并发性

锁特性

  • 读写锁,有两种加锁方式:

    1
    2
    
    1)加读锁,也就是共享锁,多个并发可以同步加此种锁,同时可以访问临界区。
    2)加写锁,也就是独占锁,只有一个并发可以成功的加写锁,此时其它并发既不能加写锁,也不能加读锁,这就是独占的意义。
    

使用

1
2
3
4
5
6
7
8
9
    #include <pthread.h>
    int phtread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); //rwlock:读写锁,attr:读写锁属性
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    /** 加读锁 */
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    /** 加写锁 */
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    /** 释放锁 */
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

信号量

介绍

信号量用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞。

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <semaphore.h>
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 信号量P操作(减 1)
int sem_wait(sem_t *sem);
// 以非阻塞的方式来对信号量进行减1操作
int sem_trywait(sem_t *sem);
// 信号量V操作(加 1)
int sem_post(sem_t *sem);
// 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);
// 销毁信号量
int sem_destroy(sem_t *sem);

条件变量

介绍

  • 条件变量用来阻塞一个线程,直到条件发生。通常条件变量和互斥锁同时使用。条件变量使线程可以睡眠等待某种条件满足。条件变量是利用线程间共享的全局变量进行同步的一种机制。
  • 条件变量的逻辑:一个线程挂起去等待条件变量的条件成立,而另一个线程使条件成立。

使用

线程在改变条件状态之前先锁住互斥量。如果条件为假,线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步

1
2
3
pthread_cond_t 
pthread_cond_wait
pthread_cond_signal

RCU锁(userspace-rcu)

This post is licensed under CC BY 4.0 by the author.