一、 进程
1.进程是操作系统分配资源的基本单元,是一个具有一定独立功能的程序关于某次数据集合的一次运行活动.
2.进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程,我们可以理解为手机上的一个app.
3.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内,拥有独立运行所需的全部资源
二、 线程
1.线程是程序执行流的最小单元,是进程中的一个实体.
2.一个进程要想执行任务,必须至少有一条线程,应用程序启动的时候,系统会默认开启一条线程,也就是主线程
三、 进程和线程的关系
1.线程是进程的执行单元,进程的所有任务都在线程中执行
2.线程是 CPU 分配资源和调度的最小单位
3.一个程序可以对应多个进程(多进程),一个进程中可有多个线程,但至少要有一条线程
4.同一个进程内的线程共享进程资源
四、多线程的优缺点
多线程的优点:
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
多线程的缺点:
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
五、任务
任务就是执行操作的意思,也就是在线程中执行的那段代码。在 GCD 中是放在 block 中的。
执行任务有两种方式:同步执行(sync)和异步执行(async)
同步(Sync):同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行,即会阻塞线程。只能在当前线程中执行任务(是当前线程,不一定是主线程),不具备开启新线程的能力。
异步(Async):线程会立即返回,无需等待就会继续执行下面的任务,不阻塞当前线程。可以在新的线程中执行任务,具备开启新线程的能力(并不一定开启新线程)。如果不是添加到主队列上,异步会在子线程中执行任务
六、队列
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务
在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。
两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列(Serial Dispatch Queue):
同一时间内,队列中只能执行一个任务,只有当前的任务执行完成之后,才能执行下一个任务。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)。主队列是主线程上的一个串行队列,是系统自动为我们创建的
并发队列(Concurrent Dispatch Queue):
同时允许多个任务并发执行。(可以开启多个线程,并且同时执行任务)。
注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
同步/异步和串行/并发
//同步分配一个任务到串行队列
- dispatch_sync(serial_queue,^{//任务});
//异步分配一个任务到串行队列
- dispatch_async(serial_queue,^{//任务});
//同步分配一个任务到并发队列
- dispatch_sync(concurrent_queue,^{//任务})
//异步分配一个任务到并发队列
- dispatch_async(concurrent_queue,^{//任务})
七、多线程
主要有三种:NSThread、NSoperationQueue、GCD
1. NSThread:轻量级的,更加面向对象,可直接操作线程对象
有三种创建方式分别为:
,手动开辟子线程,要手动启动,需要我们自己管理该线程,不只是启动,还有该线程使用完毕后的资源回收。
,直接自动启动。
,直接启动
// 当使用初始化方法出来的主线程需要start启动
[thread start];
// 可以为开辟的子线程起名字
thread.name = @"线程名称";
// 调整Thread的权限 线程权限的范围值为0 ~ 1 。越大权限越高,先执行的概率就会越高,由于是概率,所以并不能很准确的的实现我们想要的执行顺序,默认值是0.5
thread.threadPriority=1;
// 取消当前已经启动的线程
[thread cancel];
// 通过遍历构造器开辟子线程
[NSThread detachNewThreadSelector:@selector(testAction:) toTarget:self withObject:@"参数"];
//在当前线程。延迟1s执行。响应了OC语言的动态性:延迟到运行时才绑定方法
[self performSelector:@selector(testAction:) withObject:nil afterDelay:1];
// 回到主线程。
//waitUntilDone:是否将该回调方法执行完在执行后面的代码,
//如果为YES:就必须等回调方法执行完成之后才能执行后面的代码,说白了就是阻塞当前的线程;
//如果是NO:就是不等回调方法结束,不会阻塞当前线程
[self performSelectorOnMainThread:@selector(testAction) withObject:nil waitUntilDone:YES];
//开辟子线程
[self performSelectorInBackground:@selector(testAction) withObject:nil];
//在指定线程执行
[self performSelector:@selector(testAction) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]
//带afterDelay的延时函数,会在内部创建一个 NSTimer,然后添加到当前线程的Runloop中。也就是如果当前线程没有开启runloop,该方法会失效。在子线程中,需要启动runloop(注意调用顺序)
//performSelector:withObject:只是一个单纯的消息发送,和时间没有一点关系。所以不需要添加到子线程的Runloop中也能执行
[self performSelector:@selector(testAction) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
当调用 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
2、GCD,充分利用多核,自动管理生命周期
队列 | 执行方式 |
---|---|
串行 | 同步 |
并发 | 异步 |
主队列 |
组合方式 | 执行方式 | 创建线程 |
---|---|---|
串行同步 | 顺序 | 否 |
串行异步 | 顺序 | 是 |
并发同步 | 顺序 | 否 |
并发异步 | 交替 | 是 |
主队列同步 | 死锁 | 否 |
主队列异步 | 顺序 | 否 |
有3中queue
1.主队列 dispatch_main_queue(); 串行,更新UI
2.全局队列 dispatch_global_queue(); 并行 四个优先级 background、low、default、high
3.自定义队列 dispatch_queue_t(); 串行或并行 DISPATCH_QUEUE_CONCURRENT、DISPATCH_QUEUE_SERIAL
3、NSOperation 基于GCD的封装,抽象类,不能直接使用,有两个子类可用
NSInvocationOperation,开启后在主线程执行.
NSBlockOperation,开启后在主线程执行,addExecutionBlock方法在子线程中执行
maxConcurrentOperationCount
值 | 执行方式 |
---|---|
-1 | 并发执行,开多线程(默认) |
0 | 无法执行任务 |
1 | 串行执行,不开启线程 |
>1 | 并发执行(设置很大没有意义) |
4、GCD 和 NSOprationQueue的区别
GCD是面向底层的C语言的API
NSOpertaionQueue用GCD构建封装的,是GCD的高级抽象,完全面向对象,比GCD简单易用,代码可读性高。
1、GCD执行效率更高,而且由于队列中执行的是由block构成的任务,这是一个轻量级的数据结构,写起来更方便,GCD没法停⽌已经加⼊queue的block(其实是有的,但需要许多复杂的代码)
2、GCD只支持FIFO的队列,而NSOperationQueue可以通过设置最大并发数,设置优先级,添加依赖关系等调整执行顺序
3、NSOperationQueue甚至可以跨队列设置依赖关系,但是GCD只能通过设置串行队列,或者在队列内添加barrier(dispatch_barrier_async)任务,才能控制执行顺序,较为复杂,我们可以随时取消已经设定要准备执⾏的任务(当然,已经开始的任务就⽆法阻⽌了)
4、NSOperationQueue因为面向对象,所以支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
5.实例
NSLog(@"start--%@",[NSThreadcurrentThread]);
for(inti=0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"async--%@ i = %d",[NSThreadcurrentThread],i);
}); }
NSLog(@"end1--%@",[NSThreadcurrentThread]);
for(inti=0; i<10; i++) {
dispatch_sync(q, ^{
NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);
});
}
NSLog(@"end2--%@",[NSThreadcurrentThread]);
}
执行结果
执行结果.jpg
分析:先在主线程中执行打印start,然后将异步任务添加到串行队列中(只是添加,并没有立即执行),之后在主线程打印end1,再然后往串行队列里添加了1个同步任务,此时主线程堵塞,等待串行队列里地同步任务执行完毕。此时创建一个子线程开始依次执行异步任务,异步任务结束后,在主线程中执行同步任务(上面的结论)。1个同步任务执行完后(此时串行队列里没有任务了),主线程返回,for循环继续往串行队列里添加1个同步任务,此时主线程继续阻塞,等待串行队列里同步任务执行完毕,此时主线程执行这个同步任务,执行完毕后,主线程返回继续for循环……。等for循环结束后,最后在主线程执行打印end2的操作。
2.并行队列中先异步再同步
NSLog(@"start--%@",[NSThreadcurrentThread]);
for(inti=0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"async--%@---%d",[NSThreadcurrentThread],i);
});
}
NSLog(@"end1--%@",[NSThreadcurrentThread]);
for(inti=0; i<10; i++) {
dispatch_sync(q, ^{
NSLog(@"sync--%@---%d",[NSThreadcurrentThread],i);
});
}
NSLog(@"end2--%@",[NSThreadcurrentThread]);
执行结果
执行结果.jpg
分析:上面代码创建了一个并行队列,然后往并行队列中添加了10个异步任务和10个同步任务,上面只是某一次的打印结果,每次的打印结果都不一样,从打印结果可以看出:由于是并行队列,会开启多个子线程执行异步任务,所以异步任务的打印结果是无序的,而同步任务由于都是在主线程中执行,所有总体是有序的。而且同步与异步任务是交叉着执行完毕的。
3.死锁
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"123");
});
});
分析:外面的函数无论是同步还是异步都会造成死锁。 这是因为里面的任务和外面的任务都在同一个 serialQueue 队列内,又是同步,解决方法将里面的同步改成异步 dispatch_async,或者将 serialQueue 换成其他串行或并 行队列,都可以解决
NSLog(@"任务1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务2");
});
NSLog(@"任务三");
分析:先执⾏任务1,接下来,程序遇到了同步线程,它会进⼊等待,等待任务2执⾏完,然后执⾏任务3。但这是队列,有任务来,会将任务加到队尾,然后遵循FIFO原则执⾏任务,现在任务2就会被加到最后,任务3排在了任务2前⾯.
问题来了:任务3要等任务2执⾏完才能执⾏,任务2⼜排在任务3后⾯,意味着任务2要在任务3执⾏完才能执⾏,所以他们进⼊了互相等待的局⾯。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(test:) withObject:nil afterDelay:0];
});
分析:这里的 test 方法是不会去执行的,原因在于performSelector方法要创建提交任务到 runloop 上的,而GCD底层创建的线程默认是没有开启对应 runloop 的,所已 这个方法就会失效。 而如果将 dispatch_get_global_queue 改成主队列,由于主队列所在的主线程是默认开启runloop的, 就会去执行(将 dispatch_async 改成同步,因为同步是在当前线程执行,那么如果当前线程是主线程,test 方法也是会去执行的),也可以使用 GCD的dispatch_after来实现。
可以实现多读单写,多读单写的意思是可以多个读者同时读取数据,而在读的时候,不能取写入数据。并且在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。 这里的写处理就是通过栅栏的形式去写。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"------barrier------");
});
for (NSInteger i = 10; i < 20; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
分析:这里的 dispatch_barrier_sync 上的队列要和需要阻塞的任务在同一队列上,否则是无效的。 从打印上看,任务 0-9 和任务任务 10-19 因为是异步并发的原因,彼此是无序的。而由于栅栏函数的存在, 导致顺序必然是先执行任务 0-9,再执行栅栏函数,再去执行任务 10-19。
dispatch_barrier_sync: 提交一个栅栏函数在执行中,它会等待栅栏函数执行完
dispatch_barrier_async: 提交一个栅栏函数在异步执行中,它会立马返回)
而 dispatch_barrier_sync 和 dispatch_barrier_async 的区别也就在于会不会阻塞当前线程
比如,上述代码如果在 dispatch_barrier_async 后随便加一条打印,则会先去执行该打印,再去执 行任务 0-9 和栅栏函数;而如果是 dispatch_barrier_sync,则会在任务 0-9 和栅栏函数后去执行这 条打印。
在 n 个耗时并发任务都完成后,再去执行接下来的任务。比如,在 n 个网络请求完成后去刷新 UI 页 面。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (NSInteger i = 0; i < 10; i++) {
dispatch_group_async(group, concurrentQueue, ^{
sleep(1);
NSLog(@"%zd",i);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"主线程");
});
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号,保持线程同步,将异步执行任务转换为同步执行任务,保证线程安全,为线程加锁
Dispatch Semaphore 提供了三个函数
- dispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
- dispatch_semaphore_signal:发送一个信号,让信号总量加 1
- dispatch_semaphore_wait:可以使总信号量减 1,当信号总量为 0 时就会一直等待(阻塞所在线程),否 则就可以正常执行。
延时函数
dispatch_after 能让我们添加进队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指 定时间追加处理到 dispatch_queue
内部使用的是 dispatch_time_t 管理时间,而不是 NSTimer。 所以如果在子线程中调用,相比 performSelector:afterDelay,不用关心 runloop 是否开启
实现单利
+ (instancetype)sharInstance{
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
NSThread 在实际开发中比较常用到的场景就是去实现常驻线程。
由于每次开辟子线程都会消耗 cpu,在需要频繁使用子线程的情况下,频繁开辟子线程会消耗大量的 cpu,而且创建线程都是任务执行完成之后也就释放了,不能再次利用,最好是用常驻线程
+ (NSThread *)shareThread{
static NSThread *shareThread = nil;
static id instance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest1) object:nil];
[shareThread setName:@"threadTest"];
[shareThread start];
});
return shareThread;
}
+ (void)threadTest{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
//然后调用 performSelector 就可以啦
[self performSelector:@selector(test) onThread:[ViewController shareThread] withObject:nil waitUntilDone:NO];
- (void)test {
NSLog(@"test:%@",[NSThread currentThread]);
}
结构图.png
网友评论