阅读 328

OS开发基础——多线程的简单应用

小记

目前最广泛被认知的几种多线程有:

  • pthread
  • NSThread
  • GCD
  • NSOperation

其中常用的是后面两种:GCD 和 NSOperation,这边文章就是对后两种常见用法的简单介绍,以及前两种 pthread 和 NSThread 的简单说明。

pthread

一套通用的多线程API,适用于Unix\Linux\Windows等系统,跨平台,可移植性强,由纯C语言编写的API,且线程的生命周期需要程序员自己管理,使用难度较大,所以在实际开发中通常不使用,在这里也不详细说明。

注意:在使用pthread的时候一定要手动把当前线程结束掉。如果有想从底层进行定制多线程的操作,可以使用ptherad。

NSThread

基于OC语言由苹果进行封装的API,使得其简单易用,完全面向对象操作。线程的声明周期由程序员管理,在实际开发中偶尔使用。

简单使用:

创建线程:
  • 使用NSThread的init方法显式创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod) object:nil];
//线程名
[thread setName:@"thread"];
//优先级 优先级从0到1
[thread setThreadPriority:0.9];
//启动
[thread start];
复制代码
  • 使用NSThread类方法显式创建并启动线程
[NSThread detachNewThreadSelector:@selector(threadMethod:) toTarget:self withObject:nil];
复制代码
  • 隐式创建并启动线程
[self performSelectorInBackground:@selector(threadMethod:) withObject:nil];
复制代码

注意:添加线程的名字、更改优先级等操作,要使用第一种方式来创建线程。因为只有使用NSThread的init方法创建的线程才会返回具体的线程实例,此方法需要使用start方法来手动启动线程。

线程状态:
  • 启动
[thread start];
复制代码
  • 阻塞
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[NSThread sleepForTimeInterval:1];
复制代码
  • 结束(注意:当使用cancel方法时,只是改变了线程的状态标识,并不是结束线程,要配合isCancelled方法进行判断,退出线程使用)
[[NSThread currentThread] cancel];
if([[NSThread currentThread] isCancelled]) {
    [NSThread exit];//执行exit,后边的语句不再执行,可以通过 start 再次启动线程
}
if([[NSThread currentThread] isCancelled]) {
    return;//后边的语句不再执行,不可以通过 start 再次启动线程
}
复制代码
线程通讯:

线程间通信,最常用的就是开启子线程进行耗时操作,操作完毕后回到主线程,进行数据赋值以及刷新主线程UI。

[self performSelectorOnMainThread:@selector(backToMainThread:) withObject:image waitUntilDone:YES];
复制代码
[self performSelector:@selector(backToMainThread:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
复制代码
线程安全:

多线程操作会存在一定的安全隐患。原因是多线程会存在不同线程的资源共享,也就是说我们可能在同一时刻两个线程同时操作了某一个变量的值(资源竞争),但是线程的对变量的操作不同,导致变量的值出现误差。

例如:如果有一个变量x = 100,有两个线程A和B,A线程取x的值(x=100),B线程取x的值(x=100),B线程给x+1 (x=101),A线程给x+1 (x = 101),B 线程取x的值 (x = 101)或者( x = 102 )。变量出行了误差。

解决方案添加线程锁,有多种线程锁,在这里不多介绍。

iOS开发基础——线程安全(进程锁)

GCD

基于C语言编写由苹果公司提供的的一套多线程开发解决方案,使用时会以函数形式出现,且大部分函数以dispatch开头。它会自动利用多核进行并发处理和运算,它能提供系统级别的处理,而不再局限于某个进程、线程,线程的生命周期由系统自动管理(创建,调度、运行,销毁),只需要告诉GCD执行什么任务,不需要编写管理线程的代码。

基本使用

创建列队和任务:

创建列队(queue)
  • 串行列队(一次执行一个任务)
dispatch_queue_t queue = dispatch_queue_create("10900900",DISPATCH_QUEUE_SERIAL);
复制代码
  • 并发列队(一次可执行多个任务)
dispatch_queue_t queue = dispatch_queue_create("10900901",DISPATCH_QUEUE_CONCURRENT);
复制代码
  • 全局列队(本质是一个并发队列,由系统提供,所有应用程序共享的,方便编程,可以不用创建就直接使用)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
复制代码
  • 主列队(专门调度主线程的任务,不开辟新的线程。在主队列下的任务不管是异步还是同步都不会开辟新线程,任务只会在主线程顺序执行)
dispatch_queue_t queue = dispatch_get_main_queue();
复制代码
创建任务(sync 和 async)
  • 同步任务(同步串行和同步并发,任务执行的方式是一样的,没有开辟新的线程,所有的任务都是在一条线程里面执行。)
dispatch_sync(queue, ^{
});
复制代码
  • 异步任务(异步串行和异步并发,任务执行的方式是有区别的,异步串行会开辟一条新的线程,队列中所有任务按照添加的顺序一个一个执行,异步并发会开辟多条线程,至于具体开辟多少条线程,是由系统决定的。)
dispatch_async(queue, ^{
});
复制代码
列队和任务的组合各种情况分析:
  • 同步任务,并发列队
  • 所有任务都是在当前线程中执行,没有开启新的线程(同步方法不具备开启新线程的能力)
  • 同步任务需要等候列队中的任务执行结束,才会执行下一个
  • 并发列队可以开启多线程,并且可以同时执行多个任务,但是同步任务无法创建新线程,所以只有当前一个线程,而且同步任务需要等待列队中前一任务执行结束才能继续执行下面的操作,因此任务只能一个一个顺序执行
  • 同步任务,串行列队
  • 和同步任务,并发列队相似
  • 所有任务在当前线程中执行,没有开启新的线程
  • 任务是按照顺序执行的,同步任务,线程需要等待列队中的任务执行完毕,才可以开启新的任务
  • 同步任务,主列队
  • 在主线程中调用会出现死锁,互相等待
  • 死锁原因:当我们在主线程中添加这个列队的时候,添加列队的这个操作本身就是一个任务,我们把它当作任务A,这个任务也被添加到了主线程的列队中。而同步任务,会等待当前列队中前面的任务执行完毕后接着执行,我们把添加到主线程中的列队中的任务称为任务B,这就产生了一个矛盾,任务B要执行需要等任务A执行完毕后才会执行,而任务A执行完毕需要任务B执行结束(因为任务B在任务A中),这就产生了任务互相等待的情况
  • 异步任务,并发列队
  • 有几个异步任务就开启了几个新的线程,任务也是同时执行的(异步方法具备开启新线程的能力,可以同时执行多个任务)
  • 异步执行,当前线程不等待,直接开启新的线程来执行,在新线程中执行任务(异步任务,添加异步任务的线程不做等待,可继续执行别的任务)
  • 异步任务,串行列队
  • 开启了一条新的线程来执行异步任务(异步任务可以开启新线程,串行列队只能开启一个线程)
  • 线程不会等待任务执行完毕,任务的执行是按照顺序来的,每次只有一个任务被执行,任务一个接一个的执行下去
  • 异步任务,主列队
  • 没有开启新线程,所有任务都是在主线程中执行的(虽然异步任务有开启新线程的能力,但因为是在主列队,所以无法开启新线程,所有任务都在主线程中执行)
  • 由于只有一个线程可以使用,所以所有任务都是按顺序一个个执行的,一个完毕,执行下一个

线程间的通信

线程间的通讯比较常用的就是在其他线程获取数据,然后返回主线程刷新UI界面

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
    // 异步追加任务
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              
       NSLog(@"1---%@",[NSThread currentThread]);     
    }
    // 回到主线程
    dispatch_async(mainQueue, ^{
        [NSThread sleepForTimeInterval:2];               
        NSLog(@"2---%@",[NSThread currentThread]);      
    });
});
复制代码

进阶使用

GCD栅栏

有时我们需要异步执行两组操作分别为A组和B组,当A组完成后,再执行B组操作,因此我们需要把把两组操作分割开来。这时可用dispatch_barrier_async方法来实现,在添加两组任务之间添加一个分栏,函数会先把分栏前添加的任务执行完毕之后,在把分栏后的任务添加到队列中

dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_barrier_async(queue, barrierBlk);
dispatch_async(queue, blk3);
dispatch_async(queue, blk4);
复制代码

栅栏函数也可以执行队列上的操作(参数列表中有queue和block),也有对应的 dispatch_barrier_sync 函数。

栅栏函数中传入的参数队列必须是由 dispatch_queue_create 方法创建的队列,否则,与dispatch_async无异,起不到“栅栏”的作用了,对于dispatch_barrier_sync也是同理。

栅栏函数之前和之后的操作执行顺序都不固定,但是前面三个必然先执行,然后再执行栅栏函数中的操作,最后执行后面的三个

dispatch_barrier_syncdispatch_barrier_async 的区别:

  • 当栅栏前后添加的都是同步任务,两者没有区别,按照顺序依次执行
  • 当栅栏前后添加的是异步任务,sync 会先执行栅栏前的任务,然后不等待栅栏后部任务。async栅栏前后的任务都不等待

由此可见sync和async对于栅栏函数的区别作:dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们。dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务

GCD延迟

当遇到要求在指定时间后执行代码(例如5秒后执行代码),可用dispatch_after来实现,需要注意的是这个并不严谨,这个是指在指定时间后,再把代码加入列队中去,并不是严格的在多少时间后开始执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(second * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // second秒后异步追加任务代码到主队列,并开始执行
    NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
});
复制代码

GCD一次性代码

在创建单例,或者有代码要求在整个程序的运行过程中之执行一次的话可以使用GCD中的dispatch_once 函数,这个函数保证即使在多线程的环境下也可以保证只调用一次,保证线程安全

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
    });
}
复制代码

GCD快速迭代

快速迭代函数dispatch_apply按照指定的次数把指定的任务加入到指定的列队中去,并等待全部的任务执行完毕后,结束

- (void)applyTime:(NSInteger)time {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"apply---begin");
    dispatch_apply(time, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}
复制代码

注意:由于是在并发列队中异步执行,所以里面的执行完成顺序不固定

GCD信号量

GCD中的信号量,是一种持有计数的信号,计数为0时,不可通过,要等待。计数为1或大于1时,可通过,不需等待。

三个函数来完成信号量的操作
  • dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加 1
  • dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
实际开发中的作用:
  • 保持线程同步,使异步执行的任务转化为同步执行
  • 保护线程安全,为线程加锁

例如:当异步执行耗时操作时,需要使用该异步操作的结果进行一些额外的操作,例如:同时进行两个异步操作A和B,执行B的时候,需要对A的运行结果来进行操作,这个时候就可以对B加一个信号量,让B等待,当A执行完毕后,对B操作发送信号,继续执行B操作

- (void)semaphoreSync {
    NSLog(@"semaphore---begin");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建初始信号量 为 0 ,阻塞所有线程
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务A
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        number = 100;
        // 执行完线程,信号量加 1,信号总量从 0 变为 1
        dispatch_semaphore_signal(semaphore);
    });
    //原任务B
    ////若计数为0则一直等待,直到接到总信号量变为 >0 ,继续执行后续代码
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %d",number);
}
复制代码

例如:线程安全方面:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。(可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。)

GCD队列组

有时候会遇到需要异步执行两个耗时任务,然后当两个任务都执行完毕后,在回到主线程执行任务,这时候我们可以用GCD的队列组的功能来实现这个要求

GCD队列组常用函数:
  • 调用队列组的 dispatch_group_async 先把任务放到列队中,然后把列队放入到列队组中。也可用dispatch_group_enterdispatch_group_leave 两个组合来实现 dispatch_group_async
  • dispatch_group_enter:加入,一个任务追加到group,执行一次,相当于group中的未执行任务加1
  • dispatch_group_leave:离开,一个任务离开了group,执行一次,相当于group中的未执行任务减1

当group中的未执行完毕的任务数为0的时候才会执行dispatch_group_notify中的任务,以及不会使dispatch_group_wait堵塞当前线程(类似于信号量)

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_sync(queue, ^{
    // 追加任务A
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    }
    dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
复制代码
  • 调用队列组的 dispatch_group_notify 回到指定线程执行任务(监听group中的所有任务的完成状态,当所有任务都完成后,把notify中的任务添加到group中,并执行任务)
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务A
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
    }
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务B
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    }
});
    
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 追加任务Main
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"main---%@",[NSThread currentThread]);      // 打印当前线程
    }
});
复制代码
  • 调用队列组的 dispatch_group_wait 回到当前线程继续向下执行(暂停当前线程中的操作,阻塞当前线程,执行wait中的group操作,执行完后,继续执行当前线程中的操作)
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 追加任务A
    for (int i = 0; i < 2; ++i) {
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
    }
});
//执行A任务,执行完成后继续执行该线程后续任务
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"current---%@",[NSThread currentThread]);
复制代码

NSOperation

基于OC语言的API,底层是GCD,增加了一些更加简单易用的功能,使用更加面向对象。线程生命周期由系统自动管理,在实际开发中经常使用。

优势:

  1. 可以添加代完成的代码块,在操作完成后执行
  2. 添加操作之间的依赖关系,控制执行顺序
  3. 设定操作执行的优先级
  4. 可以很方便的取消一个操作的执行
  5. 使用KVO观察对象操作执行状态的更改:isExecuteing(执行) isFinished(结束) isCancelled(取消)

两个重要概念操作和操作队列:

NSOperation(操作):

  • 线程中执行的操作代码
  • 与GCD不同,GCD是放在block块中。在NSOperation中,可以使用它的子类NSInvocationOperationNSBlockOperation来实现,也可以自定义子来封装操作

NSOperationQueue(队列):

  • 这个列队不同于GCD中的列队中先进先出的原则,这里的列队对于添加到列队中的操作首先进入准备就绪的状态(这个根据操作之间的依赖关系来决定),然后进可以开始执行的操作的执行顺序要先按照操作之间的相对的优先级来决定,然后再根据进入的顺序决定执行顺序
  • 操作的队列通过设置 最大并发操作数 (maxConcurrentOperationCount)来控制并发和串行
  • NSOperationQueue提供了两种不同的列队,主列队和自定义列队。主列队运行在主线程之上,自定义列队在后台执行。

基本使用:

创建操作:

  • 使用子类 NSInvocationOperation
-(void)useNSInvocationOperation{
    NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTest) object:nil];
    [op start];
}
复制代码

是在没有使用NSOperationQueue的情况下,在主线程中单独使用子类执行一个操作,操作是在当前线程执行的,并没有开启新线程,想要在其他线程来执行这个操作的话可以使用:

[NSThread detachNewThreadSelector:@selector(useNSInvocationOperation) toTarget:self withObject:nil];
复制代码
  • 使用子类 NSBlockOperation
-(void)useNSBlockOperation{
    NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^  {
        for (NSInteger i = 0 ; i < 2 ; i ++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1---%@", [NSThread currentThread]);
        }
    }];
    [op start];
}
复制代码

和上一个一样也是在当前线程调用,想要在其他线程使用的话可以:

[NSThread detachNewThreadSelector:@selector(useNSBlockOperation) toTarget:self withObject:nil];
复制代码

注意:NSBlockOperation有方法可以添加额外的操作addExecutionBlock:

[op addExecutionBlock:^{
    for (NSInteger i = 0 ; i < 2 ; i ++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }
}];
复制代码

可以在不同的线程中并发执行,只有所有添加的操作全部完成,才视为这个block操作完成。一般情况下,一个operation对象如果封装了多个操作,是否开启新线程来执行这些操作,取决于操作的个数,由系统来决定是否开启新线程。

  • 使用自定义子类继承自NSOperation

自定义的子类可以通过重写 main 或者 start 方法来自定义操作对象 重写 main 方法比较简单,不用管理状态属性 isExecuting 等。

-(void)useCustomOperation{
    JMOperation * op = [[JMOperation alloc] init];
    [op start];
}
复制代码

操作的具体实现写在重写的类中,方便管理和统一修改。

创建列队:

  • 主列队
    • 凡是添加的主列队中的操作都在主线程中执行
    NSOperationQueue * queue = [NSOperationQueue mainQueue];
    复制代码
  • 自定义列队
    • 添加到自定义列队中的,自动放在子线程执行,后台执行
    • 同时包含了多种功能:串行,并发
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    复制代码

加入队列:

  • 将创建好的操作加入到列队中
    -(void)createCustomQueue{
        NSOperationQueue * queue = [[NSOperationQueue alloc] init];
        NSInvocationOperation * op_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationTest) object:nil];
        NSBlockOperation * op_2 = [NSBlockOperation blockOperationWithBlock:^  {
            for (NSInteger i = 0 ; i < 2 ; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2---%@", [NSThread currentThread]);
            }
        }];
        [op_2 addExecutionBlock:^{
            for (NSInteger i = 0 ; i < 2 ; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"3---%@", [NSThread currentThread]);
            }
        }];
        [queue addOperation:op_1]; // 操作加入队列
        [queue addOperation:op_2];
    }
    复制代码
  • 直接在列队中创建操作
    -(void)createMainQueue{
        NSOperationQueue * queue = [NSOperationQueue mainQueue];
        // 队列加入block方法
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
             NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
            }
        }];
    }
    复制代码

进阶使用:

NSOperationQueue控制串行执行,并发执行:

关键词(maxConcurrentOperationCount),最大并发操作数,用来控制一个列队中可以有多少个操作同时并发执行

注意:这个数值控制的并不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数,一个操作并非只能在一个线程中运行。

开启线程的数量由系统决定,无法人为管理。 queue.maxConcurrentOperationCount = 4;

  • 默认为-1,表示不限制,可以进行并发执行
  • 值为1时,表示只可串行执行
  • 大于1时,表示并发执行,不可超过系统限制

NSOperation操作依赖:

NSOperation的独有功能,操作依赖,通过操作依赖,我们可以根据设置的依赖关系,很方便的控制操作的执行顺序,NSOperation提供3个接口供我们管理和查看依赖

  • [op_1 addDependency:op_2];
  • [op_1 removeDependency:op_2];
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies;

上面的三个操作分别是添加依赖,移除依赖,获取当前操作所依赖的所有操作的数组

NSOperation优先级:

NSOperation提供了 queuePriority (优先级)属性。queuePriority 属性适用于同一操作列队中的操作,不适用于不同操作列队中的操作。默认情况下,所有操作对象优先级都是 NSOperationQueuePriorityNormal 。但是我们可以通过setQueuePriority: 方法来改变当前操作在同一个列队中的优先级:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
 	NSOperationQueuePriorityVeryLow = -8L,
 	NSOperationQueuePriorityLow = -4L,
 	NSOperationQueuePriorityNormal = 0,
 	NSOperationQueuePriorityHigh = 4,
 	NSOperationQueuePriorityVeryHigh = 8
};
复制代码

注意:

  • 依赖关系 优先于 优先级 属性,先判断依赖关系,再执行优先级的顺序
  • 当属于同一个依赖关系时,优先级高的先执行。
  • 优先级只是确保执行顺序,无法保证执行完成顺序

NSOperation 和 NSOperationQueue线程间的通信:

NSOperationQueue * queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
    for (NSInteger i = 0 ; i < 2 ; i ++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }
}];
    
    //返回主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    // 进行一些 UI 刷新等操作
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
复制代码

通过[NSOperationQueue mainQueue]函数返回主线程的操作

NSOperation 和 NSOperationQueue 的线程安全和线程锁:

多线程有多种枷锁方式来保护线程安全,常用的是NSLock,通过NSLock来给进程加锁,解锁。加锁后其他进程无法再访问这个方法和属性

  • NSLock * lock = [[NSLock alloc] init];
  • [lock lock];
  • [lock unlock];

初始化lock方法,加锁,解锁方法

某个A线程调用lock方法,这样,nslock将被上锁,可以执行被锁住的关键方法,完成后A线程调用unlock方法解锁。如果在A线程调用unlock方法之前,有B线程需要调用被锁住的关键方法,那么将无法访问,一直等待,直到A线程调用了unlock方法。

其他还有多种锁的方式,如:自旋锁,互斥锁,递归锁,条件锁,读写锁等,具体进程锁在下篇文章再来介绍。iOS开发基础——线程安全(进程锁)

关注下面的标签,发现更多相似文章
评论