问:什么是 "共享存储上的原子test-and-set操作"? 答:系统使用一个网络磁盘服务器,由主备双方共享 (图1中的 "共享磁盘")。该网络磁盘服务器有一个 "test-and-set服务"。 test-and-set服务维护一个标志,该标志最初被设置为False。 如果主服务器或备份服务器认为另一台服务器已经死亡,因此它应该自己接管,那么它首先向磁盘服务器发送一个test-and-set的操作。 else: flag = true release_lock() return true 主服务器(或备份服务器)只有在test-and-set返回true时才会接管(" 那么网络磁盘服务器就充当了破坏者的角色;test-and-set只对第一个调用返回true。 问:遵循Output Rule会损失多少性能? 答:表2提供了一些洞察力。
操作系统中提供一组原子操作指令,例如compare-and-swap,test-and-set,fetch-and-add,read-modify-write。 TAS Test-and-set,使用原子操作,修改内存值并返回对应内存修改前的值,当一个线程在执行TAS操作时,其他线程不能同时操作对应内存。 busy waiting 4.Spinlock 5.Mutex vs Semaphore 5.Compare-And-Swap 6.AQS AbstractQueuedSynchronizer 7.TAS Test-and-set
因此退出循环设置 mutext->flag = 1,这是线程B认为自己拿到了锁 操作系统重新调度运行线程A的时候,线程A也认为自己拿到了锁 要解决这个问题,需要硬件提供原子性指令支持,下面我们来看两个常见的指令: Test-And-Set Test-And-Set也称为atomic exchagne,在x86中叫做xchg ,这条指令用C语言描述的话,大概是这样: int TestAndSet (int *old_ptr, int new Spin Lock基于Test-And-Set的实现如下: void lock (lock_t *mutex) { while (TestAndSet(&lock->falg, 1) == 1)
; } return false; } 与 CAS 相似的还有下面的原子操作:(这些东西大家自己看 Wikipedia 吧) Fetch And Add,一般用来对变量做 +1 的原子操作 Test-and-set 汇编指令 BST Test and Test-and-set,用来低低 Test-and-Set 的资源争夺情况 注:在实际的C/C++程序中,CAS 的各种实现版本如下: 1)GCC 的 CAS
newval; return true; } 与CAS相似的还有下面的原子操作:(这些东西大家自己看Wikipedia,也没什么复杂的) Fetch And Add,一般用来对变量做 +1 的原子操作 Test-and-set 汇编指令BST Test and Test-and-set,用来低低Test-and-Set的资源争夺情况 注:在实际的C/C++程序中,CAS的各种实现版本如下: 1)GCC的CAS GCC4.1+版本中支持
早期处理器中支持的机器指令: 1)原子的测试并设置(Test-And-Set) 2)获取并递增 3)交换(Swap) 操作系统和JVM使用这些指令来实现锁和并发的数据结构。
int value := *location 4: *location := value + inc 5: return value 6: } Test-and-set 14: // End of atomic segment 15: 16: return oldValue; 17: } Test and Test-and-set
在多处理器环境下,通常使用原子操作,如测试并设置(Test-and-Set)或比较并交换(Compare-and-Swap),来实现互斥锁。
基本上所有系统都会实现的方法:使用test-and-set指令构建原子操作,从而满足临界代码区的保护要求,而不必禁止中断。而且,这种机制适用于多核处理器或者硬件多线程系统。细节参考下一节。 道理很简单,唯一的要求是硬件可以实现减1操作的原子性,换句话说,就是硬件必须提供test-and-set这样的原子操作指令。不管是中断,还是多核系统,都不能影响这个原子操作的正确性。 都有这样特殊的指令: X86通过在指令前面添加lock前缀锁住总线实现原子操作; ARM通过ldrex和strex独占指令实现原子操作,早期版本的ARM架构使用swp指令; 对于支持X86-多核的系统而言,使用test-and-set 实际上,其执行过程是:所有共享内存都必须停止,使用信号量的用户获取该值,完成test-and-set操作,然后将结果同步到每一份备份中。 如果能够不在每次都必须严格保证原子性的情况下,实现test-and-set操作要高效得多。换句话说,就是尝试set操作,如果是原子的,就成功;不是原子的,就重新尝试。
进程同步 3.1 进程同步的概念 3.1.1 临界资源 3.1.2 临界区 3.1.3 同步机制应遵循的规则 空闲让进 忙则等待 有限等待 让权等待 3.2 硬件同步机制 关中断 利用Test-and-Set
假设我们要对 test-and-set 服务进行备份。test-and-set,简而言之,就是一个锁服务。
利用Test-and-Set实现互斥 这是一种借助一条硬件指令—“测试并建立”指令TS(Test-and-Set)以实现互斥的方法。在许多计算机中都提供了这种指令。
我们先来看看「忙等待锁」的实现 在说明「忙等待锁」的实现之前,先介绍现代 CPU 体系结构提供的特殊原子操作指令 —— 测试和置位(Test-and-Set)指令。 如果用 C 代码表示 Test-and-Set 指令,形式如下: 测试并设置指令做了下述事情: 把 old_ptr 更新为 new 的新值 返回 old_ptr 的旧值; 当然,关键是这些代码是原子执行 原子操作就是要么全部执行,要么都不执行,不能出现执行到一半的中间状态 我们可以运用 Test-and-Set 指令来实现「忙等待锁」,代码如下: 忙等待锁的实现 我们来确保理解为什么这个锁能工作: 第一个场景是
解决办法是引入一个第三方仲裁,来保存谁可以进行应答:比如使用一个 TAS(Test-and-Set Server) 或者一个共享外存。
用Test-and-Set指令实现互斥 参考答案: boolean lock = false; do { while (TestAndSet(lock)) ; // 自旋等待直到锁可用
); new_pid = next_pid++; lock_next_pid->Release(); 大多数现代体系结构都提供特殊的原子操作指令 通过特殊的内存访问电路 针对单处理器和多处理器 Test-and-Set
并且我们需要确保抢锁和释放锁过程的原子性,通常会借助硬件层面提供的特殊的原子指令完成CAS操作: 这个特殊的硬件指令会保证一次test-and-set操作的原子性。 接下来我们看一下spinlock.c文件,先来看一下acquire函数, 在函数中有一个while循环,这就是我刚刚提到的test-and-set循环。 因为大部分处理器都有的test-and-set硬件指令,所以这个函数的实现比较直观。我们可以通过查看kernel.asm来了解RISC-V具体是如何实现的。下图就是atomic swap操作。 如果锁没有被持有,那么锁对象的locked字段会是0,如果locked字段等于0,我们调用test-and-set将1写入locked字段,并且返回locked字段之前的数值0。
我们先来看看「忙等待锁」的实现 在说明「忙等待锁」的实现之前,先介绍现代 CPU 体系结构提供的特殊原子操作指令 —— 测试和置位(Test-and-Set)指令。 如果用 C 代码表示 Test-and-Set 指令,形式如下: ? 原子操作就是要么全部执行,要么都不执行,不能出现执行到一半的中间状态 我们可以运用 Test-and-Set 指令来实现「忙等待锁」,代码如下: ?
硬件保证一个语义上看起来需要多步操作的行为只通过一条处理器指令就可以完成,这类指令常用的有: 测试并设置( Test-and-Set ) 获取并增加( Fetch-and-Increment ) 交换(
关于TAS vs CAS 另外一个事情,就是也许你会有所疑惑,怎么一会test and set ,一会又是compare and swap ,二者究竟有什么不同: test-and-set modifies accum == *dest ) { *dest = newval; return true; } return false; } Test-And-Set