锁的分类
在计算机科学中,锁是执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足
锁分为建议锁和强制锁
建议锁:每个线程在访问对应资源前都需要获取锁的信息,再根据信息决定是否可以访问,若访问对应信息,锁的状态会改变为锁定,因此其它线程此时不会访问该资源,当资源结束后,会恢复锁的状态,允许其他线程的访问。
强制锁:若有未授权的线程想要访问锁定的数据,在访问时就会发生异常。
iOS中,锁分为互斥锁,递归锁,信号量,条件锁,自旋锁,读写锁,分布式锁
atomic and nonatomic
原子性(Atomicity)
即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
nonatomic简单实现
1 | @property(nonatomic, retain) UITextField *userName; |
atomic简单实现
1 | @property(retain) UITextField *userName; |
下面三个有什么区别 ?
1 | @property(nonatomic, retain) UITextField *userName; |
“atomic”是默认关键字,使用”atomic”,合成setter/getter
将确保始终从getter
或setter
返回一个完整的值,而不管setter
在任何其他线程上的活动如何。也就是说,如果线程A处于getter
的访问中,而线程B正调用setter
,那么一个实际可行的值——很可能是一个自动释放的对象(an autoreleased object)——将返回给A中的调用者。
“atomic”不能保证线程安全。如果线程A调用getter
的同时,线程B和线程C用不同的值调用setter,线程A可能会得到三个返回值中的任何一个——调用任何setter
之前的值,或者B和C中传递给setter
的值中的任何一个。同样,对象可能最终得到B或C中的值,无法判断。
单个属性的原子性也不能保证在使用多个依赖属性时的线程安全性。
1 | @property(atomic, copy) NSString *firstName; |
在这种情况下,线程A可以通过调用setFirstName:
来重命名对象,然后调用setLastName:
。同时,线程B可能会在线程A的两次调用之间调用fullName
,并将接收新的名字和旧的姓氏。
为了解决这个问题,您需要一个事务模型。也就是说,一些其他类型的同步和/或排除,允许一个人排除访问fullName
,而依赖的属性正在更新。
原子关键字意味着合成访问器保证对象在跨线程同时访问时被完全设置或读取。如果您使用atomic关键字,这并不意味着您的属性是线程安全的。它仅仅意味着对象被保证是完整的
互斥锁 (@synchronized,NSLock)
在编程中,引入对象互斥锁的的概念,来保证共享数据操作的完整性,每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象。
@synchronized
要一个参数,这个参数相当于信号量
1 | // 用在防止多线程访问属性上比较多 |
自旋锁
何谓自旋锁?它是为实现保护共享资源而提出一种锁机制,其实,自旋锁与互斥锁比较类似,他们都是为了解决对某项资源的互斥使用。无论是互斥锁还是自旋锁,在任何时刻,最多只能有一个保持着,也就说,在任何时刻最多只能有一个执行单元获得锁,但是两者在调度机制上略有不同,对于互斥锁,如果资源已经被占用, 资源申请者只能进入睡眠状态,但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。
OSSpinLock
使用方式
1 | // 初始化 |
这个自旋锁存在优先级反转的问题,已经不再线程安全。
自旋锁和互斥锁对比
自旋锁会忙等:所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
互斥锁会休眠:所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时CPU可以调度其他线程工作,直到被锁资源释放锁,此时会唤醒休眠线程。
如果能在短时间内获得锁,自旋锁的效率远高于互斥锁,如果不能在很短的时间获得锁,会使CPU效率降低,自旋锁不能实现递归调用。
Dispatch Semaphore(信号量)
信号量其实就是用来保证访问资源的线程数,当信号量大于等于1时,资源可以访问,否则无法访问资源,直到其它线程释放资源。
主要有三个函数
1 | dispatch_semaphore_t dispatch_semaphore_create(long value); //创建一个dispatch_semaphore_t,value为初始信号量 |
注意:
同一个信号的dispatch_semaphore_wait与 异步操作不能在同一个线程中,否则异步操作会被卡住,也就不会执行到dispatch_semaphore_signal
两个线程交替打印Hello,world循环100次,如何实现?
1 | -(void)Action |
读写锁
读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分为读者和写者,读者只对共享资源进行读访问,写者只对共享资源进行写操作。允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数,写者是排他性的,一个读写锁同时只能有一个写者或多个读者,但不能同时既有读者又有写者。
dispatch_barrier_async
和dispatch_barrier_sync
(栅栏)
相同:这两个函数的作用差不多,都是把它前面和它后面的函数分隔开,使它前面的任务先执行,再执行它添加的任务,最后执行它后面的任务
不同:提交任务的方式不同,一个是同步,一个是异步。