014-多线程

一、Pthreads

Pthreads:POSIX threads,线程的POSIX标准。该标准定义了创建和操纵线程的一套API。它是基于底层的,一般我们在iOS开发中也不会用到的

 

二、NSThread

1.概述

这套方案是经过苹果封装后的,并且完全是面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们程序员去手动管理,所以这套方案也是偶尔用用

2.使用方法

1)创建线程

// 第一种创建方法:手动创建,手动启动
// 1) SEL对象形式
// 创建线程
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run1:) object:nil];
// 启动线程
[thread1 start];
// 2) Block形式
// 创建线程
NSThread *thread2 = [[NSThread alloc] initWithBlock:^{
    // 代码...
}];
// 启动线程
[thread1 start];

// 第二种创建方法:手动创建,自动启动
// 创建线程之后,自动默认启动线程(注意:这种创建方法是拿不到这个线程对象)
// 1) SEL对象形式
[NSThread detachNewThreadSelector:@selector(run2:) toTarget:self withObject:nil];
// 2) Block形式
[NSThread detachNewThreadWithBlock:^{
    // 代码...
}];

2)其他属性和方法

// 启动线程
- (void)start;

// 取消线程
- (void)cancel;

// 暂停线程
+ (void)sleepForTimeInterval:(NSTimeInterval)time; // 使当前线程暂停一段时间
+ (void)sleepUntilDate:(NSDate *)date;  // 或者暂停到某个时刻

// 设置和获取线程名字
- (void)setName:(NSString *)name;
- (NSString *)name;

// 获取当前线程
+ (NSThread *)currentThread;

// 获取主线程
+ (NSThread *)mainThread;

 

三、GCD

1.概述

GCD:Grand Central Dispatch。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如:双核、四核等),最重要的是它会自动管理这些线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们程序员去管理,我们只需要告诉干什么就行。同时它使用的是C语言,不过由于使用了Block,使用起来就更加的方便灵活。所以基本上大家都使用GCD这套方案

2.任务&队列

1)任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block

a.同步执行(sync):只要是同步执行的任务,都会在当前线程依次执行,不会另开线程,并且会阻塞当前线程,造成GCD的死锁问题(后面会讲到)

b.异步执行(async):只要是异步执行的任务,都会另开线程

2)队列:用于存放任务

a.串行队列:存放当中的任务会依次执行(FIFO)

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

c.主队列:这是一个特殊的串行队列,而且队列中的任务一定会在主线程中执行

3)自由组合

可以看到,同步执行也不一定在主线程,异步执行也不一定是新开线程(考虑主队列)

3.GCD死锁问题
1)问题来源
在使用GCD的过程中,如果向当前串行队列中同步派发一个任务,就会导致死锁
2)案例分析

a.dispatch_sync表示同步执行任务,也就是说在dispatch_sync后,当前队列会阻塞

b.dispatch_sync中的block如果要在当前队列中执行,就是要等待当前队列程序执行完成

#pragma mark - GCD死锁问题
- (void)GCDLock
{
    NSLog(@"只会打印这一句");
    
    // 在主线程中使用同步执行方式
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"这一句不会打印");
    });
    
    NSLog(@"这一句也不会打印");
}

主线程在执行dispatch_sync,随后队列中新增一个任务block。因为主队列是同步队列,所以block要等dispatch_sync执行完之后才能执行,但是dispatch_sync是同步派发,要等block执行完才算结束。在主队列中的两个任务互相等待,导致了死锁

#pragma mark - GCD死锁问题
- (void)GCDLock
{
    NSLog(@"这一句会被打印");
    
    dispatch_queue_t queue02 = dispatch_queue_create("testQueue.id", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue02, ^{
        NSLog(@"这一句也会被打印");
        
        // 这里就造成死锁
        dispatch_sync(queue02, ^{
            NSLog(@"这一句不会被打印");
        });
        
        NSLog(@"这一句不会被打印");
    });
    
    NSLog(@"这一句不会被打印");
    
}

在queue02串行队列中添加同步任务,就会阻塞当前线程,当前线程中的任务就会去顺序执行,但是不巧的是在当前线程中又添加了一个同步执行任务,这样就造成了死锁

3)解决方案

一般我们也不会使用dispatch_sync.

4.队列组

队列组可以将很多队列添加到一个组里,这样做的好处就是,当这个组里面的所有任务都执行完毕,队列组会通过一个方法通知我们

- (void)test
{
    // 1.创建队列组
    dispatch_group_t group = dispatch_group_create();
    // 2.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 3.多次使用队列组的方法执行任务, 只有异步方法
    // 3.1.执行3次循环
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"group-01 - %@", [NSThread currentThread]);
        }
    });
    
    // 3.2.主队列执行8次循环
    dispatch_group_async(group, dispatch_get_main_queue(), ^{
        for (NSInteger i = 0; i < 8; i++) {
            NSLog(@"group-02 - %@", [NSThread currentThread]);
        }
    });
    
    //3.3.执行5次循环
    dispatch_group_async(group, queue, ^{
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"group-03 - %@", [NSThread currentThread]);
        }
    });
    
    // 4.该队列组group中所有的队列任务都执行完毕后会通知调用这个方法
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"完成 - %@", [NSThread currentThread]);
    });

}

5.常用的代码实例

1)开启子线程异步执行

// 开启子线程异步执行
for (int i = 0; i < 3; i++) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%d---%@", i, [NSThread currentThread]);
    });
}

打印结果:

2017-09-27 10:37:16.468 多线程[1665:111270] 2---<NSThread: 0x608000266300>{number = 5, name = (null)}

2017-09-27 10:37:16.468 多线程[1665:111253] 1---<NSThread: 0x608000266400>{number = 4, name = (null)}

2017-09-27 10:37:16.468 多线程[1665:111269] 0---<NSThread: 0x600000267040>{number = 3, name = (null)}

2)开启队列组异步执行

// 队列组
dispatch_group_t group = dispatch_group_create();

// 获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 子线程
for (int i = 0; i < 3; i++) {
    dispatch_group_async(group, queue, ^{
        NSLog(@"%d---%@", i, [NSThread currentThread]);
    });
}

// 队列组group全部执行完毕
dispatch_group_notify(group, queue, ^{
    NSLog(@"队列组group中任务都已经完成---%@", [NSThread currentThread]);
});

打印结果:

2017-09-27 10:42:43.262 多线程[1718:115691] 0---<NSThread: 0x6080000728c0>{number = 3, name = (null)}

2017-09-27 10:42:43.262 多线程[1718:115694] 1---<NSThread: 0x600000079ac0>{number = 4, name = (null)}

2017-09-27 10:42:43.262 多线程[1718:115692] 2---<NSThread: 0x608000072880>{number = 5, name = (null)}

2017-09-27 10:42:43.263 多线程[1718:115692] 队列组group中任务都已经完成---<NSThread: 0x608000072880>{number = 5, name = (null)}

3)回到主线程刷新UI

// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"%@", [NSThread currentThread]);
});

打印结果:

2017-09-27 10:45:03.104 多线程[1748:117677] <NSThread: 0x608000070e40>{number = 1, name = main}

 

四、NSOperation

1.概述

NSOperation:和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上他也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。但是需要注意的是,NSOperation是一个抽象类,不可以直接使用,我们往往使用的是它的子类:NSInvocationOperation和NSBlockOperation

2.NSOperation的通用方法

NSOperation *operation = [[NSOperation alloc] init];

// 开始执行
[operation start];

// 取消执行
[operation cancel];

// 执行结束后调用的Block
[operation setCompletionBlock:^{
    NSLog(@"执行结束");
}];

3.单独使用NSInvocationOperation或者NSBlockOperation--一般不会这么用,毫无意义

1)单独的使用NSInvocationOperation

- (void)testInvocationOperation
{
    // 创建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethon) object:nil];
    
    // 执行
    [invocationOperation start];
}

#pragma mark - 耗时操作
- (void)invocationOperationMethon
{
    NSLog(@"%@", [NSThread currentThread]);
}

代码打印:

2017-09-27 16:23:32.995 多线程[2540:339704] <NSThread: 0x60800007b640>{number = 1, name = main}

从打印的结果上来看:

NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西更没有什么卵用,它需要配个我们的队列NSOperationQueue(后面会讲到)

2)单独使用NSBlockOperation

- (void)testBlockOperation
{
    // 创建NSBlockOperation
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@", [NSThread currentThread]);
        NSLog(@"11111111111111111111111");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
        NSLog(@"2222222222222222");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"3---%@", [NSThread currentThread]);
        NSLog(@"33333333333333333");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"4---%@", [NSThread currentThread]);
        NSLog(@"44444444444444444");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"5---%@", [NSThread currentThread]);
        NSLog(@"555555555555555555");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"6---%@", [NSThread currentThread]);
        NSLog(@"6666666666666666666");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"7---%@", [NSThread currentThread]);
        NSLog(@"77777777777777777777777");
    }];
    
    // 执行
    [blockOperation start];
}

代码打印:

2017-09-27 16:29:30.183 多线程[2609:345650] 1---<NSThread: 0x608000262d00>{number = 1, name = main}

2017-09-27 16:29:30.183 多线程[2609:345650] 11111111111111111111111

2017-09-27 16:29:30.183 多线程[2609:345650] 5---<NSThread: 0x608000262d00>{number = 1, name = main}

2017-09-27 16:29:30.184 多线程[2609:345650] 555555555555555555

2017-09-27 16:29:30.183 多线程[2609:345704] 2---<NSThread: 0x608000270e80>{number = 3, name = (null)}

2017-09-27 16:29:30.184 多线程[2609:345650] 6---<NSThread: 0x608000262d00>{number = 1, name = main}

2017-09-27 16:29:30.184 多线程[2609:345650] 6666666666666666666

2017-09-27 16:29:30.184 多线程[2609:345650] 7---<NSThread: 0x608000262d00>{number = 1, name = main}

2017-09-27 16:29:30.184 多线程[2609:345650] 77777777777777777777777

2017-09-27 16:29:30.183 多线程[2609:345734] 3---<NSThread: 0x600000261a40>{number = 4, name = (null)}

2017-09-27 16:29:30.183 多线程[2609:345735] 4---<NSThread: 0x608000270f40>{number = 5, name = (null)}

2017-09-27 16:29:30.184 多线程[2609:345704] 2222222222222222

2017-09-27 16:29:30.185 多线程[2609:345734] 33333333333333333

2017-09-27 16:29:30.185 多线程[2609:345735] 44444444444444444

从打印结果来看:

a.NSBlockOperation确实是实现了多线程,但是我们可以看到,他并非是将所有的block都放到了子线程中,而是优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数是4(包括主线程在内)。如果block数量大于了4,那么剩下的block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程

b.同一个block中的代码是同步执行的

4.NSOperationQueue

针对上面单独使用的种种问题,我们一般会将NSOperation对象添加到NSOperationQueue队列中使用

NSOperationQueue:执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行

放入队列中的NSOperation对象不需要调用start方法,NSOperationQueue会在合适的时机去自动调用

1)NSOperationQueue + NSInvocationOperation

- (void)testOperationQueueWithInvocationOperation
{
    // 创建NSInvocationOperation
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethon) object:nil];
    
    // 将NSInvocationOperation对象放进NSOperationQueue队列中
    // 自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 主队列
    // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    [queue addOperation:invocationOperation];
}

#pragma mark - 耗时操作
- (void)invocationOperationMethon
{
    NSLog(@"%@", [NSThread currentThread]);
}

代码打印:

2017-09-27 16:39:01.784 多线程[2681:353746] <NSThread: 0x608000262cc0>{number = 3, name = (null)}

从打印结果上来看:

这才是真正意义上实现了NSInvocationOperation异步执行且都在子线程中执行

2)NSOperationQueue + NSBlockOperation

- (void)testOperationQueueWithBlockOperation
{
    // 创建NSBlockOperation
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1---%@", [NSThread currentThread]);
        NSLog(@"11111111111111111111111");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"2---%@", [NSThread currentThread]);
        NSLog(@"2222222222222222");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"3---%@", [NSThread currentThread]);
        NSLog(@"33333333333333333");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"4---%@", [NSThread currentThread]);
        NSLog(@"44444444444444444");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"5---%@", [NSThread currentThread]);
        NSLog(@"555555555555555555");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"6---%@", [NSThread currentThread]);
        NSLog(@"6666666666666666666");
    }];
    
    // 添加任务
    [blockOperation addExecutionBlock:^{
        NSLog(@"7---%@", [NSThread currentThread]);
        NSLog(@"77777777777777777777777");
    }];
    
    // 将NSBlockOperation对象放进NSOperationQueue队列中
    // 自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 主队列
    // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    [queue addOperation:blockOperation];
}

代码打印:

2017-09-27 16:42:20.803 多线程[2724:357054] 1---<NSThread: 0x608000263580>{number = 3, name = (null)}

2017-09-27 16:42:20.803 多线程[2724:357053] 4---<NSThread: 0x600000269080>{number = 6, name = (null)}

2017-09-27 16:42:20.803 多线程[2724:357070] 2---<NSThread: 0x600000268e40>{number = 4, name = (null)}

2017-09-27 16:42:20.803 多线程[2724:357056] 3---<NSThread: 0x608000263880>{number = 5, name = (null)}

2017-09-27 16:42:20.804 多线程[2724:357054] 11111111111111111111111

2017-09-27 16:42:20.804 多线程[2724:357053] 44444444444444444

2017-09-27 16:42:20.804 多线程[2724:357070] 2222222222222222

2017-09-27 16:42:20.804 多线程[2724:357056] 33333333333333333

2017-09-27 16:42:20.805 多线程[2724:357054] 5---<NSThread: 0x608000263580>{number = 3, name = (null)}

2017-09-27 16:42:20.806 多线程[2724:357070] 7---<NSThread: 0x600000268e40>{number = 4, name = (null)}

2017-09-27 16:42:20.806 多线程[2724:357053] 6---<NSThread: 0x600000269080>{number = 6, name = (null)}

2017-09-27 16:42:20.807 多线程[2724:357070] 77777777777777777777777

2017-09-27 16:42:20.807 多线程[2724:357054] 555555555555555555

2017-09-27 16:42:20.808 多线程[2724:357053] 6666666666666666666

从打印结果上来看:

我们看到的NSBlockOperation中的每一个Block也是异步执行且都在子线程中执行,每一个Block内部也依然是同步执行

5.更简单明了的方法

- (void)testOperationQueue
{
    // 自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 主队列
    // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    [queue addOperationWithBlock:^{
        // 这里是你想做的操作
    }];
}

6.依赖关系

NSOperationQueue最吸引人的无疑就是它的添加依赖功能

举例:A依赖于B,那么B在执行结束之前,A永远不会执行

- (void)testOperationQueueWithDependency
{
    // 创建NSInvocationOperation对象--oneInvocationOperation
    NSInvocationOperation *oneInvocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(oneInvocationOperationMethon) object:nil];
    
    // 创建NSInvocationOperation对象--twoInvocationOperation
    NSInvocationOperation *twoInvocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(twoInvocationOperationMethon) object:nil];
    
    // 添加依赖(oneInvocationOperation依赖于twoInvocationOperation)
    [twoInvocationOperation addDependency:oneInvocationOperation];
    
    // 添加到队列中
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:oneInvocationOperation];
    [queue addOperation:twoInvocationOperation];
}

#pragma mark - 耗时任务
// 1.第一个任务
- (void)oneInvocationOperationMethon
{
    NSLog(@"one---%@", [NSThread currentThread]);
}
// 2.第二个任务
- (void)twoInvocationOperationMethon
{
    NSLog(@"two---%@", [NSThread currentThread]);
}

代码打印:

2017-09-27 16:47:01.774 多线程[2774:360825] one---<NSThread: 0x608000075b80>{number = 3, name = (null)}

2017-09-27 16:47:01.775 多线程[2774:360808] two---<NSThread: 0x6080000762c0>{number = 4, name = (null)}

从打印结果可以看出:

第二个任务依赖于第一个任务,所以第二个任务必须在第一个任务执行完毕之后才能够执行

使用依赖关系有三点需要注意

a.不要建立循环依赖,会造成死锁,原因同循环引用

b.使用依赖建议只使用NSInvocationOperation。NSInvocationOperation和NSBlockOperation混用会导致依赖关系无法正常实现

c.依赖关系不光在同队列中生效,不同队列的NSOperation对象之前设置的依赖关系一样会生效

d.添加依赖的代码必须放在添加队列的代码前面,因为前面讲到:放入队列中的NSOperation对象不需要调用start方法,NSOperationQueue会在合适的时机去自动调用。如果依赖的代码必须在添加队列之后,我们根本就无法知道在添加依赖的两个任务是否已经执行完毕,如果执行完毕,那么添加依赖也变得毫无意义

7.NSOperationQueue的其他属性

1)NSOperationQueue提供暂停和取消两种操作

暂停:只需要设置NSOperationQueue的suspended属性为YES或NO即可

取消:可以调用NSOperation的的cancle方法取消当前线程,或者调用NSOperation的cancelAllOperations方法来取消该队列中的所有线程

这里要提醒的是:所谓的暂停和取消并不会立即暂停或取消当前操作,而是不再调用新的NSOperation

2)改变NSOperationQueue的最大并发数

改变NSOperationQueue的maxConcurrentOperationCount变量值

这里要提醒的是:最大并发数是有上限的,即使你设置为100,它也不会超过其上限,而这个上限的数目也是由具体运行环境决定的。设置最大并发数一定要在NSOperationQueue初始化后立即设置,因为上面说过,被放到队列中的NSOperation对象是由队列自己决定何时执行的,有可能你这边一添加立马就被执行。因此要想让设置生效一定要在初始化后立即设置

 

posted @ 2017-11-16 09:31  Frank9098  阅读(141)  评论(0)    收藏  举报