多线程(详解)

2017-02-07  本文已影响0人  夏天不冷
1.iOS 开发中多线程出现的本质?
2.iOS中多线程的实现方案(4种)

①pthread

#import "ViewController.h"
#import <pthread.h>

@interface ViewController ()
@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

// 创建线程
/*
 第一个参数:线程对象地址
 第二个参数:属性
 第三个参数:要调用的方法指针
 第四个参数:要传递给函数的参数
 */
pthread_t thread = nil;
pthread_create(&thread, NULL, run, NULL);
}
//(*) =>函数名称
void *run(void *str)
{
    for (NSInteger i =0 ; i<300; i++) {
    NSLog(@"%zd--%@",i,[NSThread currentThread]);  //均为子线程
    }
    return NULL;
}
@end

②NSThread

③GCD

1.使用步骤:任务 + 队列

//1 获得队列
 第一个参数:C语言的字符串 对队列的名称(com.520it.www.DownloadQueue)
 第二个参数:队列的类型
 DISPATCH_QUEUE_SERIAL  串行队列
 DISPATCH_QUEUE_CONCURRENT 并发队列

dispatch_queue_t queue = dispatch_queue_create("com.520ios.www.DownloadQueue”,DISPATCH_QUEUE_SERIAL);
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//2 封装任务并把任务添加到队列
dispatch_async(queue, ^{
    NSLog(@"download1---%@",[NSThread currentThread]);
});

拓展:GCD常用函数

1)一次性代码

-(void)once
{
    static dispatch_once_t onceToken;  //typedef long dispatch_once_t;
    //内部的实现原理:最开始的时候onceToken == 0 如果onceToken == 0 那么就执行一次,执行一次之后onceToken = -1
    dispatch_once(&onceToken, ^{
      NSLog(@"once"); 
    });
  }
应用场景:单例(注意:单例中使用一次性代码或同步锁均可,都是线程安全)

2)延迟执行

  dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_SERIAL);
  //   延迟2秒,然后再把任务提交到队列
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
    NSLog(@"GCD----%@",[NSThread currentThread]); //子线程=> 异步函数
});

3)快速迭代

 dispatch_queue_t queue =  dispatch_get_global_queue(0, 0);// 开启子线程协助主线程完成任务
//     dispatch_queue_t queue = dispatch_queue_create("good", DISPATCH_QUEUE_CONCURRENT);
//     dispatch_queue_t queue = dispatch_get_main_queue();//死锁 => 同步函数 + 主队列
//     dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_SERIAL);

dispatch_apply(10, queue, ^(size_t i) {
    NSLog(@"%zd---%@",i,[NSThread currentThread]); //内部执行顺序是并发的,外部却认为它没开子线程!!
});


应用场景 :遍历并剪切文件

-(void)moveFile
{
    //01 得到上层文件夹的路径
    NSString *fromPath = @"/Users/wuyuanping/Desktop/from";
    NSString *toPath = @"/Users/wuyuanping/Desktop/to";

    //02 得到上层文件夹中所有的文件
    NSArray *subPaths =  [[NSFileManager defaultManager] subpathsAtPath:fromPath];

    //03 遍历并剪切文件
    dispatch_apply(subPaths.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
   
        NSString *fileName = subPaths[index];
        NSString *fromFullPath = [fromPath stringByAppendingPathComponent:fileName];
        NSString *toFullPath = [toPath stringByAppendingPathComponent:fileName];
    
        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
    
        NSLog(@"%@--%@--%@",fromFullPath,toFullPath,[NSThread currentThread]); //子线程和主线程都有
   });
}

4)栅栏函数

dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
      NSLog(@"+++++%@",[NSThread currentThread]);
 });
应用场景:需求是哪些子线程任务先执行

5)队列组

方式一:
// 添加任务进组函数
//dispatch_group_async =>异步函数封装任务|提交到队列|监听任务是否执行完毕
//dispatch__async      =>异步函数封装任务|提交到队列

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
    NSLog(@"1----%@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"2----%@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"3----%@",[NSThread currentThread]);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"4----%@",[NSThread currentThread]);
});

dispatch_group_notify(group, queue, ^{ //组任务都执行完毕之后会调用此方法
    NSLog(@"---end---%@",[NSThread currentThread]); //子线程
});

方式二:
 //使用函数对来监听任务(dispatch_group_enter|dispatch_group_leave)
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);

//在该函数后面的异步任务会被group监听
dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"1----%@",[NSThread currentThread]);
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"2----%@",[NSThread currentThread]);
    
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"3----%@",[NSThread currentThread]);
    
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{ //组任务都执行完毕之后会调用此方法
    NSLog(@"---end---%@",[NSThread currentThread]);
});

拓展:GCD定时器

  -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//1 创建定时器对象(技巧:输入dispatch_source 再选GCD的会一次性出来四个函数)
/*
 第一个参数:创建的source的类型 DISPATCH_SOURCE_TYPE_TIMER 定时器事件
 第二个参数:描述信息
 第三个参数:更详细的描述信息
 第四个参数:队列(线程)[主队列那么回调就在主线程中执行,否则就在子线程中执行]
 */
 dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL));

//2 设置定时器对象
/*
 第一个参数:定时器对象
 第二个参数:开始时间(第一次执行的时间)
 第三个参数:间隔时间 GCD的时间单位是纳秒
 第四个参数:精准度(允许的误差,一般取0)
 */
dispatch_time_t timeT = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);//tips:取消一开始就执行一次,即达到也是延时两秒执行
dispatch_source_set_timer(timer, timeT, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

//3 设置定时器的事件
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"GCD---%@",[NSThread currentThread]); //非主队列,则为子线程
});

//4 执行定时器对象
dispatch_resume(timer);

self.timer = timer;//强引用,防止定时器一出作用域就销毁
}

GCD总结

④NSOperation

非主流开发演示:
//1.封装操作
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"3---%@",[NSThread currentThread]); //主线程
}];

//NSBlockOperation如果操作对象中封装的操作数量>1那么添加的代码块中就会开子线程和当前线程一起执行任务
[op3 addExecutionBlock:^{
    NSLog(@"+++++++4++++%@",[NSThread currentThread]); //子线程
}];
[op3 addExecutionBlock:^{
    NSLog(@"+++++++5++++%@",[NSThread currentThread]);//子线程
}];

//2 执行操作
[op3 start];


主流开发代码演示:
-(void)BlockOperationWithQueue
{   //操作 + 队列:只要不是主队列,就会开子线程并发执行任务
    
  //1.封装操作
  NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1----%@",[NSThread currentThread]);
  }];
  //NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];

  //2.把操作添加到队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:op1];  //addOperation 内部调用start方法,故你可以不写start

  //该方法内部先封装操作,然后把操作添加到队列中(简便写法:直接操作添加到队列)
  [queue addOperationWithBlock:^{
    NSLog(@"3---%@",[NSThread currentThread]); //子线程
  }];
}

拓展:操作队列的其他用法

自定义NSOperation:
#import “YPOperation.h"
@implementation YPOperation

-(void)main
{
  for (NSInteger i = 0; i<1000; i++) {
      NSLog(@"1---%zd--%@",i,[NSThread currentThread]);
      //if(self.isCancelled) 
      return;
  }

  //每执行完一段耗时操作之后就判断当前操作是否被取消,如果被取消了那么就直接退出
  if(self.isCancelled) return;

  NSLog(@"++++++++++++++");

  for (NSInteger i = 0; i<1000; i++) {
      NSLog(@"2---%zd--%@",i,[NSThread currentThread]);
       //if(self.isCancelled) return;
  }
   if(self.isCancelled) return;

  for (NSInteger i = 0; i<1000; i++) {
       if(self.isCancelled) return;
      NSLog(@"3---%zd--%@",i,[NSThread currentThread]);
   }
}
@end


例子:设置监听和操作依赖
 NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"3----%@",[NSThread currentThread]);
}];

 NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"4-下载电影-%@",[NSThread currentThread]);
}];   
 NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"+++++++5+%@+++",[NSThread currentThread]);
}];
//设置监听
op4.completionBlock = ^{
    NSLog(@"我已经被下载完了,快点来看我吧--%@",[NSThread     currentThread]);
};

//设置依赖(不能设置循环依赖)
[op3 addDependency:op4];
[op4 addDependency:op5];

注意:因为开启子线程自定义队列默认异步执行,故只能保证op4任务结束会执行他的监听Block,即block在op4后面执行,
     但无法保证一定紧接其后,要看具体情况)  

综合案例: 多图下载(利用上面所学的多线程技术)

3.线程状态
 新建 -> 就绪 <-> 运行 -> 死亡
          |
         阻塞
4.线程安全
应用:售票
-(void)saleTicket
{
     while (1) {  //让线程一直执行
         @synchronized (self) {
          //检查余票
          NSInteger count = self.totalCount;
          if (count >0) {
              //卖出去一张
              self.totalCount = count - 1;
              NSLog(@"%@卖出去了一张票还剩下%zd张票",[NSThread   currentThread].name,self.totalCount);
            }else
          {
              NSLog(@"%@发现票已经卖完了",[NSThread currentThread].name);
              break; //让线程死亡
          }
       }
    }
}
5.线程间通信
①利用NSThread方法(2种)
 [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO];
 [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
 //直接调用本质方法
 [self.imageView performSelector:@selector(setImage:) onThread:
 [NSThread mainThread] withObject:image waitUntilDone:YES];

②利用GCD方法
//同步函数+主队列(异步函数+ 主队列更主流)
 dispatch_sync(dispatch_get_main_queue(), ^{// 注意:当“同步函数+主队列”是写在子线程当中时是不会发生死锁的
        self.imageView.image = image;
        NSLog(@"UI---%@",[NSThread currentThread]);//主线程
});

③利用NSOperation方法
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
        
        self.imageView.image = image;
        NSLog(@"UI----%@",[NSThread currentThread]);
}];
6.RunLoop
  -(void)observer
{
  //1 创建观察者对象
  //block调用:当监听者发现runloop状态改变的时候会调用block
  CFRunLoopObserverRef observer =   CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"进入runloop");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"即将处理time事件");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"即将处理source事件");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"即将休眠");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"runloop被唤醒");
            break;
        case kCFRunLoopExit:
            NSLog(@"runloop退出");
            break;
            
        default:
            break;
      }
  });

  //2 给RunLoop添加观察者
/*
 NSDefaultRunLoopMode = kCFRunLoopDefaultMode
 NSRunLoopCommonModes = kCFRunLoopCommonModes  //追踪模式下的RunLoop也可以追踪
 */
  CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

  //3.释放Observer
  CFRelease(observer);
}
RunLoop总结
上一篇 下一篇

猜你喜欢

热点阅读