iOS 锁的介绍与实践

2020-03-31  本文已影响0人  绍清_shao

前言

锁是多个线程之间交互时需要同步的工具

锁的类别

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该类还添加了tryLocklockBeforeDate:方法。

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略...

参考链接

PS: 这是我见过把多线程、线程安全说的最明白清楚的文章了。强烈建议阅读这一章节

上一篇 下一篇

猜你喜欢

热点阅读