关于iOS线程锁的一点研究

2020-12-31  本文已影响0人  优质胡萝北

什么是锁

多线程中,对共享资源进行访问,为了防止并发引起的相关问题,通常都是引入锁的机制来处理并发问题。学术上对线程锁有好几种不同的定义方式,这里要对锁的几个概念做一个解释。

  1. 临界区

    指的是一块对公共资源进行访问的代码,并非一种机制或是算法。

  2. 阻塞锁和非阻塞锁

    阻塞锁和非阻塞锁的区别,线程访问临界区时,该资源上锁与否线程是否被挂起。阻塞锁会挂起线程,等待临界区解锁,而非阻塞锁会保持活跃状态。

  3. 递归锁和非递归锁

    递归锁和非递归锁的区别,当一个线程多次获取同一个递归锁时,线程不会产生死锁。但是一个线程多次获取同一个非递归锁,则会产生死锁。从效率层面上来说,非递归锁的效率高于递归锁

  4. 死锁

    死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

自旋锁

非阻塞锁 非递归锁

自旋锁(Spin Lock),它的工作原理是当某个线程需要访问临界区时,如果该临界区已经被上锁,那么该线程不会被挂起,而是会循环请求线程锁,此时线程处于忙等的状态(在非耗时操作下,这种忙等是可以接受的),直到该资源被解锁释放。

线程挂起主动出让时间片的做法是有性能消耗的,这种上下文切换会通常占用10μs。所以非阻塞锁是性能最高的锁。

iOS系统下可用的自旋锁:

#import <os/lock.h>

{
    os_unfair_lock_t unfairLock;
    unfairLock = &OS_UNFAIR_LOCK_INIT;
    os_unfair_lock_lock(unfairLock);
    ...
    os_unfair_lock_unlock(unfairLock);
}

互斥锁

阻塞锁 递归锁 非递归锁

互斥锁(Mutex),它的工作原理是当某个线程访问临界区已经被加锁,那么该线程会进入休眠状态。当临界区解锁,则等待线程会被唤醒。互斥锁要保证在任一时刻,只能有一个线程访问临界区,同时只有上锁线程能够进行unLock操作

iOS系统下可用的互斥锁:

#import <pthread.h>


/*
 * PTHREAD_MUTEX_NORMAL     默认非递归锁
 * PTHREAD_MUTEX_ERRORCHECK 非递归锁
 * PTHREAD_MUTEX_RECURSIVE  递归锁
 * PTHREAD_MUTEX_DEFAULT    PTHREAD_MUTEX_NORMAL
 */

{
    pthread_mutex_t lock;
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    // 指定互斥锁类型为非递归锁
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&lock, &attr);

    pthread_mutex_lock(&lock);
    ...
    pthread_mutex_unlock(&lock);
        
    pthread_mutexattr_destroy(&attr);
    pthread_mutex_destroy(&lock);
}

#import <Foundation/Foundation.h>

{
    NSLock *lock = [[NSLock alloc] init];
    [lock lock];
    ...
    [lock unlock];
}

#import <Foundation/Foundation.h>

{
    NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
    [lock lock];
    ...
    [lock unlock];
}

{
    @synchronized (NSObject.new) {
        ...
    }
}

条件锁

阻塞锁 非递归锁

条件锁(Condition Lock),实际上是对一个互斥锁和一个条件变量的封装。当线程想要访问临界区,需要满足Condition

iOS系统下可用的互斥锁:

信号量

信号量(Semaphore)是实现异步调度的一种策略,这种机制可以实现线程加锁的目的。信号量机制与互斥锁最大的区别,是互斥锁要保证统一时间只能有一个线程访问临界区,但是信号量可以任意指定同时访问临界区的线程数

iOS在GCD中封装了dispatch_semaphore,用于实现信号量调度

  1. dispatch_semaphore_create(long value)
    初始化dispatch_semaphore_t类型的信号量,参数value是最大并发量。注意value须大于0,否则会返回null。

  2. dispatch_semaphore_signal(dispatch_semaphore_t signal)
    参数signal是传入所需信号量,并使传入的信号量加1,可以理解为解锁。

  3. dispatch_semaphore_wait(dispatch_semaphore_t signal, dispatch_time_t timeout)
    参数是传入一个信号量和一个超时时间。当传入的信号量的值大于0(可执行并发),会继续执行临界区代码,并且将传入的信号量减1。当传入的信号量的值等于0(无可并发资源),则线程进入休眠状态主动让出时间片,并将该临界区任务加入等待队列,待信号量加1时,执行队列顶部任务。如果在线程休眠的过程中一直没有收到信号直到timeOut,则线程会继续访问临界区。可以理解为加锁。

使用代码如下:

{
    dispatch_semaphore_t lock = dispatch_semaphore_create(1);   
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);       
    dispatch_semaphore_signal(lock);
}

总结

上一篇下一篇

猜你喜欢

热点阅读