多线程

625 阅读6分钟

一、多线程方案

默认情况下,主线程占用1M,子线程占用512KB

  • NSThread 面向对象的,手动创建线程,不需要手动销毁。子线程间通信很难。
  • GCD c语言,充分利用了设备的多核,自动管理线程生命周期。比NSOperation效率更高。
  • NSOperation 基于gcd封装,更加面向对象,比gcd多了一些功能。
GCD和NSOperation的对比:
  • GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构——写起来更加方便。
  • GCD只支持FIFO的队列,需要通过信号量才能达到并发。
  • NSOpration可以设置最大并发数(maxConcurrentOperationCount)、设置优先级、添加依赖关系等调整执行顺序
  • NSOpration甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行或者并行队列,或者在队列内添加barrier任务才能控制执行顺序,较为复杂
  • NSOperation支持KVO(面向对象)可以检测operation是否正在执行、是否结束、是否取消
GCD执行原理:

GCD有一个底层线程池,这个池中存放的是一个个的线程。“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。

NSOperation:

NSInvocationOperation 程序在主线程执行,没有开启新线程。需要配合队列NSOperationQueue。 NSBlockOperation 程序在主线程执行,没有开启新线程。需要配合队列NSOperationQueue。 NSOperation 子类重写它的main方法,是需要配合队列NSOperationQueue来实现多线程的。

NSOperationQueue:

主队列、其他队列。 主队列上的任务是在主线程执行

1、非主队列(其他队列)可以实现串行或并行。

2、队列NSOperationQueue有一个参数叫做最大并发数,maxConcurrentOperationCount默认为-1,直接并发执行,所以加入到‘非队列’中的任务默认就是并发,开启多线程。当maxConcurrentOperationCount为1时,则表示不开线程,也就是串行。当maxConcurrentOperationCount大于1时,进行并发执行。

NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool,GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool

二、线程安全

1、NSLock

iOS 多线程开发中为保证线程安全而常用的几种锁:NSLock、dispatch_semaphoreNSConditionNSRecursiveLockNSConditionLock@synchronized`, 这几种锁各有优点,适用于不同的场景,下面我们就来依次介绍一下。

什么情况使用自旋锁比较划算?

预计线程等待锁的时间很短 加锁的代码(临界区)经常被调用,但竞争情况很少发生 CPU资源不紧张 多核处理器

什么情况使用互斥锁比较划算?

预计线程等待锁的时间较长 单核处理器 临界区有IO操作 临界区代码复杂或者循环量大 临界区竞争非常激烈

2、信号量
// 根据一个初始值创建信号量
dispatch_semaphore_create(信号量值)
// 如果信号量的值<=0,当前线程就会进入休眠等待(直到信号量的值>0);如果信号量的值>0,就减1,然后往下执行后面的代码。
dispatch_semaphore_wait(信号量,等待时间)
// 提高信号量(让信号量的值加1)
dispatch_semaphore_signal(信号量)

dispatch_group_enter(请求前)
dispatch_group_leave(请求回调后)
dispatch_group_notify

信号量的销毁会调用_dispatch_semaphore_dispose函数,而此函数会执行信号当前值与初始化值的比较,如果小于初始化值,则直接抛出崩溃。

3、栅栏函数
  • dispatch_barrier_async:阻塞了队列执行,不阻塞线程执行
  • dispatch_barrier_sync:阻塞了队列执行,阻塞线程执行

栅栏函数的读写锁: 读:dispatch_sync (并发同步) 写:dispatch_barrier_async

4、串行队列

并行才是真正的多线程,而并发只是在多任务中切换。一般多核CPU可以并行执行多个线程,而单核CPU实际上只有一个线程,多路复用达到接近同时执行的效果。

5、同步函数

dispatch_sync

三、死锁

1、同步线程往串行队列中添加任务(主线程里同步执行任务) 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题

2、NSOperationQueue通过addDependency循环从属或者互相从属

3、NSLock已上锁的情况下再次上锁,形成彼此等待的局面(NSLock换成递归锁NSRecursiveLock)

四、典型的“多读单写”

pthread_rwlock:读写锁 dispatch_barrier_async:异步栅栏调用

五、dispatch_semaphore_t

控制线程并发数
//定义一个信号量,初始化为10
dispatch_semaphore_t semaphore =  dispatch_semaphore_create(10);
//同时执行100个任务
for  (int i =  0; i <  100; I++) {
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,  0),  ^{
         //当前信号量-1
         dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
         NSLog(@"任务%d执行",i+1);
         NSData *data =  [NSData dataWithContentsOfURL:[NSURL URLWithString:kUrlString]];
         dispatch_async(dispatch_get_main_queue(),  ^{
             //TODO:刷新界面
         });
         //当前信号量+1
         dispatch_semaphore_signal(semaphore);

         });
}

线程安全

初时值为1时,就可以实现线程同步。

-  (void)viewDidLoad {
    [super viewDidLoad];
    semaphore = dispatch_semaphore_create(1);
    self.array =  [NSMutableArray array];
    for  (int i=0; i<100; i++)  {
        NSThread *thread =  [[NSThread alloc]initWithTarget:self selector:@selector(test) object:nil];
        [thread start];
     }
}

-  (void)test {
    NSLog(@"测试开始");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    sleep(3);
    [self.array addObject:@"0"];
    dispatch_semaphore_signal(semaphore);
    NSLog(@"测试");
}
多个网络请求同步

首先通过网络请求一获取用户useid,之后用userid为参数发起网络请求二。

-  (void)viewDidLoad {
    [super viewDidLoad];
    //创建一个并行队列
    dispatch_queue_t queque =  dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queque,  ^{
        dispatch_semaphore_t semaphore=  dispatch_semaphore_create(0);  // 创建信号量
    [self getuserId:semaphore];
    dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
    [self requestwithuserid:useid];
     });
}

pragma mark - 网络请求一

-  (void)getuserId:(dispatch_semaphore_t)semaphore {
    AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
    sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [sessionmanger POST:@"[https://www.baidu.com/](https://www.baidu.com/)" parameters:nil constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject)  {
    NSLog(@"请求成功1%@",  [NSThread currentThread]);
    useid=@"1234";
    dispatch_semaphore_signal(semaphore);
 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error)  {
    NSLog(@"%@",error);
    dispatch_semaphore_signal(semaphore);

    }];
}

pragma mark - 网络请求二

-  (void)requestwithuserid:(NSString *)userid {
    NSDictionary *parms=[NSMutableDictionary dictionary];
    [parms setValue:userid forKey:@"userid"];
    AFHTTPSessionManager *sessionmanger=[[AFHTTPSessionManager alloc]init];
    sessionmanger.responseSerializer=[AFHTTPResponseSerializer serializer];
    [sessionmanger POST:@"[https://www.baidu.com/](https://www.baidu.com/)" parameters:userid constructingBodyWithBlock:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject)  {
        NSLog(@"请求成功2");
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error)  {
        NSLog(@"%@",error);
    }];
}

多个网络请求后刷新UI

dispatch_group_t downloadGroup = dispatch_group_create();
for (int i=0; i<10; i++) {
    dispatch_group_enter(downloadGroup);
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        dispatch_group_leave(downloadGroup);
    }];
    [task resume];
}

dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
       count++;
       if (count==10) {
           dispatch_semaphore_signal(sem);
           count =  0;
        }
    }];
    [task resume];
}

dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{

});

多个网络请求顺序执行后执行下一步

dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i=0; i<10; i++) {
    NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        dispatch_semaphore_signal(sem);
    }];
    [task resume];
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

异步操作两组数据时, 执行完第一组之后, 才能执行第二组

  • 这里使用dispatch_barrier_async栅栏方法即可实现
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    NSLog(@"第一次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第二次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"第一次任务, 第二次任务执行完毕, 继续执行");
});

dispatch_async(queue, ^{
    NSLog(@"第三次任务的主线程为: %@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
    NSLog(@"第四次任务的主线程为: %@", [NSThread currentThread]);
});