前言
锁是多个线程之间交互时需要同步的工具
锁的类别
1. Mutex (互斥)
互斥充当资源周围的保护性屏障,有线程级互斥,也有进程级互斥。线程级互斥就是说,受保护的资源一次只能授予一个线程的访问权限。类似的,进程级互斥则表示,受保护的资源一次只能授予一个进程的访问权限。获得权限后,进程内所有线程都可以访问资源
互斥锁是一种信号量,它一次只能授予对一个线程的访问权限。如果线程A正在使用互斥锁,而线程B试图获取该互斥锁,则线程B将被阻塞,直到该互斥锁被线程A释放为止。如果多个线程竞争同一个互斥锁,则一次只能访问一个。
2. Recursive lock (递归锁)
递归锁是Mutex(互斥锁)的一种变体。递归锁允许单个线程在释放它之前多次获取该锁。其他线程将保持阻塞状态,直到锁的所有者以获取锁的相同次数释放锁。
举个例子:
线程A获得递归锁,线程A没释放递归锁前又获得一次递归锁,此时线程A虽然没有释放递归锁,但任然能获得递归锁。此时线程B想获得递归锁,则线程B将被阻塞,因为线程A没以加锁的次数释放递归锁。等到线程A以加锁的次数释放递归锁,此时线程B就能获得递归锁了。
3. Read-write lock(读写锁)
读写锁也称为共享独占锁,多个读操作可以并发,写操作将串行执行。
类似GCD的栅栏函数,例如有a-z个任务,其中e、m这两个操作是写操作。执行期间,当要开始e这个写操作时,会先把所以将要执行读跟写的操作都阻塞,然后开始执行e,等e执行完后,其他读操作将继续并发执行。下次遇到写操作时,依然如此。
4. Distributed lock(分布式锁)
分布式锁在进程级别提供互斥访问。与真正的互斥锁不同,分布式锁不会阻止进程或阻止其运行。它仅报告锁繁忙的时间,并让进程决定如何进行。
** 5. 自旋锁
互斥锁是在CPU sleep-wait模式下阻塞线程,如果一个线程获取锁时被阻塞,CPU会把这个线程挂起,CPU会切换到其他线程进行任务执行。直到要获得的锁解开,会重新唤起被阻塞的线程进行任务执行。
自旋锁是忙等模式,当一个线程获取自旋锁被阻塞时,CPU不会把这个线程挂起,也不会切换到其他线程执行代码。而且不断的询问锁是否已经解开,会处于忙等待。
应用场景:临界资源执行速度非常短的时候,也就是被锁资源时间非常短暂的时候,CPU忙等的时间短,造成的CPU资源浪费小,且效率高
缺点:优先级反转,当低优先级的线程获得锁后,高优先级线程想获取自旋锁,造成高优先级线程占用CPU资源,从而不能完成低优先级线程的任务,导致无法开锁。
iOS中实践
互斥锁
1. mutex lock
pthread_mutex_t mutex; // 互斥锁声明
void MyInitFunction()
{
// 互斥锁初始化
pthread_mutex_init(&mutex, NULL);
}
void MyLockingFunction()
{
// 互斥锁加锁
pthread_mutex_lock(&mutex);
// Do work.
// 互斥锁解锁
pthread_mutex_unlock(&mutex);
}
deinit() {
// 释放互斥锁
pthread_mutex_destroy
}
2. NSLock
可以像使用任何互斥锁一样使用这些方法来获取和释放锁。
除了标准的锁定行为,NSLock
该类还添加了tryLock
和 lockBeforeDate:
方法。
tryLock
方法尝试获取锁,但如果锁不可用,则不会阻塞;相反,该方法仅返回NO。
lockBeforeDate:
方法尝试获取锁,在这期间会阻塞线程。但如果在指定的时间限制内未获取锁,则取消阻止线程(并返回NO)。
总结:虽然都是互斥锁,比
mutex lock
要灵活很多,API也很人性化。
BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
}
3. synchronized指令
代码中动态创建互斥锁的一种便捷方法,跟前面两种互斥锁达到的效果相同。synchronized
指令不必直接创建互斥量或锁定对象。相反,只需像如下这样使用即可:
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
如果在两个不同的线程中执行上述方法,并anObj
在每个线程上为参数传递一个不同的对象,则每个线程将获得其锁并继续进行处理而不会被另一个线程阻塞。但是,如果在两种情况下都传递相同的对象,则其中一个线程将首先获取锁,而另一个线程将阻塞,直到第一个线程完成关键部分。
递归锁 (可重入锁)
NSRecursiveLock
同一线程可以多次获取该锁,而不会导致线程死锁。
每次成功获取锁都必须通过相应的调用来平衡,以解锁该锁。仅当所有锁定和解锁调用均达到平衡时,才实际释放该锁定,以便其他线程可以获取它。
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
void MyRecursiveFunction(int value)
{
[theLock lock];
if (value != 0)
{
--value;
MyRecursiveFunction(value);
}
[theLock unlock];
}
MyRecursiveFunction(5);
其他锁
1. NSConditionLock略...
2. NSDistributedLock略...
参考链接
- Apple官方文档
- 《程序员的自我修养——链接、装载与库》第一章 第六节
PS: 这是我见过把多线程、线程安全说的最明白清楚的文章了。强烈建议阅读这一章节
网友评论