在上一篇文章利用汇编语言实现了一个简单的互斥锁。但是它与 mutex 互斥量有着本质的不同:

  • 如果一个线程企图获取已加锁状态的互斥量,会立即进入阻塞,即主动让出 cpu.
  • 我们自己实现的互斥锁,如果企图获取已加锁状态的 lock 变量,会进入忙等状态而不会让出 cpu.

1. pthread 自旋锁

实际上,pthread 中提供的自旋锁的原理,也就相当于上一篇实验中实现的锁。

注意:pthread 中的自旋锁(pthread_spinlock)不同于内核中的自旋锁(spin_lock)。

所以,自旋锁与互斥量最不同的一个重要特性:它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等,即不停在消耗,执行循环。

2. 自旋锁的数据类型和相关函数

pthread 提供的自旋锁的数据类型是 pthread_spinlock_t.

  • 初始化和销毁
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

// pshared = PTHREAD_PROCESS_PRIVATE: 只能被同进程内的线程访问
// pshared = PTHREAD_PROCESS_SHARED: 可以被不同进程内的线程共享
  • 加锁解释函数
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

3. 适用场景

  • 临界区的代码短小,没有任何阻塞类的函数。
  • 多核处理器

什么要这样做:

  1. 对于单核或者多核:如果临界区有阻塞,会导致线程被切换到新线程,如果新线程也尝试获取锁,会一直自旋(忙等),直到时间片耗尽,造成不必要的 CPU 浪费。
  2. 对于多核没有阻塞的情况:一次只能运行一个线程,其中一个 CPU 上的线程进入临界区,另一个 CPU 上的线程尝试获取锁会自旋,因为它不会阻塞,所以只要稍稍等一下下就能进入临界区,对于多核 CPU 来说,会提高并发率。对于单核 CPU 来说,通常没有什么问题,如果临界区有阻塞或者时间片耗尽产生调度就会浪费 CPU。

4. 总结

  • 理解自旋锁的运行原理
  • 理解 pthread 自旋锁的适用场景
  • 知乎有关于自旋锁的性能测试和解释,传送门。

练习:用 pthread_spinlock_t 修改上一篇文章中的实验。