进程/线程互斥的实现方式

首先介绍几个相关概念。

临界资源:在同一时刻,只能被一个进程/线程所使用的资源。
临界区:访问临界资源的代码段。因此同一时刻只能有一个进程/线程进入临 界区。
进入区:进入临界区之前的代码段,一般用来做加锁操作。
退出区:临界区执行之后的代码段,一般用来释放锁。

下面介绍几种进程/线程同步的实现方式

1.硬件方式-关中断。在代码执行处于进入区时,执行关中断指令,在退出区时执行开中断指令。CPU忽略中断信号,自然就不会发生进程/线程调度,保证了单核情况下只有一个进程/线程访问临界资源。然而只有执行了关中断指令的CPU会忽略中断信号,其他CPU核心仍然会不断发生进程/线程调度,因此关中断无法在多核情况下保证对于临界资源的互斥访问。

2.软件方式-皮特森算法。
在这里插入图片描述皮特森算法是一种纯软件实现方法,并且能够保证在多核情况下对于临界资源的互斥访问。但是皮特森算法要求访存操作严格按照顺序执行,然而现代CPU为了更好的性能往往允许访存操作乱序执行,皮特森算法在这类CPU上无法正确执行。(具体过程不做赘述,感兴趣可以自行搜索了解)

3.软硬件结合-使用硬件提供的原子操作(CAS等)编写代码完成线程互斥。硬件为我们提供了一些原子操作,例如CAS、FAA,使用这些原子操作,我们能设计新的算法解决临界区问题。
CAS,即Compare And Swap,比较并交换,一般需要三个操作数,一个地址值addr,一个所期望的旧值expected,一个新值newValue。只有当addr所存的数与expected相等时,才将newValue存入addr处。抽象成C语言代码如下所示(代码仅仅只是描述其含义,并非真正的实现)

int CAS(int *addr, int expected, int newValue){
	int oldValue = *addr;
	if (*addr == expected) {
		*addr = newValue;
	} 
	return oldValue;
}

对于CAS原子操作,不同的硬件平台提供有不同的实现。比如Intel平台提供的带lock前缀的cmpxchg指令,lock前缀保证了多核情况下的原子性,因为单核情况下,一条指令(完成CAS操作)本身就是原子的,但是其他核心仍然是可以访问并修改该addr地址的,这就有可能导致出现错误,因此在多核情况下需要做额外操作,早期的处理是锁总线,现代硬件采用了锁缓存等操作(感兴趣可以自行搜索了解),来保证多核情况下,CAS操作的原子性。

通过CAS操作,我们可以实现互斥锁,用一个变量来表示锁状态,在进入临界区之前,通过CAS来将锁置为上锁状态,在退出临界区时将锁释放,代码抽象如下所示

int lock = 0; //锁变量,0为未上锁,1为加锁状态,初始时为0

//加锁
void lock(){
	while (CAS(&lock,0,1) != 0) {
		;
	}
}

//释放锁
void unLock(){
	lock = 0; //这里不需要使用CAS,可以想想为什么
}

通过上述lock,unLock方法,可以实现线程的互斥。在执行lock()方法时,如果lock原本的值不为0,那么将一直执行while循环,直到lock为0。因此该锁也称为自旋锁。
自旋锁的实现简单易懂,但有一个很明显的缺点就是它不能保证有限等待,也就是说,很多个线程执行lock()方法时,竞争十分激烈,有可能其中一个线程永远也无法跳出CAS循环。即使如此,自旋锁仍然应用十分广泛,原因就是它在线程竞争程度不高时效率突出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值