iOS 多线程的简介及使用

来源:互联网 时间:2017-01-22


1 进程和线程
1.1 进程

进程是指程序在其内存地址的一次运行活动,各个进程之间是独立的,且各个进程都在其专用且受保护的内存空间运行。


1.2 线程

进程想要执行任务必须有线程,每一个进程必须要有一条线程,线程是进程的基本执行单元,线程依赖于进程而存活,一个进程里面可以有多个线程


2 串行队列和并行队列
2.1 队列的特点

队列只是负责任务的调度,而不负责任务的执行,任务是在线程中执行的。队列遵循先进先出原则,排在前面的任务最先执行


2.2 串行队列

任务按照顺序被调度,前一个任务不执行完毕,队列不会调度


2.3 并行队列

只要有空闲的线程,队列就会调度任务,不考虑前面是否有任务在执行,只要有线程可以利用,队列就会调度任务


3 同步和异步

同步执行:同步操作不管是在串行队列还是并行队列,不会新建线程,只会在当前线程按顺序执行。同步操作会阻碍当前线程并等待block中的任务执行完毕,然后当前线程才会往下执行。


异步执行:所有的异步执行都会开启新的线程,不会阻碍当前线程


4 队列的组合方式及特点

串行队列同步执行:不会开启新的线程,在当前线程任务按照顺序执行
串行队列异步执行:在其他线程任务按照顺序执行
并行队列同步执行:在当前线程任务按照顺序执行
并行队列异步执行:在多个线程任务并发执行


5 IOS的多线程方案
5.1 NSThread

NSThread是苹果封装的面向对象的多线程方案,它可以直接操纵线程对象,但是需要自己管理线程的生命周期。
用NSThread创建线程的两种方法:
1.创建一个NSThread的对象,并调用Start方法启动


NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:nil];
[thread start];

2.直接创建线程并启动


[NSThread detachNewThreadSelector:@selector(run:)toTarget:self withObject:nil];

同时还可以用NSThread获取当前线程并阻塞线程


NSThread *current = [NSThread currentThread]; //获取当前线程
NSThread *main = [NSThread mainThread];
//阻塞线程
[NSThread sleepForTimeInterval:3];
[NSThread sleepUntilDate:[NSDatedateWithTimeIntervalSinceNow:3]];

5.2 NSOperation&NSOperationQueue
5.2.1 NSOperation的优点

NSOperation可以控制线程的并发数以及线程之间的依赖关系,因为线程也是需要消耗系统资源的


5.2.2 NSOperation的使用方式

将要执行的任务封装到一个NSOperation对象中,然后将创建好的NSOperation对象放入到NSOperationQueue队列中,OperationQueue便开始启动新的线程去执行队列中的操作


NSOperation是一个抽象类,不能封装任务,它靠两个子类来进行任务的封装,分别是NSInvocationOperation和NSBlockOperation,创建一个NSOperation后需要调用start方法来启动任务,当然也可以调用cancel方法来取消任务,当创建完任务后,它会默认在当前队列同步执行(注意是在当前队列同步执行)
使用NSInvocationOperation创建:


NSInvocationOperation*operation = [NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) withObject:nil];
[operation start];

使用NSBlockOperation创建


NSBlockOperation *operation =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[operation start];

同时当在用NSBlockOperation的时候可以调用addExecutionBlock:方法来增加多个执行的block块,然后这些任务会在主线程和其他线程并发执行(此点需注意)


NSBlockOperation*operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是NSBlockOperation创建的线程");
NSLog(@"现在的线程是%@",[NSThreadcurrentThread]);
}];
for (int i = 0; i < 10; i++) {
[operation3 addExecutionBlock:^{
NSLog(@"这是NSBlockOperation创建的线程");
NSLog(@"现在的线程是%@",[NSThreadcurrentThread]);
}];
}
[operation3start];

另外addExecutionBlock必须在start方法之前调用,不然会报错


5.2.3 NSOperationQueue

NSOperationQueue只有主队列和其他队列,通过类方法来获得的是主队列,通过其他初始化方法获得的是其他队列,在主队列的任务会按照顺序执行,在其他队列的任务会在其他线程并发执行。
调用类方法获取主队列:


NSOperationQueue*main = [NSOperationQueue mainQueue];//使用类方法获取的是主队列
NSBlockOperation *operation4 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是加入主队列的");
}];
[mainaddOperation:operation4]; //加入队列的不用再调用start方法开始

使用其他初始化方法获取其他队列:


NSOperationQueue*other = [[NSOperationQueue alloc]init];//使用其他方法初始化的获取的是其他队列
for (int i = 0; i < 5; i++)
NSBlockOperation *operation5 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"执行的是%d 现在的队列是%@",i,[NSThread currentThread]);
}];
[other addOperation:operation5];
} //加入其他队里的任务在其他队列并发执行,不会占用主线程

在NSOperation里面没有串行队列,如果想要达到串行队列的效果可以调用setMaxConcurrentOperationCount设置最大并发数为1来达到串行效果
在NSOperationQueue中还可以用addOperationWithBlock:快速添加任务:


NSOperationQueue*other4 = [[NSOperationQueue alloc]init];
[other4 addOperationWithBlock:^{
NSLog(@"现在的线程是%@",[NSThreadcurrentThread]);
}];

NSOperation最大的特点就是可以设置线程间的依赖关系,因此,当某些操作必须按照顺序完成时可以用NSOperation直接设置依赖关系,不需要再去需找别的解决方案。


NSOperationQueue*other3 = [[NSOperationQueue alloc]init];
NSBlockOperation *operation7 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是执行的第一个 现在的线程是%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
NSBlockOperation *operation8 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是执行的第二个 现在的线程是%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:3];
}];
NSBlockOperation *operation9 =[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"这是执行的第三个 现在的线程是%@",[NSThread currentThread]);
}];
[operation8 addDependency:operation7];
[operation9 addDependency:operation8];
[other3addOperations:@[operation7,operation8,operation9] waitUntilFinished:NO];

当添加依赖关系后任务会在其他线程按照依赖关系执行(一般不会在主线程添加依赖关系执行),依赖是添加在任务上的和线程没关系,所以任务可以在不同的线程之间执行。但是添加依赖关系的时候不能添加相互依赖,这会造成死锁,同时也可以调用removeDependency解除依赖关系


5.3 GCD

Grand Central Dispatch是苹果为多核的并行运算提出的解决方案,它可以更合理的利用CPU内核,最大的好处是它可以帮助我们自动管理线程的生命周期(创建,调度和销毁),完全不需要我们管理


在GCD中分为3种队列,主队列,全局队列和其他队列,在GCD中任务分为同步执行和异步执行


使用GCD一般需要先创建队列,然后选择在队列里任务是同步执行的还是异步执行的


5.3.1 GCD创建队列的方式
主队列(串行队列):dispatch_queue_tqueue = dispatch_get_main_queue();

全局队列(并行队列)dispatch_queue_t global =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

自己创建的队列:这种根据标识符的不同可以分为串行队列和并行队列


串行队列:
dispatch_queue_tqueue1 = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL); //注意后面标识符
dispatch_queue_tqueue2 = dispatch_queue_create("serialQueue", nil); //后面没有标示符默认为串行

并行队列:


dispatch_queue_t queue3 = dispatch_queue_create("concurrentQueue",DISPATCH_QUEUE_CONCURRENT);//注意后面的标示符,这种是并行队列

5.3.2任务执行方式
同步任务:dispatch_sync(queue1,^{
for (int i = 0; i <5; i++) {
NSLog(@"现在的队列是%@ 下标是%d",[NSThreadcurrentThread],i);

异步任务: dispatch_async(queue1, ^{
for (int i = 0; i <5; i++) {
NSLog(@"现在的队列是%@ 下标是%d",[NSThreadcurrentThread],i);

5.3.3 队列组

当我们某些情况下需要一些任务完成后再执行另外一任务后可以考虑用队列组来实现,队列组是将所有任务加入到一个组里,当组里所有的任务都执行完毕了会发出一个通知来告诉我们,然后我们在通知里进行所想要的事件处理


dispatch_group_tgroup = dispatch_group_create();
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"这是任务一");
});
dispatch_group_async(group, queue, ^{
NSLog(@"这是任务二");
});
dispatch_group_async(group, queue, ^{
NSLog(@"这是任务三");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"三个任务完成了");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"去主线程处理某些事情");
});
});

5.3.4 dispatch_barrier_async的使用

dispatch_barrier_async 当它被加入到自己创建的,标示符为DISPATCH_QUEUE_CONCURRENT的并行队列时,它所在的任务会等它所加入的队列排在它前面的任务执行完毕后才会执行,排在它后面的任务会等它执行完成后才会执行。当它被加入别的队列时,它的作用跟dispatch_async类似,任务会并发执行


dispatch_queue_tqueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"这是任务一");
[NSThread sleepForTimeInterval:2];
});
dispatch_async(queue, ^{
NSLog(@"这是任务二");
[NSThread sleepForTimeInterval:3];
});
dispatch_barrier_async(queue, ^{
NSLog(@"这是任务三");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
NSLog(@"这是任务四");
});
dispatch_async(queue, ^{
NSLog(@"这是任务五");
});//任务3会等任务1和2执行完毕后才会执行,任务4和5 等任务3执行完毕后才会执行

dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"这是任务一");
[NSThread sleepForTimeInterval:2];
});
dispatch_async(queue, ^{
NSLog(@"这是任务二");
[NSThread sleepForTimeInterval:3];
});
dispatch_barrier_async(queue, ^{
NSLog(@"这是任务三");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
NSLog(@"这是任务四");
});
dispatch_async(queue, ^{
NSLog(@"这是任务五");
});//任务并发执行,任务三可能在第一个执行,也可能在最后一个执行

5.3.5 dispatch_once_t的使用

dispatch_once_t**使用来保证只会创建一份实例,一般常用于单例,但是在使用时注意一定要加static来修饰,不然它并不会保证只创建一个实例对象


staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//单例代码
});

5.3.6dispatch_after的使用

dispatch_after是延迟提交,并不是延迟执行,可以理解为这段代码式延迟了特定的时间才加入到队列,但是延迟时间到了后代码是否执行取决于队列的状态(比如队列休眠或者阻塞了那么这段代码肯定不会现在就执行只有等队列有多余的空闲才会执行)


dispatch_queue_tqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"begin");
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:8];
NSLog(@"second");
}); dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"thread");
});//thread在5秒后不会立即执行,因为队列处于休眠中,只有等8秒后队列空闲,thread才会被打印



相关阅读:
Top