互斥量的工作原理
如果有多个线程同时访问临界资源,互斥量保证临界资源是被串行访问的。某个线程访问临界资源,线程首先给临界资源加锁,加锁好其他资源无法访问,其他线程只能等当前线程解锁后才能使用。
自旋锁
操作系统提供的API:pthread_spinlock_t
自旋锁的工作原理与互斥量一摸一样,也是在使用前加锁,使用完解锁保证串行访问。
自旋锁也是一种多线程同步的变量,使用了自旋锁的线程会反复的检查锁变量是否可用。如果不可用,就会一直循环的检查。所以自旋锁不会让出CPU,是一种忙等待状态。是死循环等待锁被释放。
自旋锁的优缺点
- 避免了进程或线程的上下文开销,如果锁占用时间不长,使用自旋锁代价很小
- 操作系统内部很多地方使用自旋锁
- 不适合在单核CPU使用,因为他等待时不会释放CPU,而是死循环等待。
生产者消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
pthread_spinlock_t spin_lock;
int num = 0;
void *producer(void *) {
int times = 10000000;
while (times--) {
pthread_spin_lock(&spin_lock);
num += 1;
pthread_spin_unlock(&spin_lock);
}
}
void *comsumer(void *) {
int times = 10000000;
while (times--) {
pthread_spin_lock(&spin_lock);
num -= 1;
pthread_spin_unlock(&spin_lock);
}
}
int main() {
pthread_spin_init(&spin_lock, 0);
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, &producer, NULL);
pthread_create(&thread2, NULL, &comsumer, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("临界资源:%d",num)
return 0;
}
不会让出cpu的demo,让消费者sleep10秒,生产者的自旋锁将会循环等待,查看此时的进程CPU是否为100%。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <vector>
pthread_spinlock_t spin_lock;
int num = 0;
void *producer(void *) {
int times = 10000000;
while (times--) {
pthread_spin_lock(&spin_lock);
num += 1;
pthread_spin_unlock(&spin_lock);
}
}
void *comsumer(void *) {
int times = 10000000;
while (times--) {
pthread_spin_lock(&spin_lock);
num -= 1;
sleep(10);
pthread_spin_unlock(&spin_lock);
}
}
int main() {
pthread_spin_init(&spin_lock, 0);
pthread_t thread1, thread2;
pthread_create(&thread1, NULL, &producer, NULL);
pthread_create(&thread2, NULL, &comsumer, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("临界资源: %d\n", num);
return 0;
}
备注:
代码中使用的是POSIX标准提供的编程接口,mac是没有的,所以在Mac下测试自旋锁是会出现报错的。 下图是Linux下已进程形式测试自旋锁cpp,可以看到spin_lock所使用的进程接近百分之百,说明自旋锁在运行的时候并不会让出CPU。
图片来源慕课网:《变成必备基础》