一、自旋锁
自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制。
自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。
但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。自旋锁在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。
自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。
自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。
二、自旋锁的缺陷
自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:
(1)死锁。试图递归地获得自旋锁必然会引起死锁:例如递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,就不会释放此自旋锁。所以,在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。
(2)过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了。因此,一般自旋锁实现会有一个参数限定最多持续尝试次数。超出后,自旋锁放弃当前time slice,等下一次机会。
由此可见,自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#define MAXLEN 1024
pthread_spinlock_t mutex;
char buf[MAXLEN] = "";
void* read_data_one()
{
pthread_spin_lock(&mutex);
int i = 0;
for(i = 0; i < 5; i++)
{
printf("read one\n");
sleep(1);
}
pthread_spin_unlock(&mutex);
pthread_exit(NULL);
return;
}
void* write_data_one()
{
pthread_spin_lock(&mutex);
int i;
for(i = 0; i < 5; i++)
{
printf("write one\n");
strcat(buf, "hello write data one\n");
sleep(1);
}
pthread_spin_unlock(&mutex);
pthread_exit(NULL);
return;
}
int main()
{
pthread_t t1;
pthread_t t3;
pthread_spin_init(&mutex, 0);
pthread_create(&t1, NULL, write_data_one, NULL);
pthread_create(&t3, NULL, read_data_one, NULL);
pthread_join(t1, NULL);
pthread_join(t3, NULL);
printf("buf:\n%s\n", buf);
pthread_spin_destroy(&mutex);
while(1);
return;
}
使用方法与互斥锁、读写锁类似。
根据其他博文中的结论:
1、自旋锁适用于竞争不激烈、线程数较少,并且临界区小的情况。
2、互斥锁竞争激烈的情况下,引发大量的上下文切换。所以,由于竞争的存在,并不是线程愈多,效率越高。
3、保险情况下,使用线程锁,因为,极端情况下,自旋锁不停的自旋,浪费CPU,影响效率。
参考: