操作系统自旋锁和互斥锁的实现原理

0 介绍

对多线程编程有过了解的朋友一定知道锁的概念,它的作用是为了保证临界区的代码在多线程下能够正常工作,也就是说,锁将保证共享资源在任意时刻只能有一个使用者。
过去博主我也只是知道锁是如何使用的,对其内在原理并不了解。今天看看了看清华大学陈渝老师的网课,对锁的实现有了一定认识,特此记录。

1 原子操作

理解锁的实现,首先要理解Cpu的原子操作。现代的cpu都提供一些特殊的原子操作,他们执行的时候将不能被中断,如CAS(compare and swap)、Test-And-Set。这两种操作均可以实现锁,下面讲讲Test-And-Set是如何实现的。
这个操作并不复杂,对于target,如果它是false,将其修改为true,但返回的值为rv;如果是true,什么也没有发生。这个简单的操作是原子的,下面看看如何由它来实现锁。

boolbean TestAndSet(boolbead *target)
{
	boolbead *rv = *target;
	*target = true;
	return *rv;
}

3 自旋锁

所谓自旋锁,百科定义如下:

自选锁是为实现保护共享资源而提出一种锁机制。自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。

通俗来说,如果当前锁被占用,调用者将阻塞在原地,这是与互斥锁不同的地方。对于锁来说,我们一般考虑它的上锁(lock)与解锁(unlock)操作。如下面的c语言伪代码所示,当需要在临界区前上锁时,调用锁的Acquire方法即可。如果当前已经上锁了,value为1,则后来的调用者将阻塞在while处;如果未上锁,将不会阻塞在while,同时test-and-set会将value置为1,防止未解锁前后来的调用者执行临界区代码。解锁只需要将value置为0即可。

class Lock
{
    
    
int value = 0;

void Acquire()
{
    
    
	while(test-and-set(&value))
		;
}

void Release()
{
    
    
	value = 0;	
}

};

自旋锁也有一些缺点,首先它是一个忙等待锁,在while处阻塞的时候会消耗cpu资源直到阻塞结束。第二个,自旋锁的使用可能会造成死锁。比如一个低优先级的进程在占用临界区资源时,来了一个高优先级进程夺走了cpu,然后高优先级进程也在acquire锁,但是锁却在低优先级进程手里,这时死锁就发生了。

4 互斥锁

互斥锁与自旋锁不同的地方在于,当调用者发现锁已经被使用的时候,会使当前进程睡眠,加入到等待队列,然后使用调度机制(schedule函数)执行其他进程,而不是原地等待。解锁时拿出队头睡眠的进程唤醒即可。其余和自旋锁一致。

class Lock
{
    
    
int value = 0;
WaitQueue q;

void Acquire()
{
    
    
	while(test-and-set(&value))
	{
    
    
		add this TCB to the waitqueue q;
		schedule();
	}
}

void Release()
{
    
    
	value = 0;	
	remove thread t from q;
	wake(t);
}

};

猜你喜欢

转载自blog.csdn.net/MoonWisher_liang/article/details/110671123