有人问我线程的问题,我简单总结一下。
要知其然,更要知其所以然
首先,什么是线程?
一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。
iOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线成在程序中的位置和其他线程不同,它是其他线程最终的父线程,且所有的界面的显示操作即AppKit或UIKit的操作必须在主线程进行。
系统中每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则公用进程的内存空间。
每创建一个新的进成,都需要一些内存(如每个线程有自己的stack空间)和消耗一定的CPU时间。
当多个进成对同一个资源出现争夺的时候需要注意线程安全问题
网络传输方式
a:同步:所有任务放在一个线程中完成,只要当前的任务没有完成 那么下一个任务就处于阻塞的状态
b:异步:多个任务放在多个线程中完成 即使当前任务没有完成 也不会永祥其他任务的执行数据传输方式
a: 串行:一个线程中只有当前任务完成以后下一个任务才能执行
b: 并发:多个线程完成不同的任务 互不干扰
1.NSThread
//只需要创建线程 不需要手动开启线程
//线程对象只要被创建 就自动开始执行任务
[NSThread detachNewThreadSelector:@selector(threadMain1:) toTarget:self withObject:num];
或者
NSNumber *num = [NSNumber numberWithInt:100];
//创建线程
//2。
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMain2:) object:num];
//必须手动开启线程
[thread start];
//只要通知结束就会调用threadExit:这个方法 这个方法会被调用(有几个线程,调用几次)
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExit:) name:NSThreadWillExitNotification object:nil];
// UI刷新
[self performSelectorOnMainThread:@selector(refreshUI) withObject:nil waitUntilDone:NO];
注释:资源争夺的时候,比如卖火车票,不能一张卖多人,就需要加锁 lock 完事了unlock
2.NSOperation
2.1.创建队列的对象
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
2. 2.设置队列中盛放线程的最大个数
[queue setMaxConcurrentOperationCount:4];
2. 3.队列中得优先级都是平等的
//(1)创建单独子线程
NSInvocationOperation *test1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(threadMain1:) object:nil];
//线程执行结束以后触发先面对而方法
[test1 setCompletionBlock:^{
NSLog(@"text1线程执行结束");
}];
//(2).创建block类型的线程
NSBlockOperation *test2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 100; i++) {
NSLog(@"test2 i=%d",i);
[NSThread sleepForTimeInterval:0.1];
}
}];
[test2 setCompletionBlock:^{
NSLog(@"test2线程执行结束");
//线程执行结束以后 会自动从队列中移除并且退出
}];
[queue addOperation:test2];
2.4.将子线程放在队列中
[queue addOperation:test1];
//子线程放入队列中 就会立即执行线程方法
3.GCD
GCD是基于C语言底层API实现的一套多线程并发机制,非常的灵活方便,在实际的开发中使用很广泛。
简单来说CGD就是把 操作 放在 队列 中去执行。
自动管理线程的生命周期,自动利用更多的CPU内核
3.1. 队列的创建方法
- 可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并发队列。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
- 对于并发队列,还可以使用dispatch_get_global_queue来创建全局并发队列。GCD默认提供了全局的并发队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// 同步执行任务创建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
各两种,所以就有四种情况了
1.并发队列 + 同步执行
不会开启新线程,执行完一个任务,再执行下一个任务
//并发队列 + 同步执行
-(void)GCD1{
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncConcurrent---end");
}
输出
syncConcurrent---begin
1------<NSThread: 0x60000007f200>{number = 1, name = main}
1------<NSThread: 0x60000007f200>{number = 1, name = main}
2------<NSThread: 0x60000007f200>{number = 1, name = main}
2------<NSThread: 0x60000007f200>{number = 1, name = main}
3------<NSThread: 0x60000007f200>{number = 1, name = main}
3------<NSThread: 0x60000007f200>{number = 1, name = main}
syncConcurrent---end
2. 并发队列 + 异步执行
- 可同时开启多线程,任务交替执行
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncConcurrent---end");
输出
asyncConcurrent---begin
asyncConcurrent---end
1------<NSThread: 0x60000007e680>{number = 3, name = (null)}
2------<NSThread: 0x608000079fc0>{number = 4, name = (null)}
3------<NSThread: 0x60800007b140>{number = 5, name = (null)}
1------<NSThread: 0x60000007e680>{number = 3, name = (null)}
2------<NSThread: 0x608000079fc0>{number = 4, name = (null)}
3------<NSThread: 0x60800007b140>{number = 5, name = (null)}
- 除了主线程,又开启了3个线程,并且任务是交替着同时执行的。
- 所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行。
3. 串行队列 + 同步执行
- 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncSerial---end");
输出
syncSerial---begin
1------<NSThread: 0x608000064a00>{number = 1, name = main}
1------<NSThread: 0x608000064a00>{number = 1, name = main}
2------<NSThread: 0x608000064a00>{number = 1, name = main}
2------<NSThread: 0x608000064a00>{number = 1, name = main}
3------<NSThread: 0x608000064a00>{number = 1, name = main}
3------<NSThread: 0x608000064a00>{number = 1, name = main}
syncSerial---end
- 所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行。
- 所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的
4. 串行队列 + 异步执行
- 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncSerial---end");
输出
asyncSerial---begin
asyncSerial---end
1------<NSThread: 0x608000267800>{number = 3, name = (null)}
1------<NSThread: 0x608000267800>{number = 3, name = (null)}
2------<NSThread: 0x608000267800>{number = 3, name = (null)}
2------<NSThread: 0x608000267800>{number = 3, name = (null)}
3------<NSThread: 0x608000267800>{number = 3, name = (null)}
3------<NSThread: 0x608000267800>{number = 3, name = (null)}
- 开启了一条新线程,但是任务还是串行,所以任务是一个一个执行。
- 所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。
But别忘记了我们的主队列
主队列:GCD自带的一种特殊的串行队列
- 所有放在主队列中的任务,都会放到主线程中执行
- 可使用dispatch_get_main_queue()获得主队列
So 我们还有两种了
1.主队列 + 同步执行
互等卡住不可行(在主线程中调用)
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncMain---end");
输出
2017-06-07 17:04:12.154 GCD[20860:682212] syncMain---begin
(lldb)
原因:我们把任务放在了主队列中,也就放在了主线程的队列中。同步执行就是对任务立马执行。把第一个任务放在了主队列中,立马执行,但是主队列还要继续往下走,主队列中第一个任务要执行,主队列却要继续执行第二个任务第三个,so就卡住了。
假设不在主线程中调用
不会开启新线程,执行完一个任务,再执行下一个任务(在其他线程中调用)
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[self syncMain];
});
syncMain---begin
1------<NSThread: 0x60000007ed80>{number = 1, name = main}
1------<NSThread: 0x60000007ed80>{number = 1, name = main}
2------<NSThread: 0x60000007ed80>{number = 1, name = main}
2------<NSThread: 0x60000007ed80>{number = 1, name = main}
3------<NSThread: 0x60000007ed80>{number = 1, name = main}
3------<NSThread: 0x60000007ed80>{number = 1, name = main}
syncMain---end
- 在其他线程中使用主队列 +
同步执行可看到:所有任务都是在主线程中执行的,并没有开启新的线程。而且由于主队列是串行队列,所以按顺序一个一个执行。 - 同时我们还可以看到,所有任务都在打印的syncConcurrent—begin和syncConcurrent—end之间,这说明任务是添加到队列中马上执行的。
2. 主队列 + 异步执行
只在主线程中执行任务,执行完一个任务,再执行下一个任务
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncMain---end");
输出
asyncMain---begin
asyncMain---end
1------<NSThread: 0x600000073dc0>{number = 1, name = main}
1------<NSThread: 0x600000073dc0>{number = 1, name = main}
2------<NSThread: 0x600000073dc0>{number = 1, name = main}
2------<NSThread: 0x600000073dc0>{number = 1, name = main}
3------<NSThread: 0x600000073dc0>{number = 1, name = main}
3------<NSThread: 0x600000073dc0>{number = 1, name = main}
- 我们发现所有任务都在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并且一个接一个执行
- 所有任务是在打印的syncConcurrent—begin和syncConcurrent—end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。
开发中大多数还是用的GCD,那就多说点
补充1: GCD的栅栏方法
假设你想异步执行两组操作,一组操作完才可以执行另一组,dispatch_barrier_async就应运而生了
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
输出
----1-----<NSThread: 0x60000007e080>{number = 3, name = (null)}
----2-----<NSThread: 0x608000077e80>{number = 4, name = (null)}
----barrier-----<NSThread: 0x608000077e80>{number = 4, name =
----4-----<NSThread: 0x60000007e080>{number = 3, name = (null)}
----3-----<NSThread: 0x608000077e80>{number = 4, name = (null)}
上面执行完,才执行下面的
补充2:GCD的延时执行方法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
NSLog(@"run-----");
});
补充3:GCD只执行一次
使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。
补充4:GCD快速迭代方法
和for循环遍历一样,GCD中是dispatch_apply,而且是同时遍历
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd------%@",index, [NSThread currentThread]);
});
输出
0------<NSThread: 0x600000077d40>{number = 1, name = main}
2------<NSThread: 0x6000002636c0>{number = 3, name = (null)}
3------<NSThread: 0x608000262380>{number = 5, name = (null)}
1------<NSThread: 0x608000262240>{number = 4, name = (null)}
4------<NSThread: 0x600000077d40>{number = 1, name = main}
5------<NSThread: 0x6000002636c0>{number = 3, name = (null)}
补充5: GCD的队列组
有多个耗时操作,全完成之后,再返回主线程,就要用到队列组
调用队列组的dispatch_group_notify回到主线程执行操作。
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});