手机网站北京,保险代理公司,中核集团,简单网页尝试做教案锁
并发编程的一个最基本问题就是原子性地执行一系列指令。锁有助于直接解决这一问题。
锁的基本思想
锁就是一个变量。这个变量保存了锁在某一时刻的状态。它要么是可用的#xff0c;表示没有线程持有锁#xff0c;要么是被占用的#xff0c;表示有线程持有锁#xff0c;正…锁
并发编程的一个最基本问题就是原子性地执行一系列指令。锁有助于直接解决这一问题。
锁的基本思想
锁就是一个变量。这个变量保存了锁在某一时刻的状态。它要么是可用的表示没有线程持有锁要么是被占用的表示有线程持有锁正处于临界区访问共享资源的那部分代码。
Pthread锁
POSIX库将锁称为互斥量mutex因为它被用来提供线程之间的互斥。当一个线程在临界区它能阻止其它线程进入到本地线程直到本线程离开临界区。
设计锁
一个锁应当能保证不会有第二个线程进入临界区。另外还应当保证公平性所有的线程都有机会公平抢到锁。在设计锁时还应该考虑到抢锁和释放锁的开销。
为了实现上面两点我们需要使用硬件和操作系统的支持。
控制中断
在临界区关闭中断。这个方案是为单处理系统开发的。
原子交换测试并设置
控制中断无在多处理器上工作系统设计者开始让硬件支持锁。最简单的硬件支持就是测试并设置指令test-and-set instruction也叫做原子交换。
typedef struct lock_t { int flag; } lock_t;void init(lock_t* mutex)
{mutex-flag 0;
}void lock(lock_t* mutex)
{while (mutex-flag 1) //测试; //锁被其它线程占用该线程陷入自旋mutex-flag 1; //设置
}void unlock(lock_t* mutex)
{mutex-flag 0;
}这种设置也是有问题的首先它不能完成让线程互斥的基本任务。考虑这种情况当线程1在测试锁时只是通过了测试并未设置发生了中断切换的线程2随后线程2执行了完整的测试和设置此时再次发生中断切换到线程1。由于先前线程1已经通过了测试语句接下来会执行设置代码。这样一来就有两个线程同时持有锁了。
在性能上如果一个线程在等待已经被持有的锁时会一直自旋不停地检查锁。这会浪费许多CPU时间。
实现这种锁需要一条硬件指令支持。在x86上是xchg指令原子交换。下面的代码片段说明了该指令的工作方式
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, 1) 1);//自旋
}void unlock(lock_t* lock)
{lock-flag 0;
}比较并交换
另一个硬件支持是比较并交换指令。
int CompareAndSwap(int *ptr, int expected, int new)
{int actual *ptr;if (actual expected)*ptr new;return actual;
}链接的加载和条件式存储指令
int LoadLinked(int* ptr)
{return *ptr;
}int StoreConditional(int* ptr, int value)
{if (no one has updated *ptr since the LoadLinked to this address){*ptr value;return 1;}else{return 0;}
}获取并增加
int FetchAndAdd(int *ptr)
{int old *ptr;*ptr old 1;return old;
}typedef struct lock_t
{int ticket;int turn;
} lock_t;void lock_init(lock_t *lock)
{lock-ticket 0;lock-turn 0;
}void lock(lock_t* lock)
{int myturn FetchAndAdd(lock-ticket);while (lock-turn ! myturn); //spin
}void unlock(lock_t* lock)
{FetchAndAdd(lock-turn);
}解决自旋时的浪费
在自旋的时候线程应该放弃CPU从运行状态变为就绪状态。而且我们应该控制释放锁时谁可以抢到锁这是为了避免性能上的浪费以及饿死的问题。
为此我们需要操作系统维护一个队列保存正在等待锁的线程队列。这样我们就可以在锁被占用时休眠线程在锁可用时从队头中唤醒一个线程。