IOS之多线程基础(OC)

基本概念理解

并行和并发

并发和并行是即相似又有区别(微观概念)

  • 并行:指两个或者多个事件在同一时刻点发生

  • 并发:指两个或则多个事件在同一时间段内发生

  • 在操作系统中,在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单CPU系统中,每一时刻却仅能有一道程序执行(时间片),故微观上这些程序只能是分时地交替执行。倘若计算机系统中有多个CPU,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行,因为是微观的,所以大家在使用电脑的时候感觉就是多个程序是同时执行的。

  • 线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度时间片即CPU分配给各个程序的运行时间(很小的概念).

进程和线程

进程

  • 进程: 计算机系统中运行的一个应用程序,每个进程是独立的。
  • 多线程:操作系统中同时运行多个程序

线程

  • 线程:一个进程想要执行任务,必须有一个线程,而且每一个进程中必有至少有一个线程,进程中的所有任务都在线程中执行
  • 多线程:在同一个进程中同时运行多个任务
  • 线程的执行顺序:一个线程中的任务都是串行执行的。一个线程中有多个任务时,只能按照顺序一个一个来执行,同一个时刻,一个线程只能执行一个任务

进程和线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间) 是独立的,至少有一个线程
  • 线程:堆空间是共享的,占空间是独立的,线程的消耗资源也比进程小,相互之间可以影响,又称为轻型进程或进程元。

因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器,程序员是控制不了的。
我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性

线程调度

  • 计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令.
    所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务.

多线程

  • IOS开发多线程的3种常用方式

NSThread

  • NSThread有两种创建方法,一种是类方法,一种是实例方法。类方法创建的时候需要我们确定好执行的任务,没有任何返回

类方法创建NSThread

/// 使用类方法来创建NSThread
- (void)creatThread {
    //1. block方式
    if (@available(iOS 10.0, *)) {
        //这个类创建线程方法  IOS版本 >= 10.0
        [NSThread detachNewThreadWithBlock:^{
            NSLog(@"detachNewThreadWithBlock ==current thread =====%@",[NSThread currentThread]);
        }];
    } else {
        // Fallback on earlier versions
        //SEL创建方式
        [NSThread detachNewThreadSelector:@selector(runThread) toTarget:self withObject:nil];
    }
}

- (void)runThread{
    NSLog(@"detachNewThreadSelector ===current thread =====%@",[NSThread currentThread]);
}

打印结果:
在这里插入图片描述
打印当前线程,输出结果是新开一个线程

实例方法创建NSThread

- (void)creatThread2 {
    //如果是实例方法创建的NSThread对象,我们需要手动调用方法[thread start]来启动线程
    if (@available(iOS 10.0, *)) {
    //blcok创建方式
        NSThread *thread = [[NSThread alloc] initWithBlock:^{
            NSLog(@"initWithBlock == current thread====%@",[NSThread currentThread]);
        }];
        [thread start];
    }else {
        // Fallback on earlier versions
        //SEL创建方式
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
        [thread start];
    }
}

打印结果:
在这里插入图片描述

  • NSThread除了创建的两种方式,还有许多其他的方法 例如:
	[thread setName:@"设置线程的名称"];
    //设置线程的优先级,由0 到 1 的浮点数来制定,其中1.0是最高优先级。
    [thread setThreadPriority:1];
    //推出当前线程
    [NSThread exit];
    //使当前线程睡眠-> 单位是秒
    [NSThread sleepForTimeInterval:1];
    //使当前线程沉睡直到某个时间
    [NSThread sleepUntilDate:[[NSDate alloc] init]];
    //判断是否在主线程
    [NSThread isMainThread]

实例

接下来我们使用经典的买票问题来模拟并解决NSthread中的线程同步问题,有两个售票员开始卖票,一共20张票,模拟该场景:

- (void)exampleThread {
    //1.首先我们设置总票数
    self.sumCount = 10;
    
    //2。创建两个售票员在卖票
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        [NSThread detachNewThreadWithBlock:^{
            while (weakSelf.sumCount > 0) {
                [NSThread sleepForTimeInterval:1];
                weakSelf.sumCount--;
                NSLog(@"还有%d张票",weakSelf.sumCount);
            }
        }];
        
        [NSThread detachNewThreadWithBlock:^{
            while (weakSelf.sumCount > 0) {
                [NSThread sleepForTimeInterval:1];
                weakSelf.sumCount --;
                
                NSLog(@"还有%d张票",weakSelf.sumCount);
            }
        }];
        
    } else {
        // Fallback on earlier versions
    }
}

打印结果:
在这里插入图片描述
结果出现了 相同的票数,很明显这个结果是有问题的,这 是因为同一张票是被卖出了两次。为了避免这种情况,通常的做法就是 给线程上锁,当某一条线程对数据操作时,先给数据上锁,别的线程阻塞,等到这条线程操作结束,在开锁,别的线程在进去,上锁,操作,开锁…这样就保证了数据的操作。

@synchornized(object){}

我们可以使用@synchornized(object){}来上锁,括号里面可以填写任意对象,但要注意的是,必须填写线程共有的变量才能实现上锁,局部变量是无效的,原因是如果使用局部变量,就会创建多个锁,这些锁之间并无关联,所以与不上锁没有区别

- (void)exampleThread {
    //1.首先我们设置总票数
    self.sumCount = 10;
    
    //2。创建两个售票员在卖票
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        [NSThread detachNewThreadWithBlock:^{
        //上锁
            @synchronized (weakSelf) {
                while (weakSelf.sumCount > 0) {
                    [NSThread sleepForTimeInterval:1];
                    weakSelf.sumCount--;
                    NSLog(@"还有%d张票",weakSelf.sumCount);
                }
            }
        }];
        
        [NSThread detachNewThreadWithBlock:^{
        //上锁
            @synchronized (weakSelf) {
                while (weakSelf.sumCount > 0) {
                    [NSThread sleepForTimeInterval:1];
                    weakSelf.sumCount --;
                    
                    NSLog(@"还有%d张票",weakSelf.sumCount);
                }                
            }
        }];
        
    } else {
        // Fallback on earlier versions
    }
}

打印结果:
在这里插入图片描述
因为我们上了锁,在锁内进行数据操作时,其他线程都会阻塞在外面,这个时候其实线程并不是并发执行的,所以我们不难想到,所内执行的任务越少,那么这段代码执行的效率就越高。在此基础上,我们可以对前面的枷锁进行一个小小的修改

- (void)exampleThread {
    //1.首先我们设置总票数
    self.sumCount = 10;
    
    //2。创建两个售票员在卖票
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        [NSThread detachNewThreadWithBlock:^{
//            @synchronized (weakSelf) {
//                while (weakSelf.sumCount > 0) {
//                    [NSThread sleepForTimeInterval:1];
//                    weakSelf.sumCount--;
//                    NSLog(@"还有%d张票",weakSelf.sumCount);
//                }
//            }
            while (true) {
                [NSThread sleepForTimeInterval:1];
                @synchronized (self) {
                    if (weakSelf.sumCount < 1) {
                        break;
                    }
                    self.sumCount --;
                    NSLog(@"还有%d张票",weakSelf.sumCount);
                }
            }
        }];
        
        [NSThread detachNewThreadWithBlock:^{
//            @synchronized (weakSelf) {
//                while (weakSelf.sumCount > 0) {
//                    [NSThread sleepForTimeInterval:1];
//                    weakSelf.sumCount --;
//
//                    NSLog(@"还有%d张票",weakSelf.sumCount);
//                }
//            }
            while (true) {
                [NSThread sleepForTimeInterval:1];
                @synchronized (self) {
                    if (weakSelf.sumCount < 1) {
                        break;
                    }
                    self.sumCount --;
                    NSLog(@"还有%d张票",weakSelf.sumCount);
                }
            }
            
        }];
        
    } else {
        // Fallback on earlier versions
    }
}

打印结果:
在这里插入图片描述
通过比对两次的买票打印时间,我们发现 我们修改后的卖票效率几乎提升了一倍

NSLOCK

当然我们也可以用NSLock来进行上锁,使用NSLock需要创建一个NSLockd 的实例,然后调用lock() 和 unlock() 两个方法来进行就锁和解锁的操作

- (void)exampleThread2 {
    //1.首先我们设置总票数
    self.sumCount = 10;
    self.lock = [[NSLock alloc] init];
    
    //2。创建两个售票员在卖票
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
        [NSThread detachNewThreadWithBlock:^{
            
            while (true) {
                [NSThread sleepForTimeInterval:1];
                [weakSelf.lock lock];
                if (weakSelf.sumCount < 1) {
                    break;
                }
                self.sumCount --;
                NSLog(@"还有%d张票",weakSelf.sumCount);
                [weakSelf.lock unlock];
            }
        }];
        
        [NSThread detachNewThreadWithBlock:^{
            while (true) {
                [NSThread sleepForTimeInterval:1];
                [weakSelf.lock lock];
                if (weakSelf.sumCount < 1) {
                    break;
                }
                self.sumCount --;
                NSLog(@"还有%d张票",weakSelf.sumCount);
                [weakSelf.lock unlock];
            }
        }];
        
    } else {
        // Fallback on earlier versions
    }
}

打印结果:
在这里插入图片描述

结果输出和上面使用@synchronized(object){} 加锁一样的效果。
不知道你还记得不记得atomic,这个就修饰了属性的原子性,如果直接把属性修饰改为atomic,会不会就不需要我们加锁了呢?我试过,不行!这是因为atomic只会对该属性的Getter和Setter方法上锁,而我们很显然是在别的方法里面对数据进行操作,所以并没什么卵用。同时也因为atomic太耗性能,所以在实际开发中,我们一般都不使用它来修饰变量

GCD

  • GCD(Grand Central Dispatch): 翻译过来是宏伟的中枢调度,是一种基于C语言的并发编程技术。它是苹果为多核的并行运算提出的解决方案,会自动调度系统资源,所以效率很高。GCD并不直接操作线程,而是操作队列和任务,我们只需要把任务添加到队列里,然后指定任务的执行方式,GCD就会自动调度线程执行任务。
  • GCD的队列都是以block的形式存在的,队列有两种: 串行队列/并行队列

死锁

死锁就是队列引起的循环等待

一个比较常见的死锁例子: 主队列同步

- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_sync(dispatch_get_main_queue(), ^{
       
        NSLog(@"deallock");
    });
    // Do any additional setup after loading the view, typically from a nib.
}

在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
同步对于任务是立刻执行的,那么当把任务放进主队列时,它就会立马执行,只有执行完这个任务,viewDidLoad才会继续向下执行。
而viewDidLoad和任务都是在主队列上的,由于队列的先进先出原则,任务又需等待viewDidLoad执行完毕后才能继续执行,viewDidLoad和这个任务就形成了相互循环等待,就造成了死锁。
想避免这种死锁,可以将同步改成异步dispatch_async,或者将dispatch_get_main_queue换成其他串行或并行队列,都可以解决。

同样下面的代码也会造成死锁:

dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

dispatch_async(serialQueue, ^{
       
        dispatch_sync(serialQueue, ^{
            
            NSLog(@"deadlock");
        });
    });

外面的函数无论是同步还是异步都会造成死锁。
这是因为里面的任务和外面的任务都在同一个serialQueue队列内,又是同步,这就和上边主队列同步的例子一样造成了死锁
解决方法也和上边一样,将里面的同步改成异步dispatch_async,或者将serialQueue换成其他串行或并行队列,都可以解决

    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
       
        dispatch_sync(serialQueue2, ^{
            
            NSLog(@"deadlock");
        });
    });

GCD的执行顺序

串行队列先异步后同步:
    dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    
    NSLog(@"1");
    
    dispatch_async(serialQueue, ^{
        
         NSLog(@"2");
    });
    
    NSLog(@"3");
    
    dispatch_sync(serialQueue, ^{
        
        NSLog(@"4");
    });
    
    NSLog(@"5");

打印顺序是13245
原因是:
首先先打印1
接下来将任务2其添加至串行队列上,由于任务2是异步,不会阻塞线程,继续向下执行,打印3
然后是任务4,将任务4添加至串行队列上,因为任务4和任务2在同一串行队列,根据队列先进先出原则,任务4必须等任务2执行后才能执行,又因为任务4是同步任务,会阻塞线程,只有执行完任务4才能继续向下执行打印5
所以最终顺序就是13245。
这里的任务4在主线程中执行,而任务2在子线程中执行。
如果任务4是添加到另一个串行队列或者并行队列,则任务2和任务4无序执行(可以添加多个任务看效果)

performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        [self performSelector:@selector(test:) withObject:nil afterDelay:0];
    });

这里的test方法是不会去执行的,原因在于

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

这个方法要创建提交任务到runloop上的,而gcd底层创建的线程是默认没有开启对应runloop的,所有这个方法就会失效
而如果将dispatch_get_global_queue改成主队列,由于主队列所在的主线程是默认开启了runloop的,就会去执行(将dispatch_async改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test方法也是会去执行的)。

队列和任务执行

  • 串行队列只能等一个任务完成之后,在执行下一个任务。
/**
     @param 参数1 队列的名称
     @param dispatch_queue_attr_t DISPATCH_QUEUE_SERIAL: 串行队列  DISPATCH_QUEUE_CONCURRENT:并发队列
     */
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
  • 并发队列可以同时调度多个任务
/**
    @param 参数1 队列的名称
    @param dispatch_queue_attr_t DISPATCH_QUEUE_SERIAL: 串行队列  DISPATCH_QUEUE_CONCURRENT:并发队列
    */
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
  • 同步执行会等待当前任务完成才会执行下一个任务,不会开启新的线程。
/** 
同步执行队列
参数: 1队列, blcok(任务)
*/
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)
  • 异步执行不会等待当前任务完成就会执行下一个任务,可以开启新的线程。(如果是在主队列,则不会开启新的线程,因为主队列都会在主线程执行)
/** 
异步执行队列
参数: 1队列, blcok(任务)
*/
dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, <#^(void)block#>)

队列和任务都有两种,排列组合以后就有四种情况,在不同的情况下,执行的结果可能会有差异,如果不清楚原理比较容易混淆。这里有一个简单的方法去分析执行情况:队列的类型决定了能不能同时执行多个任务串行队列一次只能执行一个任务,并发队列一次可以执行多个任务),执行的方式决定了会不会开启新线程同步执行不会开启新线程,异步执行可以开启新线程)。

接下来我们演示下4中情况

同步执行串行队列任务

//创建一个串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    
    //使用串行队列执行同步函数
    dispatch_sync(serialQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"current thread====%@  i=====%d",[NSThread currentThread],i);
        }
    });

结果:
在这里插入图片描述
上述结果可以看出,串行队列 + 同步函数 不会产生新的线程, 都是在主线程执行

异步执行串行队列任务

dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        dispatch_async(serialQueue, ^{
            NSLog(@"current thread====%@  i=====%d",[NSThread currentThread],i);
        });
    }

在这里插入图片描述
上述结果表示 串行队列 + 异步执行方式 会产生一个新的线程,并且是按照顺序执行的,因为是串行队列,任务只能一个一个按照顺序执行

同步执行并行队列任务

dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_sync(concurrentQueue, ^{
            NSLog(@"current thread====%@  i=====%d",[NSThread currentThread],i);
        });
    }

在这里插入图片描述

上述结果表示并行队列 + 同步函数 不会产生新的线程,是在主线程执行

异步执行并行队列任务

dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        dispatch_async(concurrentQueue, ^{
            NSLog(@"current thread====%@  i=====%d",[NSThread currentThread],i);
        });
    }

在这里插入图片描述

上述结果标书, 异步函数 + 并行队列 会产生新的线程,并且是没有顺序的执行的

全局队列 和主队列

系统也为我们提供了两种队列,分别是:全局队列dispatch_get_global_queue(long identifier, unsigned long flags)、主队列dispatch_get_main_queue()

全局队列

全局队列本质上是一个并发队列,第一个参数是队列是服务质量的选择(优先级), 第二个参数是保留参数暂时传0

 /**
     #define DISPATCH_QUEUE_PRIORITY_HIGH 2
     #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
     #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
     */
    //获取一个默认优先级的全局队列
    dispatch_queue_t systemQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

一般情况下队列默认优先级就可以了,优先级本质上是一个概率问题,优先级越高,CPU调度的概率越高,并不具备确定性。

主队列

主队列不需要参数可以直接获取

dispatch_queue_t mainQueue = dispatch_get_main_queue()

GCD常用函数

比如实际开发中我们需要同步数据,但是这些数据来自于不同的接口,只有所有的信息同步结束之后可以操作,同步信息的操作应该在子线程中进行,我们可以一步并发来执行这些好事操作,但是我们怎么知道所有的任务操作都执行完了?

GCD中Group的用法
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"异步队列1====%d",i);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"异步队列2====%d",i);
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"两个队列都执行完了---");
    });

在这里插入图片描述

还有另外一种方法:dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"异步队列1====%d",i);
        }
    });
    
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"异步队列2====%d",i);
        }
    });
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        NSLog(@"两个队列都执行完了---");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"可以做操作了---");
        });
    });

两种方法的运行结果是相同的,第二种更加灵活一些,但是代码相对较多一点。我们可以更好的对任务进行控制,并且在特定的场景满足我们的需求。灵活使用调度组,可以让我们对线程同步控制更加得心应手

Dispatch Semaphore(队列中的信号值)

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。

Dispatch Semaphore 提供了三个函数
1.dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量
2.dispatch_semaphore_signal:发送一个信号,让信号总量加1
3.dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
Dispatch Semaphore 在实际开发中主要用于:

保持线程同步,将异步执行任务转换为同步执行任务
保证线程安全,为线程加锁

保持线程同步
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block NSInteger number = 0;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    
    NSLog(@"semaphore---end,number = %zd",number);

dispatch_semaphore_wait加锁阻塞了当前线程,dispatch_semaphore_signal解锁后当前线程继续执行

保证线程安全,为线程加锁:

在线程安全中可以将dispatch_semaphore_wait看作加锁,而dispatch_semaphore_signal看作解锁
首先创建全局变量

 _semaphore = dispatch_semaphore_create(1);

注意到这里的初始化信号量是1

- (void)asyncTask
{
    
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
    
    count++;
    
    sleep(1);
    
    NSLog(@"执行任务:%zd",count);
    
    dispatch_semaphore_signal(_semaphore);
}

异步并发调用asyncTask

for (NSInteger i = 0; i < 100; i++) {
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            
            [self asyncTask];
        });
    }

然后发现打印是从任务1顺序执行到100,没有发生两个任务同时执行的情况。
原因如下:
在子线程中并发执行asyncTask,那么第一个添加到并发队列里的,会将信号量减1,此时信号量等于0,可以执行接下来的任务。而并发队列中其他任务,由于此时信号量不等于0,必须等当前正在执行的任务执行完毕后调用dispatch_semaphore_signal将信号量加1,才可以继续执行接下来的任务,以此类推,从而达到线程加锁的目的。

dispatch_once_t

block中的内容只执行一次。

for (int i = 0; i < 5; i++) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"回执行几次==");
        });
    }
dispatch_after
  • dispatch_after 基本操作延迟执行
- (void)after {
    NSLog(@"------------");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"++++++++");
    });
}

在这里插入图片描述

结果表示延迟两秒执行

  • dispatch_after中执行一些需要注意的方面

dispatch_after 延时操作,不会对控制器强引用, dispatch_after的操作和控制器的消亡没有关系,及时控制器销毁,但是dispatch_after在延迟时间到达后还是会执行操作。如果dispatch_after的block代码块中,对当前控制器产生了强引用,当销毁控制器时,控制器不会立刻销毁,等到dispatch_after中的block代码执行完毕之后,在销毁。

- (void)afterExecture {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //对当前控制器强引用
        NSLog(@"延迟10秒执行操作===%@",self.mark);
    });
}

在这里插入图片描述

没有产生引用的执行代码和结果:

- (void)afterExecture {
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //对当前控制器强引用
        NSLog(@"延迟10秒执行操作===%@",weakSelf.mark);
    });
}
GCD定时器
/*
     第一个参数:source的类型DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
     第二个参数:描述信息,线程ID
     第三个参数:更详细的描述信息
     第四个参数:队列,决定GCD定时器中的任务在哪个线程中执行
     dispatch_source_create(dispatch_source_type_t type,
     uintptr_t handle,
     unsigned long mask,
     dispatch_queue_t _Nullable queue);
     */
   
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
    //2.设置定时器(起始时间|间隔时间|精准度)
    /*
     第一个参数:定时器对象
     第二个参数:起始时间,DISPATCH_TIME_NOW 从现在开始计时
     第三个参数:间隔时间 2.0 GCD中时间单位为纳秒
     第四个参数:精准度 绝对精准0
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD定时器-----");
        
    });
    dispatch_resume(timer);
dispatch_barrier
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1==%d",i);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2==%d",i);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"截断,等上面任务完之后再无按成下面的任务");
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务3==%d",i);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务4==%d",i);
        }
    });

输出结果:
在这里插入图片描述
dispatch_barrier 中使用队列 必须要是DISPATCH_QUEUE_CONCURRENT并行队列,不然效果就和dipatch_async/dispatch_sync普通效果一样,没有拦截作用

//使用的是全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务1==%d",i);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务2==%d",i);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"截断,等上面任务完之后再无按成下面的任务");
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务3==%d",i);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"任务4==%d",i);
        }
    });

在这里插入图片描述
结果表示没并没有起到拦截作用

NSOperation

NSOperation是GCD的面向对象的封装。它拥有GCD的高效,也拥有面向对象的编程思想。NSOperation本身是个基类,我们需要使用他的子类。系统给我们提供了两个分别是:NSBlockOperationNSInvocationOperation。一种是通过Selector的形式添加操作,一种是block的形式添加操作,

NSBlockOperation

创建NSBlockOperation对象,然后在调用start方法才能执行,但是执行在主线程。

 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation  === current thread======%@",[NSThread currentThread]);
    }];
    [blockOperation start];

在这里插入图片描述

如果我们在blockOperation的基础之上添加一个操作任务,那这个操作任务会开辟一个新的线程来执行任务:

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation  === current thread======%@",[NSThread currentThread]);
    }];
    
    //添加任务为开辟的新县城
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecution  current thread ======%@",[NSThread currentThread]);
    }];
    [blockOperation start];

在这里插入图片描述
只有新添加的操作,才会开辟新的线程执行,要想使blockOperation这个任务也开辟一个新的线程执行任务,那么需要天家到队列中:

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation  === current thread======%@",[NSThread currentThread]);
    }];
    
    //添加任务为开辟的新县城
    [blockOperation addExecutionBlock:^{
        NSLog(@"addExecution  current thread ======%@",[NSThread currentThread]);
    }];
    //[blockOperation start];
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    [operationQueue addOperation:blockOperation];

在这里插入图片描述
添加到队列中的操作,都会开辟一个新的线程 来执行任务。

NSInvocationOperation

NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    [invocationOperation start];
  • NSInvocationOperation的用法和NSBlockOperation的用法类似。

NSOperationQueue

  • NSOperationQueue是一个队列,添加到队列中的对象,都会都会在子线程中执行操作任务,NSOperationQueue可以添加NSBlockOperation,NSInvocationOperation实例对象之外还可以添加block的操作。
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    //添加block操作
    [operationQueue addOperationWithBlock:^{
        NSLog(@"操作1------%@",[NSThread currentThread]);
    }];
    [operationQueue addOperationWithBlock:^{
        NSLog(@"操作2------%@",[NSThread currentThread]);
    }];
    [operationQueue addOperationWithBlock:^{
        NSLog(@"操作3------%@",[NSThread currentThread]);
    }];

运行结果:
在这里插入图片描述

  • NSOperationQueue并没有全局队列,但我们也可以根据自己的需求来创建全局队列。NSOperationQueue也有获取主队列的类方法[NSOperationQueue mainQueue],用起来也很简单方便,跟GCD中的主队列一样。

    • 一个子线程处理耗时的操作,然后刷新UI代码,使用NSOperationQueue是这样实现的:
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    //添加block操作
    [operationQueue addOperationWithBlock:^{
        NSLog(@"子线程处理耗时操作------%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
           NSLog(@"主线程更新UI------%@",[NSThread currentThread]);
        }];
    }];
  • 在线程同步上,NSOperation 没有group,但是有操作依赖,一样可以实现同样的效果。它的依赖,是操作的方法,所以如果要使用依赖,我们就得自己创建操作,然后操作之间设置好依赖关系,再把它们丢到队列里,比如说前面GCD中的那个同步信息的例子:
- (void)operationRelpOn {
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务一======");
    }];
    
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2-=====");
    }];
    
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
    //设置依赖关系
    [invocationOperation addDependency:blockOperation];
    [invocationOperation addDependency:blockOperation2];
    
    [operationQueue addOperation:blockOperation];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:invocationOperation];
}

- (void)invocationOperation1 {
    NSLog(@"任务3=====");
}

在这里插入图片描述
这里操作3依赖了操作1、操作2,必须等着两个操作完成之后在完成执行。

在给队列添加操作的时候,我们还有一个方法可以一次性添加多个操作的方法

/**
第一个参数是需要天骄操作的数组集合  
第二个参数: 是否等待这些操作任务完成,如果 设置为YES 则必须等待所有任务完成,此操作会阻塞主线程; 如果设置为NO,则不会阻塞主线程,也就是不会等待这些操作完成,就可以往下继续执行
*/
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)operationRelpOn2 {
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务一======");
    }];
    
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2-=====");
    }];
    
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
    //设置依赖关系
    [invocationOperation addDependency:blockOperation];
    [invocationOperation addDependency:blockOperation2];
    
    [operationQueue addOperations:@[blockOperation,blockOperation2,invocationOperation] waitUntilFinished:NO];
    
    NSLog(@"====================");
}

- (void)invocationOperation1 {
    NSLog(@"任务3=====");
}

输出结果:可以发先如果第二个参数设置为NO,并不会等待任务执行完成,反之如果为YES则会阻塞主线程,等待所有任务完成之后,在继续往下执行
在这里插入图片描述
在这里插入图片描述

虽然这我们不能在主线程中使用这种,但是我们可以在子线程中使用这个参数 设置为YES,可以和在主线程使用设置为NO达到一样的效果

NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    [operationQueue addOperationWithBlock:^{
        
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
               NSLog(@"任务一======");
           }];
           
           NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
               NSLog(@"任务2-=====");
           }];
           
           NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation1) object:nil];
           //设置依赖关系
           [invocationOperation addDependency:blockOperation];
           [invocationOperation addDependency:blockOperation2];
           
           [operationQueue addOperations:@[blockOperation,blockOperation2,invocationOperation] waitUntilFinished:YES];
        
        NSLog(@"任务4===========");
    }];

在这里插入图片描述

  • NSOperationQueue本身并没有并发队列和串行队列的选项,它默认是并发队列,但是它还有一个maxConcurrentOperationCount 属性,这个属性是设置最大并发数量(最多能开几条线程执行操作),如果最大并发数设置为1 ,他就是串行队列的模式。

  • NSOperationQueue还可以使用suspended属性来控制队列中的暂停和继续;调用cancelAllOperations() 方法可以取消队列里面的所有操作

  • IOS13.0 以后NSOperationQueue新增一个api方法 可以实现GCD中dispatch_barrier(栅栏)效果

- (void)operationBarrier{
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务一======");
    }];
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务2-=====");
    }];
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务3-=====");
    }];
    NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务4-=====");
    }];
    
    [operationQueue addOperation:blockOperation];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addBarrierBlock:^{
        NSLog(@"拦截成功-----------");
    }];
    [operationQueue addOperation:blockOperation3];
    [operationQueue addOperation:blockOperation4];
}

在这里插入图片描述

本文相关代码:多线程OC版-demo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值