美文网首页
iOS 多线程

iOS 多线程

作者: NextStepPeng | 来源:发表于2019-07-17 18:05 被阅读0次

    iOS使用线程的方式

    1. pthread
    2. NSThread
    3. GCD
    4. NSOperation

    NSThread线程的创建与启动

    • 创建线程后启动线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
     [thread start];
    
    • 创建线程后自动启动线程
    NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    
    • 隐式创建线程
    [self performSelectorInBackground:@selector(run) withObject:nil];
    

    NSThread线程相关方法

    • 获取当前线程
    [NSThread currentThread];
    //获取主线程
    [NSThread mainThread];
    
    
    • 是否是主线程
    [NSThread isMainThread];
    
    • 设置线程名称
    [[NSThread currentThread] setName:@"设置线程名称"];
    
    • 启动线程
     [[NSThread currentThread] start];
    
    • 阻塞线程 让线程休息会
    //[NSThread sleepUntilDate:[]]
        [NSThread sleepForTimeInterval:2];
    

    NSThread线程间通信

    • 子线程->主线程 子线程->指定线程
    self performSelectorOnMainThread:<#(nonnull SEL)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
    self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>
    
    • 举例说明(项目中经典例子 图片下载及 图片UI显示)
    [self performSelectorInBackground:@selector(downloadImage) withObject:nil];
    //在子线程下载图片
    - (void)downloadImage{
        NSURL *imageUrl = [NSURL URLWithString:@"图片地址"];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        UIImage *image = [UIImage imageWithData:imageData];
        //在主线程将图片显示
        [self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:true];
    }
    
    
    - (void)refreshOnMainThread:(UIImage *) image {
        UIImageView *imageV = [[UIImageView alloc] initWithImage:image];
    }
    

    NSThread线程安全

    • @synchronized (self)
      性能是较差的,但是很方便,不用自己创建锁,不用加锁,一般项目都能满足
    - (void)myMethod:(id)anObj
    {
        @synchronized(anObj)
        {
            // Everything between the braces is protected by the @synchronized directive.
        }
    }
    
    • NSLock
      在Cocoa程序中NSLock中实现了一个简单的互斥锁。所有锁(包括NSLock)的接口实际上都是通过NSLocking协议定义的,它定义了lock和unlock方法。你使用这些方法来获取和释放该锁
    - (NSLock *)lock
    {
        if (!_lock)
        {
            _lock = [[NSLock alloc] init];
        }
        
        return _lock;
    }
    [self.lock lock];
        self->_myVTKPolyData->SetPoints(m_Points);
        self->_myVTKPolyData->SetLines(lines);
        self->_myVTKPolyData->GetPointData()->SetScalars(colors);
        [self.lock unlock];
    

    -dispatch_semaphore 利用信号量控制(dispatch_semaphore_t sema = dispatch_semaphore_create(1);)

    - (void)initTicketStatusSafe{
        semaphoreLock = dispatch_semaphore_create(1); //一次只有一个
        self.ticketSurplusCount = 50;
        //代表售卖窗口1
        dispatch_queue_t queue1 = dispatch_queue_create("peng1", DISPATCH_QUEUE_SERIAL);
        
        //代表售卖窗口2
        dispatch_queue_t queue2 = dispatch_queue_create("peng2", DISPATCH_QUEUE_SERIAL);
        
        __weak typeof(self) weakSelf = self;
        dispatch_async(queue1, ^{
            [weakSelf saleTicketSafe];
        });
        
        dispatch_async(queue2, ^{
            [weakSelf saleTicketSafe];
        });
        
        
    }
    
    - (void)saleTicketSafe {
        while (1) {
            //如果还有票,继续售卖
            //        @synchronized (self) {
            
            dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
            
            if (self.ticketSurplusCount > 0) {
                self.ticketSurplusCount --;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", self.ticketSurplusCount, [NSThread currentThread].name]);
                [NSThread sleepForTimeInterval:0.5];
            }
            //如果已卖完,关闭售票窗口
            else {
                NSLog(@"所有火车票均已售完");
                dispatch_semaphore_signal(semaphoreLock);
                break;
            }
            //        }
            
            dispatch_semaphore_signal(semaphoreLock);
        }
    }
    

    既然讲到了GCD信号量,就直接切入GCD多线程,GCD还是非常实用

    GCD

    • GCD优点
      1、GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
      2、GCD会自动利用CPU内核(特别现在都是多核CPU,可以发挥淋漓尽致)
    • GCD 任务和队列
      1. 任务
        任务具体干些什么事,执行任务有两种方式:同步执行(sync)和异步执行(async)
      • 同步执行(sync)
        同步执行只能在当前线程中执行任务,不具备开启线程的能力,同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行
      • 异步执行(async)
        异步执行具备开启新线程的能力,可以在新线程中执行任务,异步执行添加到队列中,它不需要等待,可以继续执行任务。
      1. 队列 在GCD中有两个队列 串型队列和并发队列,既然是队列就都符合FIFO原则,两者的区别是:执行的顺序不同和开启的线程不同
      • 串型队列(Serial Dispatch Queue)
        每次只有一个任务执行。所有任务在同一个线程中执行(如果是同步不会开启线程,如果是异步有且只能开启一个新线程,队列里面同时有异步和同步例外,有异步就会有且只能再开一个线程),满足FIFO原则,执行一个任务完毕才执行下一个任务;

      • 并行队列(Concurrent Dispatch Queue)
        可以让多个任务同时执行(可以开启多个线程)并发队列的并发功能只有在异步async任务下才有用;
        举个栗子,你要打电话给女朋友A 和女朋友B。
        同步执行就是,你打电话给女朋友A的时候,就不能同时打电话给女朋友B,等到给女朋友A打完电话,才可以打电话给女朋友B。而且只能用当前的电话(不具备开启新线程的能力)
        而异步执行就是,你打电话给女朋友A的时候,不等和女朋友A通话结束,还能直接给女朋友B打电话,不用等和女朋友A通话结束之后再打(不用等待任务执行结束)。除了当前电话,你还可以使用其他电话(具备开启新线程的能力);

    队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表。采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

    GCD的使用步骤

    GCD使用步骤只有两步
    1、创建一个队列(串型队列或者并发队列)
    备注:主队列是一个特殊的串队列

        //串型队列
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
        //并型队列
        dispatch_queue_t queueC = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    

    2、将任务追加到任务的队列 中去,然后系统就会根据任务类型执行任务(同步执行或者异步执行)

        dispatch_async(queue, ^{
            // 异步追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 异步追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_sync(queue, ^{
            // 同步追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
    

    组合

    image.png

    注意,在主队列中 异步是不会开启新线程执行

    dispatch_queue_t queue = dispatch_get_main_queue();
        //并型队列
        //dispatch_queue_t queueC = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        
        dispatch_async(queue, ^{
            // 异步追加任务1
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 异步追加任务2
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        dispatch_async(queue, ^{
            // 异步追加任务3
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
                NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            }
        });
        
        NSLog(@"syncConcurrent---end");
    //打印
    currentThread---<NSThread: 0x600003b2a940>{number = 1, name = main}
    syncConcurrent---begin
    syncConcurrent---end
    1---<NSThread: 0x600003b2a940>{number = 1, name = main}
    1---<NSThread: 0x600003b2a940>{number = 1, name = main}
    2---<NSThread: 0x600003b2a940>{number = 1, name = main}
    2---<NSThread: 0x600003b2a940>{number = 1, name = main}
    3---<NSThread: 0x600003b2a940>{number = 1, name = main}
    3---<NSThread: 0x600003b2a940>{number = 1, name = main}
    

    GCD 线程间的通信

    - (void)GCDCommunication{
        //获取全局并发队列
        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]);
            });
        });
    }
    //打印
    1-----<NSThread: 0x6000021bea80>{number = 3, name = (null)}
    1-----<NSThread: 0x6000021bea80>{number = 3, name = (null)}
     2----<NSThread: 0x6000021b1400>{number = 1, name = main}
    

    GCD的其它方法

    GCD栅栏方法: dispatch_barrier_async

    使用场景:有时我们需要异步执行两组任务,而且第一组的任务执行完之后,才能开始执行第二组任务,这样我们就需要一个一个相当于“栅栏”一样的一个方法将两组异步执行任务分割气啦,这个时候需要用到dispatch_barrier_async方法在两个任务组将形成栅栏

    - (void)barrier{
        
        dispatch_queue_t queue = dispatch_queue_create("peng", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            //异步追加任务1
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"1----%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            //异步追加任务2
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2----%@",[NSThread currentThread]);
            }
        });
        
        //dispatch_barrier_sync 也可以 只是会切换到调用线程  主线程调用就是 main
        
        dispatch_barrier_async(queue, ^{
            //追加任务barrier
            for (int i = 0; i < 2; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"barrier----%@",[NSThread currentThread]);
            }
        });
        
        
        dispatch_async(queue, ^{
            //异步追加任务3
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"3----%@",[NSThread currentThread]);
            }
        });
        
        dispatch_async(queue, ^{
            //异步追加任务4
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"4----%@",[NSThread currentThread]);
            }
        });
    }
    2019-07-17 15:03:33.907484+0800 YBStockChartView[1988:60887] 1----<NSThread: 0x600001476380>{number = 4, name = (null)}
    2019-07-17 15:03:33.907484+0800 YBStockChartView[1988:60889] 2----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:35.910472+0800 YBStockChartView[1988:60887] 1----<NSThread: 0x600001476380>{number = 4, name = (null)}
    2019-07-17 15:03:35.910479+0800 YBStockChartView[1988:60889] 2----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:37.914415+0800 YBStockChartView[1988:60889] barrier----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:39.915950+0800 YBStockChartView[1988:60889] barrier----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:41.920864+0800 YBStockChartView[1988:60889] 3----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:41.920869+0800 YBStockChartView[1988:60887] 4----<NSThread: 0x600001476380>{number = 4, name = (null)}
    2019-07-17 15:03:43.923980+0800 YBStockChartView[1988:60889] 3----<NSThread: 0x600001400b80>{number = 3, name = (null)}
    2019-07-17 15:03:43.924004+0800 YBStockChartView[1988:60887] 4----<NSThread: 0x600001476380>{number = 4, name = (null)}
    

    GCD延时操作

    - (void)after{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            //2秒后异步追加到主队列执行
            NSLog(@"after-----%@",[NSThread currentThread]);
        });
    }
    

    GCD 只有执行一次性:dispatch_once

    在整个程序运行过程中只执行一次

    - (void)once {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            //只执行一次
            NSLog(@"只执行一次");
        });
    }
    

    GCD快速迭代方法:dispatch_apply

    与FOR 循环区别,for 循环 顺序遍历 ,而 dispatch_apply 不是

    - (void)dispatch_apply{
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        NSLog(@"apply-- begin");
        dispatch_apply(6, queue, ^(size_t index) {
            NSLog(@"%zd----%@",index,[NSThread currentThread]);
        });
        
        NSLog(@"apply---end");
    }
    2019-07-17 15:25:12.435116+0800 YBStockChartView[2352:77865] apply-- begin
    2019-07-17 15:25:12.435942+0800 YBStockChartView[2352:77901] 4----<NSThread: 0x6000017a8980>{number = 5, name = (null)}
    2019-07-17 15:25:12.435946+0800 YBStockChartView[2352:77900] 2----<NSThread: 0x6000017bfe80>{number = 4, name = (null)}
    2019-07-17 15:25:12.435948+0800 YBStockChartView[2352:77904] 1----<NSThread: 0x6000017c9b00>{number = 3, name = (null)}
    2019-07-17 15:25:12.435948+0800 YBStockChartView[2352:77865] 0----<NSThread: 0x6000017f1400>{number = 1, name = main}
    2019-07-17 15:25:12.435955+0800 YBStockChartView[2352:77903] 3----<NSThread: 0x6000017c9a40>{number = 6, name = (null)}
    2019-07-17 15:25:12.435965+0800 YBStockChartView[2352:77902] 5----<NSThread: 0x6000017a0b40>{number = 7, name = (null)}
    2019-07-17 15:25:12.436115+0800 YBStockChartView[2352:77865] apply---end
    

    GCD 队列组 dispatch_group

    - (void)dispatch_group{
        dispatch_group_t gruop = dispatch_group_create();
        
        
        dispatch_group_async(gruop, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //追加耗时任务
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"1====%@",[NSThread currentThread]);
            }
        });
        
        dispatch_group_async(gruop, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //追加耗时任务
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2====%@",[NSThread currentThread]);
            }
        });
        
        dispatch_group_notify(gruop, dispatch_get_main_queue(), ^{
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"3====%@",[NSThread currentThread]);
            }
            NSLog(@"group--end");
        });
    }
    
    2019-07-17 15:39:32.459319+0800 YBStockChartView[2552:87029] 2====<NSThread: 0x600002ee4780>{number = 3, name = (null)}
    2019-07-17 15:39:32.459320+0800 YBStockChartView[2552:87028] 1====<NSThread: 0x600002ed8200>{number = 4, name = (null)}
    2019-07-17 15:39:34.464453+0800 YBStockChartView[2552:87028] 1====<NSThread: 0x600002ed8200>{number = 4, name = (null)}
    2019-07-17 15:39:34.464480+0800 YBStockChartView[2552:87029] 2====<NSThread: 0x600002ee4780>{number = 3, name = (null)}
    2019-07-17 15:39:36.465933+0800 YBStockChartView[2552:86994] 3====<NSThread: 0x600002eba940>{number = 1, name = main}
    2019-07-17 15:39:38.466517+0800 YBStockChartView[2552:86994] 3====<NSThread: 0x600002eba940>{number = 1, name = main}
    2019-07-17 15:39:38.466685+0800 YBStockChartView[2552:86994] group--end
    

    GCD dispatch_group_wait

    暂停当前线程(阻塞),等待指定的group中的任务执行完成后,才会往下执行

    - (void)groupWait{
        NSLog(@"currentThread====%@",[NSThread currentThread]);
        NSLog(@"group--begin");
        
        dispatch_group_t group = dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           //追加
            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), ^{
            //追加
            for (int i = 0; i < 2; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2=====%@",[NSThread currentThread]);
            }
        });
        
        // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
        NSLog(@"group---end");
    }
    

    GCD dispatch_group_enter、dispatch_group_leave

    • dispatch_group_enter 意思是一个任务追加到group 执行一次,相当于group中的未执行完毕任务书+1
    • dispatch_group_leave 意思是一个任务执行完毕离开group ,执行一次,相当于group中的未执行完毕任务书-1
    • 当group中未执行完毕任务数未0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify的任务中去
    - (void)gruopEnterAndLeave{
        NSLog(@"currentThread===%@",[NSThread currentThread]);
        NSLog(@"group--begin");
        
        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_async(queue, ^{
            for (int i = 0; i < 2; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"1==-%@",[NSThread currentThread]);
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            for (int i = 0; i < 2; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2==-%@",[NSThread currentThread]);
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            for (int i = 0; i < 2; i ++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"3==-%@",[NSThread currentThread]);
            }
            NSLog(@"group====end");
        });
        
      
    }
    2019-07-17 16:07:42.775761+0800 YBStockChartView[2954:104854] currentThread===<NSThread: 0x600003cb6940>{number = 1, name = main}
    2019-07-17 16:07:42.775845+0800 YBStockChartView[2954:104854] group--begin
    2019-07-17 16:07:44.778363+0800 YBStockChartView[2954:104888] 2==-<NSThread: 0x600003cfa480>{number = 3, name = (null)}
    2019-07-17 16:07:44.778373+0800 YBStockChartView[2954:104887] 1==-<NSThread: 0x600003cdcb00>{number = 4, name = (null)}
    2019-07-17 16:07:46.783729+0800 YBStockChartView[2954:104887] 1==-<NSThread: 0x600003cdcb00>{number = 4, name = (null)}
    2019-07-17 16:07:46.783748+0800 YBStockChartView[2954:104888] 2==-<NSThread: 0x600003cfa480>{number = 3, name = (null)}
    2019-07-17 16:07:48.784472+0800 YBStockChartView[2954:104854] 3==-<NSThread: 0x600003cb6940>{number = 1, name = main}
    2019-07-17 16:07:50.785241+0800 YBStockChartView[2954:104854] 3==-<NSThread: 0x600003cb6940>{number = 1, name = main}
    2019-07-17 16:07:50.785515+0800 YBStockChartView[2954:104854] group====end
    

    GCD信号量 dispatch_semaphone

    将异步线程转换为同步线程 还可以利用信号量保证线程安全

    - (void)semaphoneSync{
        NSLog(@"currentThread===%@",[NSThread currentThread]);
        NSLog(@"semaphone--begin");
        
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        
        dispatch_semaphore_t semaphone = dispatch_semaphore_create(0);
    //创建一个 Semaphore 并初始化信号的总量
        
        __block int number = 0;
        dispatch_async(queue, ^{
           //追加任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1======%@",[NSThread currentThread]);
            
            number =100;
            //发送一个信号,让信号总量加 1
            dispatch_semaphore_signal(semaphone);
            
        });
        //dispatch_semaphore_signal(semaphone);
    //可以使总信号量减 1,信号总量小于 1 时就会一直等待(阻塞所在线程),否则就可以正常执行
        dispatch_semaphore_wait(semaphone, DISPATCH_TIME_FOREVER);
        
        NSLog(@"semaphore---end,num == %d",number);
    }
    
    2019-07-17 16:33:22.483822+0800 YBStockChartView[3373:125495] currentThread===<NSThread: 0x6000029fe940>{number = 1, name = main}
    2019-07-17 16:33:22.483902+0800 YBStockChartView[3373:125495] semaphone--begin
    2019-07-17 16:33:24.484306+0800 YBStockChartView[3373:125556] 1======<NSThread: 0x6000029a6a00>{number = 3, name = (null)}
    2019-07-17 16:33:24.484620+0800 YBStockChartView[3373:125495] semaphore---end,num == 100
    

    NSOperation

    是苹果提供给开发者一套多线程解决方案,实际上基于GCD更高一层的封装,完全面向对象。但比GCD 更简单易用、代码可读性也更高
    1、可以使用 KVO 观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。
    2、设定操作执行的优先级
    3、可以很方便的取消一个操作的执行

    NSOperation、NSOperationQueue 操作和操作队列,

    可以结合GCD的任务和任务队列理解

    • 操作 operation
    • 操作队列 (Operation Queues)
      操作队列可以通过设置最大并发操作数(maxConcurrentOperationCount来控制并发、串行
      NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。
      主队列运行在主线程上,而自定义队列在后台执行

    NSOperation、NSOperationQueue 使用步骤

    NSOperation需要配合NSOperationQueue来实现多线程。默认情况下
    NSOperation 单独使用时系统同步执行操作,配合NSOperationQueue 能更好的实现异步执行

    分为三步
    1、创建操作:先将需要执行的操作封装到一个NSOperation对象中
    2、创建队列:创建NSOperationQueue 对象
    3、将操作加入到队列中:将NSOperation对象添加到NSOperatinoQueue对象中

    然后系统就会自动将NSOperationQueue 中的NSOperation 取出来,在新线程中执行操作。

    NSOperation 和 NSOperationQueue 基本使用

    创建

    NSOperation是个抽象类,需要使用他的子类来封装操作,

    • 1、使用子类NSInvocationOperation
    • 2、使用子类NSBlockOperation
    • 3、自定义继承自NSOperation的子类,实现内部方法来封装操作。
    使用子类NSInvocationOperation
    - (void)useInvocationOperation{
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        [op start];
    }
    
    -(void)task1{
        for (int i = 0; i < 5; i++) {
            NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
        }
    }
    2019-07-17 17:05:52.389871+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
    2019-07-17 17:05:52.389995+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
    2019-07-17 17:05:52.390056+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
    2019-07-17 17:05:52.390132+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
    2019-07-17 17:05:52.390232+0800 YBStockChartView[3820:146376] 1---<NSThread: 0x60000151e940>{number = 1, name = main}
    

    上面打印可以看出,在没有使用NSOperationQueue,在主线程中单独使用子类 NSInvocationOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程

    使用NSBlockOperation
    - (void)useBlockOperation{
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op start];
    }
    

    并没有开新线程,但是 NSBlockOperation 的addExecutionBlock 可以在开启新线程

    - (void)useBlockOperation{
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"1---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"2---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"3---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"4---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"5---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        
        [op start];
    }
    2019-07-17 17:13:33.847155+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
    2019-07-17 17:13:33.847161+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
    2019-07-17 17:13:33.847160+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
    2019-07-17 17:13:33.847160+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
    2019-07-17 17:13:33.847162+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
    2019-07-17 17:13:33.847288+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
    2019-07-17 17:13:33.847289+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
    2019-07-17 17:13:33.847289+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
    2019-07-17 17:13:33.847290+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
    2019-07-17 17:13:33.847296+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
    2019-07-17 17:13:33.847347+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
    2019-07-17 17:13:33.847372+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
    2019-07-17 17:13:33.847430+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
    2019-07-17 17:13:33.847596+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
    2019-07-17 17:13:33.847715+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
    2019-07-17 17:13:33.847797+0800 YBStockChartView[3939:152105] 2---<NSThread: 0x600000794000>{number = 3, name = (null)}
    2019-07-17 17:13:33.847913+0800 YBStockChartView[3939:152104] 3---<NSThread: 0x6000007a87c0>{number = 4, name = (null)}
    2019-07-17 17:13:33.847990+0800 YBStockChartView[3939:152071] 1---<NSThread: 0x6000007fe900>{number = 1, name = main}
    2019-07-17 17:13:33.848107+0800 YBStockChartView[3939:152103] 4---<NSThread: 0x6000007b2c40>{number = 5, name = (null)}
    2019-07-17 17:13:33.848199+0800 YBStockChartView[3939:152106] 5---<NSThread: 0x600000794040>{number = 6, name = (null)}
    

    一般情况下,如果一个NSBlockOperation对象封装了多个操作,是否开启新线程,取决操作的个数,操作个数多就会开启多线程。具体个数有系统决定

    使用自定义继承自NSOperatrion的子类 重写main方法
    //NSOperation
    - (void)main{
        if (!self.isCancelled) {
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"PengOperation====%@", [NSThread currentThread]);
                
            }
        }
    }
    

    创建队列 NSOperationQueue

    • 主队列
      凡是添加到主队列中的操作,都会放到主线程中执行(注意:不包括操作使用addExecutionBlock:添加的额外操作)
     NSOperationQueue *queue = [NSOperationQueue mainQueue];
    
    • 非主队列
      会自动放到子线程中执行
      同时包含串性和并发
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    

    将操作加入到队列中

    - (void)addOprationToQueue{
        //创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        //创建操作
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"op3---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        [op3 addExecutionBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"other==op3---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        
        [queue addOperation:op];
        [queue addOperation:op2];
        [queue addOperation:op3];
        
    }
    

    - (void)addOperationWithBlock:(void (^)(void))block;

    无需先创建操作,在block中添加操作,直接将包含block加入到队列中

    - (void)addOperationWithBlockToQueue{
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 4; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"1----%@",[NSThread currentThread]);
            }
        }];
        
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 4; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2----%@",[NSThread currentThread]);
            }
        }];
        
        
    }
    2019-07-17 17:39:52.279263+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
    2019-07-17 17:39:52.279275+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
    2019-07-17 17:39:54.279991+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
    2019-07-17 17:39:54.279997+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
    2019-07-17 17:39:56.283783+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
    2019-07-17 17:39:56.283804+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
    2019-07-17 17:39:58.285930+0800 YBStockChartView[4380:172926] 1----<NSThread: 0x6000012cb500>{number = 3, name = (null)}
    2019-07-17 17:39:58.285930+0800 YBStockChartView[4380:172928] 2----<NSThread: 0x6000012da340>{number = 4, name = (null)}
    

    NSOperatrion控制串行、并发执行---maxConcurrentOperationCount

    注意:这里maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数,而且一个操作也并非只能在一个线程中运行
    默认情况maxConcurrentOperationCount = -1

    • maxConcurrentOperationCount 为1时,队列为串行队列
    • maxConcurrentOperationCount 大于1时,队列为并发队列
    - (void)addOperationWithBlockToQueue{
        NSLog(@"currentThread----%@",[NSThread currentThread]);
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        //queue.maxConcurrentOperationCount = 1; //串行队列,切记并非只有一个线程,刻意有多个线程,但并发数最大为1
        queue.maxConcurrentOperationCount = 6;
        
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 4; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"1----%@",[NSThread currentThread]);
            }
        }];
        
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 4; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"2----%@",[NSThread currentThread]);
            }
        }];
        
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 4; i++) {
                [NSThread sleepForTimeInterval:2];
                NSLog(@"3----%@",[NSThread currentThread]);
            }
        }];
        
         NSLog(@"currentThread---end----%@",[NSThread currentThread]);
    }
    

    NSOperatrion 操作依赖

    - (void)addDependency{
        
        //1/创建队列
        NSLog(@"currentThread----%@",[NSThread currentThread]);
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        //queue.maxConcurrentOperationCount = 1; //串行队列,切记并非只有一个线程,刻意有多个线程,但并发数最大为1
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"op3---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 5; i++) {
                NSLog(@"op4---%@",[NSThread currentThread]); //打印当前线程
            }
        }];
        
        
        [op3 addDependency:op4];
        
        [queue addOperation:op3];
        [queue addOperation:op4];
        
        NSLog(@"currentThread---end----%@",[NSThread currentThread]);
        
    }
    2019-07-17 18:04:28.581372+0800 YBStockChartView[4808:189453] currentThread----<NSThread: 0x600002c42940>{number = 1, name = main}
    2019-07-17 18:04:28.581706+0800 YBStockChartView[4808:189453] currentThread---end----<NSThread: 0x600002c42940>{number = 1, name = main}
    2019-07-17 18:04:28.581756+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
    2019-07-17 18:04:28.581834+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
    2019-07-17 18:04:28.581897+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
    2019-07-17 18:04:28.581978+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
    2019-07-17 18:04:28.582085+0800 YBStockChartView[4808:189498] op4---<NSThread: 0x600002c1b0c0>{number = 3, name = (null)}
    2019-07-17 18:04:28.582262+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
    2019-07-17 18:04:28.582342+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
    2019-07-17 18:04:28.593061+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
    2019-07-17 18:04:28.593160+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
    2019-07-17 18:04:28.593247+0800 YBStockChartView[4808:189494] op3---<NSThread: 0x600002c1d240>{number = 4, name = (null)}
    

    NSOperation 、NSOperationQueue线程间的通信

    - (void)communication{
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperationWithBlock:^{
            for (int i = 0; i < 3; i++) {
                NSLog(@"1=====%@",[NSThread currentThread]);
            }
            
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                NSLog(@"主线程刷新UI%@",[NSThread currentThread]);
            }];
        }];
    }
    
    2019-07-17 20:57:03.534990+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
    2019-07-17 20:57:03.535153+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
    2019-07-17 20:57:03.535227+0800 YBStockChartView[5714:238159] 1=====<NSThread: 0x6000008fd100>{number = 3, name = (null)}
    2019-07-17 20:57:03.537354+0800 YBStockChartView[5714:238126] 主线程刷新UI<NSThread: 0x6000008aa940>{number = 1, name = main}
    

    NSOperation 常用属性和方法

    • 取消操作方法
      -(void)cancel;
    • 判断状态方法
      -(BOOL)isFinished;
      -(BOOL)isCancelled;
      -(BOOL)isExecuting;
      -(BOOL)isReady;
    • 操作同步
      -(void)waitupntifinished 阻塞当前线程,知道该操作结束
      -(void)addDependency:(NSOperation)op 添加依赖
      -(void)removeDependency:(NSOperation
      )op 移除依赖

    NSOperationQueue 常用属性和方法

    • 1、取消/暂停/恢复操作
      -(void)cancelAllOperations;可以取消队列的所有操作;
      -(BOOL)isSuspended;判断队列是否处于暂停状态
    • (void)setSuspended:(BOOL)b;可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
    • 2、操作同步
    • (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕
    • 3、添加/获取操作
    • (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象
    • (void)addOperations:(NSArray *)ops waitUntilFinished:
      -(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
    • (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
    • (NSUInteger)operationCount; 当前队列中的操作数
    • 4、获取队列
    • (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil
    • (id)mainQueue; 获取主队列

    注意:

    这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
    暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作

    相关文章

      网友评论

          本文标题:iOS 多线程

          本文链接:https://www.haomeiwen.com/subject/ivwflctx.html