互斥 并发小结

为什么需要互斥?为什么有临界区?
因为中断随时可能发生,而中断必不可少。
1、最早为了实现互斥,使用控制中断的方法——在临界区关闭中断
很好理解,处理器自闭了,和每个进程(线程)轮番上演吊死在一棵树上的戏码。
缺点:特权滥用,害怕恶意进程,不支持多处理器,以及,中断很重要!(比如磁盘完成了读取请求但是由于没有中断,等待读取的进程无从得知这一事实)

2、测试并设置指令(test-and-set)——一种原子交换,自旋性能损耗大,有硬件支持,不会因为不可预知的中断导致失去原子性和正确性

本文代码来自Remzi的操作系统导论OSTEP

int TestAndSet(int *old_ptr,int new)//原子性的把旧值变为新值,并且返回旧值
{
	int old=*old_ptr;
	*old_ptr=new;
	return old;
}

typedef struct lock_t
{
	int flag;
}lock_t;

void init(lock_t *lock)
{
	lock->flag=0;
}

void lock(lock_t *lock)
{
	while(TestAndSet(&lock->flag,1)==1)  //还有一种错误写法 :while(lock_t->flag==1) ; lock_t->flag=1;
		;//	自旋                                                                                   
}

void unlock(lock_t *lock)
{
	lock->flag=0;
}

3、比较并交换(SPARC系统上是compare-and-swap,x86上是compare-and-exchange)
只需要将TestAndSet函数换为以下函数即可:

int CAS(int *old_ptr,int expected,int new)
{
	int old=*old_ptr;
	if(old==expected)  //本行就是比test-and-set添加的部分
		*old_ptr=new;
	return old;
}

4、连接的加载和条件式存储指令

int LoadLinked (int *ptr)
{
	return *ptr;
}

int StoreConditional(int *ptr,int value)
{
	if(加载的地址在这段期间都没有更新过)
	{
		*ptr=value;
		return 1;
	}
	else
		return 0;
}

void lock(lock_t *lock)
{
	while(1)
	{
		while(LoadLinked(&lock->flag)==1) //锁目前不可用就自旋
			;
		if(StoreConditional(&lock->flag,1)==1)//如果说成功把*ptr更新为1,那么获取锁成功,否则失败
			return;
	}
}

void unlock(lock_t *lock)
{
	lock->flag=0;
}

1、2、3、4均是硬件原语的C语言伪代码,它们实现了正确性(原子性),但是没有考虑公平性。
5、获取并增加(fetch-and-add)
代码结构与test-and-set类似,但加入了ticket和turn变量实现了轮转,考虑了公平性,能保证所有线程都能抢到锁。

1、2、3、4、5均属于自旋锁,不同系统有具体的构建更有效率的锁的方法,大部分都使用休眠代替了自旋,详细可参见OSTEP的第28章第230页。

说完了并发环境下锁、互斥的几种实现,说说如何用于数据结构吧。
1、并发计数
为了在多核系统中提高并发性能,需要维护好临界区。
显然,如果每一次简单的计数操作(比如++)都需要生成锁、释放锁,性能上十分不划算。
因此,要偷懒,不实时更新计数值!
引入多个局部计数器,一个全局计数器,比如说4个CPU则有4个局部计数器。
懒惰带来性能!
常用的实现是设置一个阈值S,比如S=100万则4个CPU上的每个计数器上的值到达100万时,加一次锁,把值汇总到全局计数器。
S变大则性能变好,计数器延迟增大。S变小则性能较差,延迟较小。

2、并发链表
这种数据结构在加锁时可能出现问题——锁还占用着,程序崩溃了——比如说在插入链表时,如果我们把获取锁放在程序开头,释放锁放在程序结尾,程序确实不会有临界区冲突的问题,但是如果在释放锁之前,程序崩溃,那么锁就会…
以上问题的解决方案:使得临界区更精准,而不是大大咧咧的整个把程序括起来,从而让释放锁之前绝不崩溃 ——具体的说,临界区应该是包含可能受到中断影响的部分(比如指针)的最小代码段。

3、并发队列
队列头尾各一个锁。
思路与并发链表类似——保护好敏感值(通常是指针)。

欢迎大家勘误及留言~(Remzi的书真的很不错!

发布了5 篇原创文章 · 获赞 0 · 访问量 160

猜你喜欢

转载自blog.csdn.net/jojozym/article/details/104636298