相关知识点引入
多线程优秀博客:(关于iOS多线程,你看我就够了)https://www.jianshu.com/p/0b0d9b1f1f19
1.进程和线程
进程:指系统上正在运行的程序;例如:我们打开QQ是一个进程,打开Xcode也是一个进程
线程:指程序在执行过程中,能够执行程序代码的一个执行单元(也就是线程是运行在程序中)
二者关系:一个进程可以有多个线程;线程是进程的执行单元;
各个线程之间共享程序的内存空间,但是各个线程又拥有自己独立的内存空间。1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程;一个进程(程序)的所有任务都在线程中执行;注意:1个线程中任务的执行是串行的)
2.串行和并行
串行:一个线程执行多个任务,采取排队方式执行;
并行:多个线程分担多个任务,不同任务同时执行(食堂窗口打饭)
3.同步和异步(主要考虑阻塞问题)
同步:当前面一个任务未完成,后面的任务就会被阻塞;
异步:不同的任务在不同的线程中执行,当子线程发生阻塞,主线程和其他线程不会受到影响(食堂窗口打饭)
注意:异步强调解决阻塞问题,可以开辟新的线程执行任务;并发指子线程之间的关系,多个任务同时执行
4.多线程:
多线程概念:1个进程中可以开启多条线程,多条线程可以并行(同时)执行不同的任务
多线程原理:
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
为什么使用多线程:
可以减少程序的响应时间、简化程序结构、内存开销更小、能适当提高程序的执行效率、能适当提高资源利用率(CPU、内存利用率)等等
多线程缺点
1.对其他程序不友好(例抢红包)
2.可能占用了更多的CPU资源。当然,也不是线程越多,程序的性能就越好,因为线程之间的调度和切换也会浪费CPU时间。
思考:如果线程非常非常多,会发生什么情况?
多条线程会再CPU来回切换,会销毁大量的CPU资源
每条线程被调度执行的频度会变小,线程的执行效率会降低伴随着
5.主线程
1).概念:一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
2). 作用: 显示\刷新UI界面; 处理UI事件
3).使用主线程注意:
- 别将比较耗时的操作放到主线程中(一般都在子线程(后台线程、非主线程)处理耗时操作后回到主线程中刷新UI界面);
- 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验
五种方法
1.GCD(Grand Central Dispatch)
概念:是实现多线程的一种方法;是一套底层API,GCD的API很大程度上基于block,当配合block使用时,GCD非常简单易用且能发挥其最大能力。
队列有三种:用户队列、全局队列、主队列
---使用经典(常用)形式---
1.先全局的并发队列,异步(供整个应用使用;执行耗时的异步操作 )
dispatch_async(dispatch_get_global_queue(0,0),^{
// 第一个0表示默认的全局队列的优先级,可记为固定;第二个是保留
的参数,没什么用,直接写0
代码...
});
�
2.回到主队列(刷新界面 )
dispatch_async(dispatch_get_main_queue(),^{
代码...
});
Demo
//插座变量
@property (weak, nonatomic) IBOutlet UIImageView *baiduImageView;
//开始触摸事件
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//为避免循环引用;采取弱引用方式(遇到block大多可采用此方法)
__weak typeof(self)weakSelf=self;
//1.先创建一个全局并发队列
dispatch_queue_t queue=dispatch_get_global_queue(0, 0);
//2.调用异步函数
dispatch_async(queue, ^{
NSString *strUrl=[NSString stringWithFormat:@"https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/bd_logo1_31bdc765.png"];
NSURL *url=[NSURL URLWithString:strUrl];
NSData *data=[NSData dataWithContentsOfURL:url];
UIImage *image=[UIImage imageWithData:data];
//3.主队列刷新界面
dispatch_sync(dispatch_get_main_queue(), ^{
weakSelf.baiduImageView.image=image;
});
});
}
2.NSOperation方法
创建一个操作对象,然后将其扔到队列中去,再最后回到主线程刷新
创建一个操作对象(待会要将该队列放到一个队列中去执行)
NSOperation *op=[NSBlockOperation blockOperationWithBlock:^{
[self foo:sender];
}];
创建一个并发对列
NSOperationQueue *queue=[NSOperationQueue alloc]init];
//设置最大并发数量
queue.maxConcureentOperationCount=5;
//向对列中添加一个操作
[queue addOperation:op];
1.创建一个操作对象
NSOperation *op=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(bar:)object:button];
2.向主队列中添加操作对象(操作放到主线程中执行)
[[NSOperationQueue mainQueue]addOperation:op];
3.pThreads - POSIX
是C语言写的函数,可以实现多线程;但使用非常麻烦,要调非常多的函数...
4.NSThread方法
5.NSObject扩展(补丁包)
选择器方法
//开辟新线程执行耗时操作(相当于放在后台新线程中执行)以免阻塞为主线程
performSelectorInBackground:withObject:
//主线程刷新
performSelectorOnMainThread: withObject: waitUntilDone:
NSThread(有2种方式执行)
一.实现方法
//方法1:
[NSThread detachNewThreadSelector:@selector(foo:) toTarget:self withObject:sender];
//方法2:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(foo:) object:sender];
// 启动线程(执行foo回调方法)
[thread start];
// sleep(1);
// 提示: 如果线程已经开始执行则无法取消
// [thread cancel];
具体看课程代码
二.考虑开辟多条线程竞争同一资源(或多个资源,这考虑同一的情况)
//可在AppDelegate.m中写
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 创建一个可变字符串作为多个线程共同竞争的一个资源
NSMutableString *mStr = [NSMutableString stringWithCapacity:50000];
// 创建5个线程操作同一个资源
for (int i = 1; i <= 5; i++) {
[NSThread detachNewThreadSelector:@selector(foo:) toTarget:self withObject:mStr];
}
return YES;
}
- (void) foo:(NSMutableString *) mStr {
for (int i = 0 ; i < 10000; i++) {
// 提示: 如果多个线程竞争同一个资源通常需要进行同步保护
// 同步: 只让一个线程获得资源使用完毕后下一个线程才能获得(排队方式)
// 该资源其他线程仍然等待使用资源的线程释放资源
@synchronized (mStr) {
[mStr appendString:@"a"];
}
}
}
NSOperation
使用NSOperation的子类NSInvocationOperation和NSBlockOperation
//两个按钮那个例子(讲NSBlockOperation)
- (IBAction)onbuttonClicked:(UIButton *)sender {
sender.enabled=NO;
[sender setTitle:@"执行中" forState:0];
//1创建任务(以block形式)
NSOperation *op=[NSBlockOperation blockOperationWithBlock:^{
sleep(10);
NSLog(@"任务1完成");
//3.再创建一个队列,然后放在主队列中(就会在主线程中执行),来刷新界面
NSOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
[sender setTitle:@"button1" forState:0];
}];
[[NSOperationQueue mainQueue]addOperation:op2];//刷新界面
}];
//2并发队列(同时执行放在里面的任务)
NSOperationQueue *queue=[[NSOperationQueue alloc]init];
[queue addOperation:op];//相队列中添加一个任务(操作)
}
只执行一次处理
static dispatch_once_t token;
dispatch_once(&token, ^ {});
总结
代码只被执行一次:
(利用这种方式,可以简单的创建一个单例对象)
方法dispatch_once(&,^(void){});
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
NSLog(@"代码块只执行了一次");
});
拓展
1.runloop与线程有什么关系?
总的说来,Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分, Cocoa 和 CoreFundation 都提供了 run loop 对象方便配置和管理线程的 run loop (以下都以 Cocoa 为例)。每个线程,包括程序的主线程( main thread )都有与之相应的 run loop 对象。
runloop 和线程的关系:
1.主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
重点是UIApplicationMain()函数,这个方法会为main thread设置一个NSRunLoop对象,这就解释了:为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
2.对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
3.在任何一个 Cocoa 程序的线程中,都可以通过以下代码来获取到当前线程的 run loop 。
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
2.runloop的mode作用是什么?
model 主要是用来指定事件在运行循环中的优先级的,分为:
1.NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
2.UITrackingRunLoopMode:ScrollView滑动时
3.UIInitializationRunLoopMode:启动时
4.NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
苹果公开提供的 Mode 有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
有关Runloop的优秀博客:http://www.jianshu.com/p/cb2a1f25b646