同步工具
虽然避免需要同步的场景是上上策,但并不总能如愿,还是有需要对操作进行同步的场景
原子操作
原子操作是简单数据类型适用的一种简单的同步方式,它的优势在于不会阻塞竞争线程。对于简单的操作比如计数器增1来说,原子操作比锁有更大的性能优势。
Memory barriers 和 易变变量
为实现最佳CPU利用率,编译器会对汇编指令进行重排以更充分地利用CPU执行流水线。作为这个编译优化的一部分,如果编译器认为不会有产生不正确数据的危险,它会对访问主存的指令进行重排,然而对于编译器来说,它并不总能将所有对内存有依赖的操作都在优化中进行不会产生问题的重排。因为各变量之间是互相影响的,编译器优化可能用错误的顺序更新这些变量,由此产生潜在的不正确的结果。
memory barrier是一种不阻塞线程的确保操作按照了正确顺序的同步工具。它的运行原理类似于栅栏一样,它强制处理器完成memory barrier之前的所有加载和存储操作,再进行其之后的加载和存储操作,它可以确保某线程中的内存操作是按期望的顺序进行的。使用memory barrier只需要调用OSMemoryBarrier函数即可。
而易变 变量又是另一种内存使用的限制了,编译器经常通过将变量值加载进寄存器以优化存取速度。对于局部变量,这种做法自然没有问题,但如果这个变量在其它线程可见,则可能导致它们观察不到这个变量的值的变化,�因为有可能主存中的变量值已经被别的线程修改了,导致寄存器中读取的值与变量的值已经不一样了,这时候从寄存器中读取到的值已经不是变量的值了。将变量定义为volatile可以强制编译器每次加载变量时都从主存中读取。
锁
锁是最常用的同步工具,它用来保护关键代码段,即一次只允许一个线程访问的代码段。
锁 | 描述 |
---|---|
Mutex | 互斥锁扮演资源保护barrier的角色,它是确保一种一次只授权一个线程访问的信号量,其它想获取它的线程会在上一个线程使用时一直被阻塞直到锁被释放 |
Recursive lock | 它是Mutex锁的变种,它允许同一个线程获取多次,且直到它悉数释放了才会唤醒其它线程试图访问它的线程。当然这个锁最初是用于递归调用的场景,但也可以有多个方法,每个方法单独获取 |
Read-write lock | 也称为共享独占锁,这种锁通常用于保护读取频繁而写入较少的场景,且性能优异。如果写线程请求锁,则会阻塞直到所有 读线程完成读取,而写线程在等待时,读线程会一直阻塞直到写完 |
Distributed lock | 提供处理级的相互互斥访问,与真正的互斥锁不同的是,distributed lock不会阻止或者阻塞进程,它只会简单地报告锁是否繁忙,由进程决定怎样处理 |
spin lock | 自旋锁会不断轮询其锁条件直到其条件满足,自旋锁大量用于期望锁的等待时间小的多处理器系统上。在这些场景中,通常更有效的是轮询而不是阻塞线程,因为阻塞线程通常意味着耗时的上下文切换和线程数据结构的更新。由于它的轮询特性,系统并不提供任何自旋锁的实现,但可以在特定场景下轻松对其实现,可以参考kernel programming guide中在内核中实现自旋锁的信息 |
double-checked lock | 不推荐使用 |
条件
condition是另一种允许条件成真时线程间相互通知的信号量,通常用于表明资源的可用性以确保操作按特定的顺序进行。线程测试condition时,它会阻塞直到其它线程显式更新条件。其与互斥锁不同的地方在于它允许多个线程同时访问condition。其实类似于生产者消费者模式,它的使用方式中的一种是管理挂起消息池。队列中有消息时,会用condition变量signal等待线程,如果来了一条消息,则会signal一次,让等待线程中的一个取事件处理,如果几乎同一时间来了两次,则会signal condition两次,唤醒两个线程。
perform selector routines
cocoa应用可以用一种便利而同步的方式向线程传递消息,NSObjec对象声明了在线程上执行selector的方法,这些方法异步地传递消息,而系统确保会同步地在目标线程上执行这些selector,每个请求都会在目标线程的runloop上排上队,并按收到的顺序进行执行。
网友评论