美文网首页
iOS多线程

iOS多线程

作者: 飘金 | 来源:发表于2017-06-21 16:28 被阅读0次

    并发编程推荐文章objc中国的并发编程API挑战

    pthread

    POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。是一套C语言写的线程库.

    先要包含其头文件

    #import <pthread.h>
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self createPThread];
    }
    
    - (void)createPThread{
        pthread_t pthreadID;
        NSString *param = @"piaojin";
        //pthreadID线程的标识符
        //cFunc要调用的方法(C语言的方法)
        //param传递的参数
        int result = pthread_create(&pthreadID, NULL, &cFunc, (__bridge void *)(param));
        printf("%d",result);
    }
    
    void * cFunc(void *param){
        NSLog(@"%@,param:%@",[NSThread currentThread],param);
        return NULL;
    }
    

    看代码就会发现他需要 c语言函数,这是比较蛋疼的,更蛋疼的是你需要手动处理线程的各个状态的转换即管理生命周期,比如,这段代码虽然创建了一个线程,但并没有销毁。

    NSThread

    这套方案是经过苹果封装后的,并且完全面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们手动管理,所以这套方案也是偶尔用用,比如 [NSThread currentThread],它可以获取当前线程类,你就可以知道当前线程的各种属性,用于调试十分方便。下面来看看它的一些用法。

    创建并启动

    先创建线程类,再启动

      // 创建
      NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
    
      // 启动
      [thread start];
    
      [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
    
    //使用 NSObject 的方法创建并自动启动
    
      [self performSelectorInBackground:@selector(run:) withObject:nil];
    
    //除了创建启动外,NSThread 还以很多方法,下面我列举一些常见的方法,当然我列举的并不完整,更多方法大家可以去类的定义里去看。
    
    //取消线程
    - (void)cancel;
    
    //启动线程
    - (void)start;
    
    //判断某个线程的状态的属性
    @property (readonly, getter=isExecuting) BOOL executing;
    @property (readonly, getter=isFinished) BOOL finished;
    @property (readonly, getter=isCancelled) BOOL cancelled;
    
    //设置和获取线程名字
    -(void)setName:(NSString *)n;
    -(NSString *)name;
    
    //获取当前线程信息
    + (NSThread *)currentThread;
    
    //获取主线程信息
    + (NSThread *)mainThread;
    
    //使当前线程暂停一段时间,或者暂停到某个时刻
    + (void)sleepForTimeInterval:(NSTimeInterval)time;
    + (void)sleepUntilDate:(NSDate *)date;
    

    GCD

    Grand Central Dispatch,听名字就霸气。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案,老少咸宜,实在是居家旅行、杀人灭口,必备良药。不好意思,有点中二,咱们继续。

    任务和队列

    在 GCD 中,加入了两个非常重要的概念: 任务 和 队列。

    任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行,他们之间的区别是 是否会创建新的线程。

    同步(sync)和异步(async)的主要区别在于会不会阻塞当线程,直到Block中的任务执行完毕!如果是 同步(sync)操作,它会塞当前线程并等待Block中的任务执行完毕,然后当前线程才会继续往运行。如果是异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。

    队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。

    放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。

    放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。

    //同步任务 + 并发队列(任务还是以同步的形式,一个一个完成),没有开启新线程
    - (void)sync{
        //创建并发队列DISPATCH_QUEUE_CONCURRENT,默认是同步队列DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列
        dispatch_queue_t queue = dispatch_queue_create("同步任务 + 并发队列", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i);
            }
        });
        
        dispatch_sync(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i);
            }
        });
        
        dispatch_sync(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i);
            }
        });
    }
    
    //异步任务 + 串行队列(任务还是以同步的形式,一个一个完成),开启了新线程
    - (void)async{
        //创建并发队列DISPATCH_QUEUE_CONCURRENT,默认是同步队列DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列
        dispatch_queue_t queue = dispatch_queue_create("异步任务 + 串行队列", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->1<-%d",[NSThread currentThread],i);
            }
        });
        
        dispatch_async(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->2<-%d",[NSThread currentThread],i);
            }
        });
        
        dispatch_async(queue, ^{
            for(int i = 0;i < 5;i++){
                NSLog(@"currentThread:%@,->3<-%d",[NSThread currentThread],i);
            }
        });
    }
    
    #define ImageUrl @"https://img.haomeiwen.com/i3362328/bdcbda41eac5e8a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"
    //线程间通讯,NSThread利用PerformSelector系列方法
    - (void)communication{
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //耗时操作,此处为从网络获取一直图片
            //耗时的操作
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]];
            UIImage *image = [UIImage imageWithData:imageData];
            //把image传递给主线程(线程间的通讯)
            dispatch_async(dispatch_get_main_queue(), ^{
                //到主线程更新UI
                weakSelf.imageView.image = image;
            });
        });
    }
    
    //延时
    - (void)delay{
        //NSObject的延时方法
        [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
        
        //延时两秒
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self run];
        });
        
        //利用定时器也可以达到延时执行的效果,这时候repeats要设置为NO,只执行一次,用scheduledTimerWithTimeInterval方法创建的定时器会默认添加到当前的RunLoop中去,并且启动
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
    }
    
    - (void)run{
        NSLog(@"延时两秒");
    }
    
    //一次性操作
    - (void)once{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"一次性操作");
        });
    }
    
    //多次操作
    - (void)apply{
        /**
         *size_t iterations执行的次数
         **/
        dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
            //index每次遍历的下标,注意并不是按顺序的
            NSLog(@"%zu",index);
        });
    }
    
    //队列组操作
    - (void)group{
        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 < 100; i++) {
                NSLog(@"queue1:%d",i);
            }
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            for (int i = 0; i < 100; i++) {
                NSLog(@"queue2:%d",i);
            }
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"queue3");
        });
        
        //前面创建了三个任务,这边三个任务完成时回执行dispatch_group_notify中的block代码
        dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"三个任务都完成了!");
        });
    }
    
    

    当dispatch_group_async的block里面执行的是异步任务,如果还是使用上面的方法你会发现异步任务还没跑完就已经进入到了dispatch_group_notify方法里面了,这时用到dispatch_group_enter和dispatch_group_leave就可以解决这个问题.进入前dispatch_group_enter(group),离开执行完毕时dispatch_group_leave(group).

    NSOperation和NSOperationQueue

    NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列

    NSOperation是一个抽象的类,并不具备封装操作的能力,必须使用它的子类:

    • NSInvocationOperation

    • NSBlockOperation

    • 自定义子类继承NSOperation是一个抽象的类,重写内部相应的方法(main方法)

    操作步骤也很好理解:

    • 先将需要执行的操作封装到一个NSOperation对象中

    • 将NSOperation对象添加到NSOperationQueue中

    • 系统会自动将NSOperationQueue中的NSOperation取出来

    • 将取出来的NSOperation封装的操作放到一条线程中去执行

    NSOperationQueue的作用

    如果将NSOperation添加到NSOperationQueue中,系统会默认异步执行NSOperation的操作

    添加NSOperation到NSOperationQueue中

    • 调用addOperation:的方法

    • 调用addOperationWithBlock:方法

    //自定义继承NSOperation
    @implementation PJCustomOperation
    
    //需要重写main方法,任务是在main方法中执行的
    - (void)main{
        NSLog(@"PJCustomOperation:%@",[NSThread currentThread]);
    }
    
    @end
    

    没有添加到NSOperationQueue的情况

    //NSInvocationOperation
    - (void)invocationOperation{
        //默认在主线程中执行,并且不会创建新的线程number = 1(主线程),相单与[self performSelector:@selector(run) withObject:nil];,只有放到NSOperationQueue才会开启新线程,异步执行
        NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
        //开启
        [invocationOperation start];
    }
    
    //NSBlockOperation
    - (void)blockOperation{
        //默认在主线程中执行,并且不会创建新的线程number = 1(主线程),相单与[self performSelector:@selector(run) withObject:nil];
        NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"blockOperation1%@",[NSThread currentThread]);
        }];
        
        //添加额外任务会开启新线程,number != 1,在新线程中完成
        [blockOperation addExecutionBlock:^{
            NSLog(@"blockOperation2%@",[NSThread currentThread]);
        }];
        
        [blockOperation start];
    }
    
    //自定义NSOperation,需要重写main方法,任务是在main方法中执行的
    - (void)customOperation{
        //此时是在主线程中执行,并没有开启新线程
        PJCustomOperation *customOperation = [[PJCustomOperation alloc] init];
        [customOperation start];
    }
    
    - (void)run{
        NSLog(@"执行任务%@",[NSThread currentThread]);
    }
    
    

    添加到NSOperationQueue的情况

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //创建队列,默认创建的是并行队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        //GCD的队列形式
        //全局 主队列 串行队列 并行队列
        
        //NSOperationQueue队列形式
        //主队列 自己创建的队列
        
        //创建NSOperation
        NSInvocationOperation *invocationOperation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation1"];
        //放入队列中,就会执行不用显示的去[invocationOperation start],会创建一个新线程,异步执行
        [queue addOperation:invocationOperation1];
        
        NSInvocationOperation *invocationOperation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"invocationOperation2"];
        //放入队列中,就会执行不用显示的去[invocationOperation start],会创建一个新线程,异步执行
        [queue addOperation:invocationOperation2];
        
        //会创建一个新线程,异步执行
        [queue addOperationWithBlock:^{
            NSLog(@"addOperationWithBlock的形式:thread:%@",[NSThread currentThread]);
        }];
    
    }
    
    - (void)run:(NSString *)name{
        NSLog(@"name:%@,thread:%@",name,[NSThread currentThread]);
    }
    

    NSOperation通信与依赖关系

    //利用NSOperationQueue进行现场间通讯
    - (void)queueMessage{
        //利用NSOperation和NSOperationQueue来执行
        __weak typeof(self) weakSelf = self;
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        [queue addOperationWithBlock:^{
            //耗时的操作
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:ImageUrl]];
            //耗时操作执行完后会到主队列执行UI更新操作
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                weakSelf.imageView.image = [UIImage imageWithData:imageData];
            }];
        }];
    }
    
    //依赖 如果A依赖与B,则A等到B完成了才执行
    - (void)dependency{
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        
        NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"operation1:%@",[NSThread currentThread]);
        }];
        
        NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"operation2:%@",[NSThread currentThread]);
        }];
        
        NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"operation3:%@",[NSThread currentThread]);
        }];
        
        //添加依赖 前者(operation1)依赖后者(operation2),后者(operation2)先执行,在执行前者(operation1)
        //任务之间不能造成依赖循环 A -> B,B -> C,C -> A
        [operation1 addDependency:operation2];
        
        [queue addOperations:@[operation1,operation2,operation3] waitUntilFinished:NO];
    }
    

    NSOperation并发数与挂起

    最大并发数

    若是开启线程较多,会增大CPU工作量,从而降低了每个线程被调用的次数.为了合理的提高CPU的工作效率,我们可以设置队列最大并发数.

    • 可以通过为maxConcurrentOperationCount属性赋值即可,maxConcurrentOperationCount的默认值为-1,即不限并发数量.当其设置后队列变成串行队列.

    @interface ViewController ()
    
    @property (nonatomic, strong)NSOperationQueue *queue;
    
    @end
    
    
    //最大并发数量
    - (void)maxConcurrentOperationsCount{
        _queue = [[NSOperationQueue alloc] init];
        //设置为2后表示每次只能并发执行2个任务,变成了串行执行,如果设置为1就是每次只能并发执行一个任务
        //下面的执行就是1和2先并发执行,然后3和4并发执行,入股偶设置为1则按顺序1 2 3 4一个一个执行
        _queue.maxConcurrentOperationCount = 2;
        [_queue addOperationWithBlock:^{
            for (int i = 0; i < 5000; i++) {
                NSLog(@"--->1<---%@",[NSThread currentThread]);
            }
        }];
        
        [_queue addOperationWithBlock:^{
            for (int i = 0; i < 5000; i++) {
                NSLog(@"--->2<---%@",[NSThread currentThread]);
            }
        }];
        
        [_queue addOperationWithBlock:^{
            for (int i = 0; i < 5000; i++) {
                NSLog(@"--->3<---%@",[NSThread currentThread]);
            }
        }];
        
        [_queue addOperationWithBlock:^{
            for (int i = 0; i < 5000; i++) {
                NSLog(@"--->4<---%@",[NSThread currentThread]);
            }
        }];
    }
    
    

    队列的挂起与取消

    挂起

    suspended:将其赋值为YES即挂起,赋值为NO即恢复

    • 当线程处于执行状态,设置队列挂起时不会影响其执行,受影响的是那些还没有执行的任务

    • 当队列设置为挂起状态后,可以修改其状态再次恢复任务

    取消

    设置取消的方法是cancelAllOperations,当取消任务后不可以恢复

    若任务操作时自定义的NSOperation类型的话,执行完一个耗时操作后需要加一是否取消任务的判断,再去执行另外一个耗时操作.同样取消不影响当前正在执行的线程,后面的线程会被取消.

    //挂起与取消
    - (void)suspendedAndCancel{
        //挂起 当前正在执行的任务不受影响,后面还未执行的任务会被挂起,后面的任务可以被恢复
    //    [self.queue setSuspended:!self.queue.suspended];
        //取消 当前所有任务会被取消,并且不可恢复
        [self.queue cancelAllOperations];
    }
    
    - (void)customOperation{
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        PJCustomOperation *customOperation1 = [[PJCustomOperation alloc] init];
        [queue addOperation:customOperation1];
        self.queue = queue;
    }
    
    @implementation PJCustomOperation
    
    - (void)main{
        
        //如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
        if(self.isCancelled){
            return;
        }
        
        //模拟在同一个线程里面做多个耗时操作
        for (int i = 0; i < 50000; i++) {
            NSLog(@"--->PJCustomOperation1<---%@",[NSThread currentThread]);
        }
        
        //如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
        if(self.isCancelled){
            return;
        }
        
        for (int i = 0; i < 50000; i++) {
            NSLog(@"--->PJCustomOperation2<---%@",[NSThread currentThread]);
        }
        
        //如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
        if(self.isCancelled){
            return;
        }
        
        for (int i = 0; i < 50000; i++) {
            NSLog(@"--->PJCustomOperation3<---%@",[NSThread currentThread]);
        }
        
        //如果任务任务被取消了就不往下执行,不做这个判断的话,当队列被取消是当前的线程不受影响,故如果该线程有多个耗时操作会继续进行下去
        if(self.isCancelled){
            return;
        }
        
        for (int i = 0; i < 50000; i++) {
            NSLog(@"--->PJCustomOperation4<---%@",[NSThread currentThread]);
        }
    }
    

    相关文章

      网友评论

          本文标题:iOS多线程

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