进程和线程的区别
1.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,它是系统资源分配的基本单位。线程是处理机调度的最小单位。
2.进程有自己独立的地址空间;进程至少有一个线程,它们共享进程的地址空间,同一个进程内的线程共享进程的资源。
多线程是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。其本质上并不是同时执行多个程序,而是操作系统根据合理的算法在较短的时间内完成各个线程之间的切换,每个线程执行一段时间后,切换到下一个线程执行。
多线程的优点:能适当提高程序的执行效率;能适当提高资源利用率(CPU、内存利用率)。
多线程的缺点:开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能;线程越多,CPU在调度线程上的开销就越大;程序设计更加复杂:比如线程之间的通信、多线程的数据共享。
使用线程可以把占据时间长的程序中的任务放到后台去处理用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度程序的运行速度可能加快在一些等待的任务实现上如用户输入、文件读写和网络收发数据等。
iOS中实现多线程的方式
NSThread
轻量级别的多线程技术,需要我们手动来管理线程。提供的方法比较少,例如串行,并发执行这些实现起来相当困难。开辟子线程的方法有两种,一个是初始化需要手动开启,也就是调用start方法,并且有返回值,返回的就是NSThread对象,可以设置线程名称,设置线程的优先级等一些参数。另一种是便利构造器的方法开辟子线程无返回值,会自动启动线程。不需要手动调用start方法。
//通过NSThread开辟子线程
//object:这个是回调方法的参数
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread) object:nil];
thread.name = @"我是第二条线程";
//调整thread的优先级,线程权限的范围值为0~1,值越大优先级越高,默认值为0.5。由于是概率,所以并不能很准确的实现我们先要的执行顺序。
thread.threadPriority = 0.8;
//当使用初始化方法创建的子线程,需要start启动
[thread start];
//取消当前已经启动的线程
// [thread1 cancel];
线程的回调方法
- (void)testThread
{
//如果子线程是我们手动开辟的,那么就需要我们来回收它所占用的资源。
@autoreleasepool {
NSLog(@"%@",[NSThread currentThread]);
double sum = 0;
for (double i = 0; i < 500000000; i++) {
sum += i;
}
//[NSThread currentThread]获取当前线程
NSLog(@"sum------%f------%@",sum,[NSThread currentThread]);
}
NSObject
只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程,其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
开辟子线程:
[self performSelectorInBackground:@selector(aaa) withObject:nil];
进入主线程:
[self performSelectorOnMainThread:@selector(bbb) withObject:nil waitUntilDone:YES];
NSOperationQueue
它使OC级别的封装,是将一组事件添加到队列中,如果想让这组事件在主线程中执行,那么就需要主队列[NSOperationQueue mainQueue];如果想要将一组事件在子线程中执行那么就需要其他队列[[NSOperation alloc] init];NSOperation就是事件,它本身是一个抽象类,如果需要实现具体操作,需要它的两个子类:NSInvocationOperation和NSBlockOperation;事件本身和线程无关,只是看你将它加到那种队列中或者将事件放入主线程中。如果在队列中想要使得事件顺序执行,需要给事件添加依赖关系,添加依赖关系的时候两个事件不能互为依赖。也可以设置事件的优先级来提高它先执行的概率,但是不准确。还可以设置队列的最大并发执行的个数,来使得事件顺序执行。NSThread需要程序员手动管理,NSOperationQueue会自动管理线程。
- (void)operationQueue
{
//这个类只是执行一个操作,本身和线程无关,也就是把对象放到哪个线程之中,它就在哪个线程中执行。
NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationAction) object:nil];
//该事件需要手动启动
// [invocation start];
//通过block方式来增加事件
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block------%@",[NSThread currentThread]);
}];
//block方式的operation可以增加一组额外的block事件,通过这种方式为blockOperation添加的block事件的执行顺序无法掌控,所在的线程也无法掌控。
for (int i = 0; i < 5; i++) {
[blockOperation addExecutionBlock:^{
NSLog(@"execuBlock------%@",[NSThread currentThread]);
}];
}
//当所有的block事件都执行完成,我们可以让它发出通知,告诉我们所有事件都执行完成
blockOperation.completionBlock = ^{
NSLog(@"不管上面怎么执行,它肯定是最后一个执行的");
NSLog(@"%@",[NSThread currentThread]);
};
//只要是事件都需要手动启动
// [blockOperation start];
//事件不具备开辟线程的能力,所以我们需要配合queue来使用它,queue就是队列,其实队列就是用来处理一组时间,事件加到队列中,就不需要手动启动,也不用我们来关心它开辟线程之后的消耗,这些事情都是队列帮助我们处理。队列一般分为主队列和其他队列
//其他队列,可以将时间在除主线程的其他线程中处理,而且执行顺序不定(并发执行)通过设置最大并发数或者事件依赖,就可以将一组事件在子线程中顺序执行,达到串行的目的。
// NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//主队列,是将一组时间在主线程中执行,不用设置任何属性,一组事件都会顺序执行。
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//我们需要按照顺序执行一组事件,有两种方式
//第一种,设置该队列最大并发执行事件的个数,该属性默认值为-1,该队列中有多少个事件,就并发执行多少个。如果并发事件设置为1,那就是一次只执行一个事件
// queue.maxConcurrentOperationCount = 1;
//第二种方式,就是添加事件依赖,事件依赖意思就是,当一个时间执行完毕才会执行另一个事件,这里就是先执行blockOperation,在执行invocation
[blockOperation addDependency:invocation];
//给队列中添加事件
[queue addOperation:invocation];
[queue addOperation:blockOperation];
}
GCD
GCD效率比operationQueue要高一些,功能更强大,目前有替代其他多线程方式的趋势,它处理事件主要是通过队列来执行。分为两种队列,一种是串行,一种是并行。系统提供给我们的一种是全局队列,一种是主队列。添加事件的函数为:dispatch_async();一般我们都是用异步添加时间,最重要的原因是它不会阻塞当前线程。全局队列中所添加的异步时间肯定都是在子线程中的。主队列中添加的事件不管是异步还是同步都是在主线程中。
同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
同步函数:
(1)并发队列:不会开线程
(2)串行队列:不会开线程
异步函数:
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
补充:凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。
GCD的数据类型在ARC的环境下不需要再做release。
异步函数具备开线程的能力,但不一定会开线程。
串行队列
//创建一个串行队列
//dispatch_queue_create函数是用来创建队列使用,第一个参数为该队列的标签;第二个参数为该队列类型,通常设置为NULL
dispatch_queue_t serialQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
//给该队列添加事件
//第一个参数为该事件所在的队列;第二个参数为block,该时间所要做的处理
dispatch_async(serialQueue, ^{
NSLog(@"洛洛出生了---%@",[NSThread currentThread]);
});
并行队列
//创建一个并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"我是第1个---%@",[NSThread currentThread]);
});
//此函数会阻塞当前线程,对主线程无影响
dispatch_barrier_async(queue, ^{
NSLog(@"我在执行---%@",[NSThread currentThread]);
});
系统提供的全局队列
- (void)globalQueue
{
//得到系统的提供的全局队列
//dispatch_get_global_queue(0, 0);
//第一个参数为该全局队列的优先级
//第二个参数暂时没用,是系统为了后面扩展来使用,在将来的某一天会使用到。直接赋值为0就可以
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
NSLog(@"正在网络下载或者一些其他耗时的操作");
//耗时操作完成之后为主线程更新UI,GCD回主线程方式
//要得到主队列,和operationQueue中的mainQueue是一样的概念
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
//在此处进行UI刷新
NSLog(@"mainQueue---%@",[NSThread currentThread]);
});
});
}
GCD中代码的延时执行
//第一个参数是说从什么时候开始计时
//多少秒之后执行block中的操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"5秒之后执行");
});
NSObject代码的延时执行
[self performSelector:@selector(afterAction:) withObject:@"五秒之后执行" afterDelay:5];