一.多进程概述:
进程
:
一个正在运行的程序
叫做一个进程
多进程
:
多个程序正在运行
叫做多线程
线程
:
一个进程
都会有一个或多个线程
如果只有一个
叫做主线程
主线程负责用户能看见的任务
例如
添加控件
刷新页面
除了主线程以外
都叫子线程
线程之间是独立的
并没有任何联系
.
子线程一般负责用户不直接看到的任务
例如加载图片的过程
,
下载视频等
线程要明确的
:
只要用户看得见的
或者跟用户看得见有关的
都使用主线程
进行操作.
因为开启子线程
进行一些操作的时候
为了更好的用户体验
用户体验最直接的表现为
看到的或者点击的流畅
线程是耗费资源的
,
虽然可以多线程操作
提高用户体验
但是不建议进行
很多线程同时进行操作
二.NSThread
NSThread是一个轻量级的多线程. 有两种创建方法:
1. - (id)initWithTarget:(id)target selector:(SEL
)selector object:(id)
argument;
2. + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)aTarget withObject:(id)argument;
3. start
4. cancel
// 创建一个线程 就是给它一个方法去执行
#pragma mark -- NSThread
// 创建一个子线程 专门打印用
// self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(actionPrint:) object:nil];
// 开启子线程
// [self.thread start];
// 线程操作的时候
// 在主线程的时候系统自动给了一个自动释放池 那么咱们在操作子线程的时候也要添加一个自动释放池
// 如果线程开辟的比较多 代码 比较乱 阅读性 不高
// 一般方法中 添加了自动释放池 一般都是线程的方法
三.NSOperation
1).
NSOperation 概述
NSOperation 类 在MVC中属于M, 是用来封装单个任务相关的代码和数据的抽象类
因为它是抽象类 不能直接使用该类, 只能使用子类(NSInVocationOperation / NSBlockOperation) 来执行实际任务.
NSOperation 包括其子类, 只是一个操作 , 本身无主线程和子线程之分, 可在任意线程使用,通常与NSOperationQueue 结合使用
NSOperationQueue 是操作队列,它用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并发执行
其中NSOperation 可以调节它在队列中的优先级
最大并发数为1 时,能实现线程同步
#pragma mark -- NSOperation
// NSOperation 也是一个抽象类 没有实现具体功能
// NSBlockOperation 在block中操作 (相当于任务)
// NSInvocationOperation 调用操作 (相当于任务)
// NSOperationQueue 线程队列
// 创建任务1
NSInvocationOperation *invocation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocation1) object:nil];
// 创建任务2
// block块儿中就相当于 添加的任务
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
[self block];
}];
// 创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 依赖性 (串行)
/**
* 注意 : 添加任务前 要先设置 依赖关系
*/
// 前面的完事了 后面的才能开始
[invocation1 addDependency:block2];
// 把任务添加进队列当中
[queue addOperation:invocation1];
[queue addOperation:block2];
// 设置最大并发数
queue.maxConcurrentOperationCount = 2;
- (void)invocation1
{
// 子线程中 添加一个自动释放池
@autoreleasepool {
// 是不是主线程
// 当前线程的信息
NSLog(@"我是 invocation1 %@, %d", [NSThread currentThread],[NSThread isMainThread]);
}
}
// 创建任务2 的方法
- (void)block
{
// 先添加释放池
@autoreleasepool {
// 是不是主线程
// 当前线程的信息
NSLog(@"%@, %d", [NSThread currentThread],[NSThread isMainThread]);
/*
{number = 2, name = (null)}, 0
打印出来的 number 是线程的个数 表示当前是第几个
*/
}
}
2).利用NSOperation 同步加载一张图片 不出现卡顿的现象
NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
// 在子线程里面添加自动释放池
@autoreleasepool {
// 任务: 加载图片
NSURL *url = [NSURL URLWithString:kPictureUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"GET"];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
// 以上是同步请求不加线程 应该卡
// 回到主线程去显示图片
[self performSelectorOnMainThread:@selector(imageViewWithData:) withObject:data waitUntilDone:YES];
}
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:blockOP];
// 回到主线程 显示图片
- (void)imageViewWithData:(NSData *)data
{
NSLog(@"%d", [NSThread isMainThread]);
// 注意: 子线程加载完 一定要回答主线程 刷新界面
self.imageView.image = [UIImage imageWithData:data];
}
四.NSObject
NSObject 中存在了一个最简单的后台执行方法
- (void)performSelectorInBackGround:(SEL)selector withObject:(id)
arguement;
#pragma mark -- NSObject
// NSObject 也提供了 开启子线程的方法 也可以解决程序卡死的问题
// [self performSelectorInBackground:@selector(actionPrint:) withObject:nil];
五.GCD(Grand Central Dispatch)使用
Grand Central Dispatch是苹果公司开发的技术, 以优化应用程序支持多核心处理器和其他的对称多处理系统的系统.
GCD 属于函数级的多线程, 性能更高,功能更强大.
任务: 具有一定功能的代码段, 一般是一个Block 或者是 函数.
分发队列: GCD 以队列的方式进行工作.FIFO.(first in first out)
GCD 会根据分发队列的类型, 创建合适数量的线程执行队列中的任务.
GCD 的部分功能
1). dispatch_async() 往队列中添加任务 任务会排队执行
2). dispatch_after() 往队列里添加任务 还会延迟的时间点执行
3). dispatch_apply()
往队列里添加任务 任务会重复执行n次
4).
dispatch_group_async()
将任务添加到队列中,并添加分组标记
5).
dispatch_group_notify() 将任务添加到队列中, 当某个分组的所有任务都执行完成后 此任务才会执行
6).dispatch_barrier_async() 将任务添加到队列中,此任务执行的时候,其他任务停止执行
7).dispatch_once() 只执行一次
dispatch queue 分为以下两种:
1). SerialQueue: 串行的,一次执行一个任务.
- (void)serialQueue
{
// 串行队列分两种
// 1. 主线程中的串行队列(主串队列)
// dispatch_queue_t GCD 中表示一个队列
// 创建一个主队列 (实际上就是把主线程取出来了)
// dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 添加任务1
/**
* 参数1: 添加任务的队列
参数2: 要执行的任务
*/
// dispatch_async(mainQueue, ^{
// // NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
// });
// // 添加任务2
// dispatch_async(mainQueue, ^{
// // NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
// });
// // 添加任务3
// dispatch_async(mainQueue, ^{
// // NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
// });
// 2. 自定义的串行队列
// 创建一个自定义的线程队列
/**
* 参数1: 队列的标识符 并且是char * 类型的 一个名字随便起 千万不能加 @" "
参数2: 队列的执行类型(串 / 并 ) DISPATCH_QUEUE_SERIAL 串行的
*/
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.MyQueue", DISPATCH_QUEUE_SERIAL);
// 添加任务1
/**
* 参数1: 添加任务的队列
参数2: 要执行的任务
*/
dispatch_async(myQueue, ^{
NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
});
// 添加任务2
dispatch_async(myQueue, ^{
NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
});
// 添加任务3
dispatch_async(myQueue, ^{
NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
});
// 延迟执行1 回到主线程
// delayInSeconds 延迟几秒 单位是秒
// code to be executed after a specified delay
// 延迟几秒后 回到主线程去执行任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟5秒执行");
NSLog(@"延迟,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
});
// 延迟执行 回到自定义线程
// ull C语言中是字面量 表示 unsigned long long
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 6ull * NSEC_PER_SEC), myQueue, ^{
NSLog(@"延迟 6秒执行");
});
// 延迟执行一个任务
// self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>
}
2). Concurrent: 并行的,可以并发执行多个任务,但是遵守FIFO.
- (void)concurrentQueue
{
// 创建一个队列
/**
* 参数1: 队列的执行顺序 DISPATCH_QUEUE_PRIORITY_DEFAULT 默认的执行顺序
参数2: <#unsigned long flags#> 预留参数 一般为 0
*/
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
// // 添加任务1
// dispatch_async(queue, ^{
// for (int i = 0; i < 10; i++) {
// NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
// }
// });
//
// // 添加任务2
// dispatch_async(queue, ^{
// for (int i = 10; i < 20; i++) {
// NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
// }
// });
// // 添加任务3
// dispatch_async(queue, ^{
// for (int i = 20; i < 30; i++) {
// NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
// }
// });
// 自定义并行队列
/**
* 参数1: 队列的标示符
参数2: 串 / 并 执行顺序 DISPATCH_QUEUE_CONCURRENT 并发执行
*/
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.www", DISPATCH_QUEUE_CONCURRENT);
// 添加任务1
dispatch_async(myQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
}
});
// 添加任务2
dispatch_async(myQueue, ^{
for (int i = 10; i < 20; i++) {
NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
}
});
// 添加任务3
dispatch_async(myQueue, ^{
for (int i = 20; i < 30; i++) {
NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
}
});
}
六.线程之间的通信
分为两种:
1). 主线程进入子线程
2). 子线程回到主线程
GCD: dispatch_get_main_queue();
NSObject: - (void
) performSelector:(SEL)aSelector withObject:(id)arg waitUntilDown:(BOOL)wait;
// 模拟游戏开始的线程操作
需求: 一共四个玩家 一起进入游戏 当所有人都进入游戏的时候 开始读取数据
思路:
1. 选取什么队列 选用并行
2. 如何实现特殊要求 把并行队列的用屏障隔开 (屏障的作用: 把队列分成上下部分 分开执行)
- (void)gameStart
{
// 创建队列
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.www", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myQueue, ^{
NSLog(@"第一名玩家正在进入... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第二名玩家正在进入... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第三名玩家正在进入... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
// 增加屏障
dispatch_barrier_async(myQueue, ^{
NSLog(@"都进入了,可以开始 读取玩家的信息了");
});
dispatch_async(myQueue, ^{
NSLog(@"第一名玩家正在读取信息... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第二名玩家正在读取信息... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
dispatch_async(myQueue, ^{
NSLog(@"第三名玩家正在读取信息... ... 所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
});
// 主线程 进入游戏 需要刷新界面
// 再加一个屏障 进入主线程 刷新界面
dispatch_barrier_async(myQueue, ^{
// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程 ,可以刷新页面,开始游戏.....敌军还有30秒到达战场.所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread
]);
});
});
}
七. 线程互斥
指的是某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性
互斥无法限制访问者的访问顺序 即 访问时无顺序的,因为需要加上互斥锁来进行顺序访问.
NSLock类
NSLock *lock = [[NSLock alloc] init];
锁:
[lock lock];
解锁: [lock unLock];
// 买火车票的线程锁实现
- (void)tickets
{
// 给个票数的初值
self.totalTickets = 100;
// 剩余票数
self.subTickets = 100;
// 初始化线程锁
self.lock = [[NSLock alloc] init];
// 先创建出两个 并行队列
/**
* 一个队列是火车站
一个队列是12306
*/
dispatch_queue_t queue1 = dispatch_queue_create("trainStation", DISPATCH_QUEUE_CONCURRENT);
// 给火车站添加一个卖票的
dispatch_async(queue1, ^{
// sale tickets
[self saleTickets:queue1];
});
// 添加一个12306队列
dispatch_queue_t queue2 = dispatch_queue_create("com.12306.www", DISPATCH_QUEUE_CONCURRENT);
// 给队列添加一个卖票的任务
dispatch_async(queue2, ^{
// sale tickets
[self saleTickets:queue2];
});
// 添加一个售票机队列
dispatch_queue_t queue3 = dispatch_queue_create("售票机", DISPATCH_QUEUE_CONCURRENT);
// 给队列添加一个卖票的任务
dispatch_async(queue3, ^{
// sale tickets
[self saleTickets:queue3];
});
}
// 卖票的方法
- (void)saleTickets:(dispatch_queue_t)queue
{
// 卖票循环
while (self.subTickets > 0) {
// 添加互斥锁
[self.lock lock];
// const 一个变量不允许被改变 静态变量
const char *label = dispatch_queue_get_label(queue);
NSString *str = [NSString stringWithUTF8String:label];
NSLog(@"是%@ 卖出的,剩余 %ld 张.", str, self.subTickets--);
// 接触互斥锁 在自动释放池使用的方法差不多 中间添加的部分是锁的内容
[self.lock unlock];
}
}