原子操作
概念来源于物理概念中的原子定义,指执行结束前不可分割(即不可打断)的操作,是最小的执行单位。
原子操作的原子性依赖于ldrex与strex实现,ldrex读取数据时会进行独占标记,防止其他内核路径访问,直至调用strex完成写入后清除标记。自然strex也不能写入被别的内核路径独占的内存,若是写入失败则循环至成功写入。
自旋锁(spinlock)
同步机制:若自旋锁已被别的执行者保持,调用者就会原地循环等待并检查该锁的持有者是否已经释放锁(即进入自旋状态),若释放则调用者开始持有该锁。自旋锁持有期间不可被抢占。
信号量(Semaphore)
信号量在创建时设置一个初始值count,用于表示当前可用的资源数。一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作为count-1,若当前count为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待;若当前count为非负数,表示可获得信号量,因而可立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把count+1实现,如果count为非正数,表明有任务等待,它也唤醒所有等待该信号量的任务。
互斥锁(Semaphore)
互斥锁是非常常用的同步机制,互斥锁是这样一种同步机制:在互斥锁中同时只能有一个任务可以访问该锁保护的共享资源,且释放锁和获得锁的调用方必须一致。因此在互斥锁中,除了对锁本身进行同步,对调用方(或称持有者)必须也进行同步。当互斥锁无法获得时,task会加入等待队列,直至可获得锁为止。
读写信号量(RW_Semaphore)
读写信号量将访问者分为读者或者写者,读者在持有读写信号量期间只能对该信号量保护的共享资源进行读访问,而只要一个任务需要写,它就被归类为写者,其进行访问之前必先获得写者身份,在其不需写访问时可降级为读者。读写信号量可同时拥有不受限的读者数,写者是排他性的,独占性的,而读者不排他。若读写信号量未被写者持有或者等待,读者就可以获得读写信号量,否则必须等待直到写者释放读写信号量为止;若读写信号量没有被读者或写者持有,也没用写者等待,写者可以获得该读写信号量,否则等待至信号量全部释放(没有其他访问者)为止。
读写锁(RW_Lock)
读写锁实际上是特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。
在读写锁保持期间也是抢占失效的。如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否则它必须自旋在那里,直到没有任何写者或读者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。
顺序锁(Seq_Lock)
顺序锁对读写锁的一种优化:读者绝不会被写者阻塞,也就说,读者可以在写者对被顺序锁保护的共享资源进行写操作时仍然可以继续读,不必等待写者完成写操作,写者也不需要等待所有读者完成读操作才去进行写操作。但是,写者与写者之间仍然是互斥的。写操作的优先级大于读操作。
顺序锁有一个限制,它必须要求被保护的共享资源不含有指针,因为写者可能使得指针失效,但读者如果正要访问该指针,将导致OOPs。如果读者在读操作期间,写者已经发生了写操作,那么,读者必须重新读取数据,以便确保得到的数据是完整的。 顺序锁适用于读多写少的情况。
这种锁对于读写同时进行的概率比较小的情况,性能是非常好的,而且它允许读写同时进行,更大地提高了并发性。顺序锁的一个典型的应用在于时钟系统。
RCU(Read-Copy Update)
RCU是读写锁的高性能版本,既允许多个读者同时访问被保护的数据,又允许多个读者和多个写者同时访问被保护的数据(注意:是否可以有多个写者并行访问取决于写者之间使用的同步机制),读者没有任何同步开销,而写者的同步开销则取决于使用的写者间同步机制。
对于被RCU保护的共享数据结构,读者不需要获得任何锁就可以访问它,但写者在访问它时首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机(所有引用该数据的CPU都退出对共享数据的操作时)把指向原来数据的指针重新指向新的被修改的数据。 有一个专门的垃圾收集器探测读者的信号,一旦所有读者都已发送信号告知它们不在使用被RCU保护的数据结构,垃圾收集器就调用回调函数完成最后的数据释放或修改操作。
RCU不能替代读写锁,当写比较多时,对读者的性能提高不能弥补写者导致的损失,但是大多数情况下RCU的表现高于读写锁。
机制 | 等待机制 | 优缺点 | 适用于 |
---|---|---|---|
原子操作 | 无,ldrex 与strex实现对内存的独占访问 | 性能很高,场景受限 | 资源计数 |
自旋锁 | 忙等待, 唯一持有的 | 多处理器下性能优异,临界区时间长会浪费 | 中断上下文 |
信号量 | 睡眠等待,多数持有 | 相对灵活,适用于复杂情况,但是耗时较长 | 情况复杂的场景,比如内核与用户空间的交互 |
互斥锁 | 睡眠等待,优选自旋锁等待,唯一持有 | 比信号量更高效,适用复杂场景,但存在限制 | 优先于信号量 |
读写信号量 | 睡眠等待,优选自旋锁等待,读者多数持有,写着唯一持有 | 读写优化的信号量,适用于多读写少的情况 | 类似信号量,多读写少,例如内存管理 |
读写锁 | 忙等待,优选自旋锁等待,读者多数持有,写着唯一持有 | 读写优化的自旋锁,是由于多读写少的情况 | 类似信号量,多读写少 |
顺序锁 | 忙等待 | 写优先,读不阻塞写,对于读写同时比较少的情况适用 | 时钟系统 |
RCU | 读写锁的高性能版本 | 绝大部分为读而极少部分为写的情况,非常高效 | 读多写少,且对内存消耗不敏感的情况下,优先于读写锁。 |