详解macOS多线程和锁

803 阅读6分钟

线程的状态与生命周期

下图是线程状态示意图,从图中可以看出线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡

image.png

  • 下面分别阐述线程生命周期中的每一步

  • 新建:实例化线程对象

  • 就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。

  • 运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。

  • 阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(互斥锁)。

  • 死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象

  • 还有线程的exit和cancel

  • [NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。

  • [thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。

多线程的使用

NSThread的使用

NSThread有三种创建方式

  • init方式

  • detachNewThreadSelector创建好之后自动启动

  • performSelectorInBackground创建好之后也是直接启动

    /** 方法一,需要start */
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"];
    
    // 线程加入线程池等待CPU调度,时间很快,几乎是立刻执行
    [thread1 start];
    
    /** 方法二,创建好之后自动启动 */
    [NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"];
    
    /** 方法三,隐式创建,直接启动 */
    [self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"];
    
    - (void)doSomething1:(NSObject *)object {
        // 传递过来的参数
        NSLog(@"%@",object);
        NSLog(@"doSomething1:%@",[NSThread currentThread]);
    }
    
    - (void)doSomething2:(NSObject *)object {
        NSLog(@"%@",object);
        NSLog(@"doSomething2:%@",[NSThread currentThread]);
    }
    
    - (void)doSomething3:(NSObject *)object {
        NSLog(@"%@",object);
        NSLog(@"doSomething3:%@",[NSThread currentThread]);
    }
    
  • 创建常驻线程,主线程为什么不会退出的原因
    AFNetworking源码学习

    + (NSThread *)networkRequestThread {
        static NSThread *_networkRequestThread = nil;
        static dispatch_once_t oncePredicate;
    
        dispatch_once(&oncePredicate, ^{
            _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
            [_networkRequestThread start];
        });
        
        return _networkRequestThread;
    }
    
    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            //暂停线程使用[NSRunloop currentRunloop] removePort: <#(nonnull NSPort)#> forMode: <#(nonull NSRunLoopMode)#>
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    

GCD的使用

GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等),底层是个线程池,线程数量有限。

  • dispatch_get_global_queue参数详解
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)//flags保留参数,不要填0以外的其他数字

  • dispatch_barrier_async栅栏,无锁队列实现(多读单写),将栅栏前后的任务分成前后两组,第一组执行完之后,才执行第二组,只能用在自己创建的对象,对于dispatch_get_global_queue,仅仅相当于调用dispatch_async

  • 栅栏代码实战

    - (IBAction)barrierGCD:(id)sender {
        // 并发队列
        dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
        // 异步执行
        dispatch_async(queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"栅栏:并发异步1 %@",[NSThread currentThread]);
            }
        });
    
        dispatch_async(queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"栅栏:并发异步2 %@",[NSThread currentThread]);
            }
        });
    
        dispatch_barrier_async(queue, ^{
            NSLog(@"------------barrier------------%@", [NSThread currentThread]);
            NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
        });
    
        dispatch_async(queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"栅栏:并发异步3 %@",[NSThread currentThread]);
            }
        });
    
        dispatch_async(queue, ^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"栅栏:并发异步4 %@",[NSThread currentThread]);
            }
        });
    }
    
  • 结果输出

    栅栏:并发异步1 {number = 3, name = (null)}
    栅栏:并发异步2 {number = 6, name = (null)}
    栅栏:并发异步1 {number = 3, name = (null)}
    栅栏:并发异步2 {number = 6, name = (null)}
    栅栏:并发异步1 {number = 3, name = (null)}
    栅栏:并发异步2 {number = 6, name = (null)}
    
    ------------barrier------------{number = 6, name = (null)}
    
    ******* 并发异步执行,但是34一定在12后面 *********
    
    栅栏:并发异步4 {number = 3, name = (null)}
    栅栏:并发异步3 {number = 6, name = (null)}
    栅栏:并发异步4 {number = 3, name = (null)}
    栅栏:并发异步3 {number = 6, name = (null)}
    栅栏:并发异步4 {number = 3, name = (null)}
    栅栏:并发异步3 {number = 6, name = (null)}
    
  • GCD队列组实现
    在所有队列执行完毕之后得到通知

    - (void)testGroup {
        dispatch_group_t group = dispatch_group_create();
            dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"队列组:有一个耗时操作完成!");
        });
    
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            NSLog(@"队列组:有一个耗时操作完成!");
        });
    
        //非阻塞等待所有队列执行完毕
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
        });
    
        //阻塞等待,DISPATCH_TIME_FOREVER时就意味着永久等待
        long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,1 * NSEC_PER_SEC));
    
        if (timeout == 0) {
            NSLog(@"回来了");
        } else {
            NSLog(@"等待中 -- 转菊花");
        }
    }
    
  • dispatch_after延迟执行,不需要创建RunLoop
    两种时间延迟设置

    1. dispatch_time(DISPATCH_TIME_NOW, 10* NSEC_PER_SEC);//系统休眠停止计时
    2. dispatch_walltime(DISPATCH_TIME_NOW, 10* NSEC_PER_USEC);//系统休眠不停止
  • dispatch_once_t实现单例

    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        NSLog(@"程序运行过程中我只执行了一次!");
    });
    
  • GCD延时执行

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 5秒后异步执行
        NSLog(@"我已经等待了5秒!");
    });
    
  • RunLoop
    GCD创建的线程,默认没有开启RunLoop,如调用performSelector不会执行(performSelector实现是创建了一个NSTimer),不建议在dispatch_get_global_queue中创建RunLoop使得线程常驻

NSOperation的使用

基于GCD的更高一层的封装

  • NSOperation的三种创建方式

  • NSInvocationOperation的使用
    程序在主线程执行,没有开启新线程。这是因为NSOperation多线程的使用需要配合队列NSOperationQueue

    - (void)testNSInvocationOperation {
        // 创建NSInvocationOperation
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    
        // 开始执行操作
        [invocationOperation start];
    }
    
    - (void)invocationOperation {
        NSLog(@"NSInvocationOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
    }
    
  • NSBlockOperation的使用
    程序在主线程执行,没有开启新线程。这是因为NSOperation多线程的使用需要配合队列NSOperationQueue

    - (void)testNSBlockOperation {
        // 把任务放到block中
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"NSBlockOperation包含的任务,没有加入队列========%@", [NSThread currentThread]);
        }];
        
        [blockOperation start];
    }
    
  • 运用继承自NSOperation的子类
    定义一个继承自NSOperation的类,然后重写它的main方法

    
    /*******************"WHOperation.h"*************************/
    
    #import @interface WHOperation : NSOperation
    
    @end
    
    /*******************"WHOperation.m"*************************/
    
    #import "WHOperation.h"
    
    @implementation WHOperation
    
    - (void)main {
        for (int i = 0; i < 3; i++) {
            NSLog(@"NSOperation的子类WHOperation======%@",[NSThread currentThread]);
        }
    }
    
    @end
    
    /*****************回到主控制器使用WHOperation**********************/
    
    - (void)testWHOperation {
        WHOperation *operation = [[WHOperation alloc] init];
        [operation start];
    }
    
    
  • NSOperation+NSOperationQueue,将任务加入队列执行

    - (void)testOperationQueue {
        // 创建队列,默认并发
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
        // 创建操作,NSInvocationOperation
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil];
    
        // 创建操作,NSBlockOperation
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"addOperation把任务添加到队列======%@", [NSThread currentThread]);
            }
        }];
    
        [queue addOperation:invocationOperation];
        [queue addOperation:blockOperation];
    }
    
    - (void)invocationOperationAddOperation {
        NSLog(@"invocationOperation===aaddOperation把任务添加到队列====%@", [NSThread currentThread]);
    }
    
  • setSuspended/cancel/cancelAllOperations
    暂停和取消不是立刻取消当前操作,而是等当前的操作执行完之后不再进行新的操作。

  • 操作依赖,当OperationA结束的时候,才开始执行OperationB

  • 操作依赖实现代码

    - (void)testAddDependency {
    // 并发队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 操作1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"operation1======%@", [NSThread currentThread]);
        }
    }];
    
    // 操作2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****");
    
        for (int i = 0; i < 3; i++) {
            NSLog(@"operation2======%@", [NSThread currentThread]);
        }
    }];
    
    // 使操作2依赖于操作1
    [operation2 addDependency:operation1];
    
    // 把操作加入队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    }
    
    

锁概念介绍

锁用于解决线程争夺资源的问题,一般分为两种,自旋锁(spin)和互斥锁(mutex)。

  • 互斥锁可以解释为线程获取锁,发现锁被占用,就向系统申请锁空闲时唤醒他并立刻休眠。

  • 自旋锁比较简单,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。

  • 自旋锁应用场景
    OC中实例变量的set/get,原子操作的颗粒度最小,只限于读写,对于性能的要求很高,如果使用了互斥锁势必在切换线程上耗费大量资源。相比之下,由于读写操作耗时比较小,能够在一个时间片内完成,自旋更适合这个场景。

  • 自旋锁的坑
    新版iOS中,系统维护了5个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,导致低优先级线程迟迟不能处理资源并释放锁,导致陷入死锁,从而破坏了spin lock。

  • 下面是苹果spinlock_t实现,系统中自旋锁已经全部改为互斥锁(os_unfair_lock)实现了,只是名称一直没有更改,坑货。

    using spinlock_t = mutex_tt<LOCKDEBUG>;
    using mutex_t = mutex_tt<LOCKDEBUG>;
    using monitor_t = monitor_tt<LOCKDEBUG>;
    using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
    
    // Use fork_unsafe_lock to get a lock that isn't
    // acquired and released around fork().
    // All fork-safe locks are checked in debug builds.
    struct fork_unsafe_lock_t {
        constexpr fork_unsafe_lock_t() = default;
    };
    extern const fork_unsafe_lock_t fork_unsafe_lock;
    
    #include "objc-lockdebug.h"
    
    template <bool Debug>
    
    class mutex_tt : nocopy_t {
        os_unfair_lock mLock;
    
    public:
    constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
        lockdebug_remember_mutex(this);
    }
    
    constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
    
    void lock() {
        lockdebug_mutex_lock(this);
    
        os_unfair_lock_lock_with_options_inline(&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION);
    }
    
    void unlock() {
        lockdebug_mutex_unlock(this);
    
        os_unfair_lock_unlock_inline(&mLock);
        }
    }
    

MacOS开发中用到的锁

互斥锁

atomic
  • reallySetProperty方法实现
    从源码可以看到,atomic实现是spinlock_t,而spinlock_t从上文看到内部实现是互斥锁

    static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
    {
        if (offset == 0) {
            object_setClass(self, newValue);
            return;
        }
    
        id oldValue;
        id *slot = (id*) ((char*)self + offset);
    
        if (copy) {
            newValue = [newValue copyWithZone:nil];
        } else if (mutableCopy) {
            newValue = [newValue mutableCopyWithZone:nil];
        } else {
            if (*slot == newValue) return;
                newValue = objc_retain(newValue);
        }
    
        if (!atomic) {
            oldValue = *slot;
            *slot = newValue;
        } else {
            spinlock_t& slotlock = PropertyLocks[slot];
            slotlock.lock();
            oldValue = *slot;
            *slot = newValue;
            slotlock.unlock();
        }
    
        objc_release(oldValue);
    }
    
synchronized
  • 锁实现代码
    从源码可以看到,传入nil,不会上锁,会以传入的obj的地址为key,绑定一个互斥锁,当obj改变时,互斥锁失效

    // Begin synchronizing on 'obj'.
    // Allocates recursive mutex associated with 'obj' if needed.
    // Returns OBJC_SYNC_SUCCESS once lock is acquired.
    int objc_sync_enter(id obj)
    {
        int result = OBJC_SYNC_SUCCESS;
        
        if (obj) {
            SyncData* data = id2data(obj, ACQUIRE);
            assert(data);
            data->mutex.lock();
        } else {
            // @synchronized(nil) does nothing
            if (DebugNilSync) {
                _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
            }
    
            objc_sync_nil();
        }
    
        return result;
    }
    
  • @synchronized(self)的坑
    加锁对象对外可见(self),被外部使用@synchronized (objectA)导致死锁(class A,class B分别得到了一个锁),正确的做法是传入一个类内部维护的NSObject对象,而且这个对象是对外不可见的。

    //class A
    @synchronized (self) {
        [_sharedLock lock];
        NSLog(@"code in class A");
        [_sharedLock unlock];
    }
    
    //class B
    [_sharedLock lock];
    @synchronized (objectA) {
        NSLog(@"code in class B");
    }
    
    [_sharedLock unlock];
    

信号量

互斥量值只能为0/1,信号量值可以为非负整数。 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到

  • 信号量使用代码
    dispatch_queue_t workConcurrentQueue = dispatch_queue_create("cccccccc", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t serialQueue = dispatch_queue_create("sssssssss",DISPATCH_QUEUE_SERIAL);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);//信号量初始为3
    
    for (NSInteger i = 0; i < 10; i++) {
        dispatch_async(serialQueue, ^{
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//将信号量减少1,信号量如果等于0,则等待
            dispatch_async(workConcurrentQueue, ^{
                NSLog(@"thread-info:%@开始执行任务%d",[NSThread currentThread],(int)i);
                sleep(1);
                NSLog(@"thread-info:%@结束执行任务%d",[NSThread currentThread],(int)i);
                dispatch_semaphore_signal(semaphore);});//将信号量增加1
            });
    }
    
    NSLog(@"主线程...!");
    

引用