IOS线程的一些总结
主线程的作用 (在主线程中才能设置)
显示/刷新UI界面
处理UI事件(比如点击事件、滚动事件、拖拽事件);
主线程的使用注意
别将比较耗时的操作放到主线程中。
耗时操作会卡住主线程。影响体验。
[NSThread currentThread]获得当前线程。
打印线程。num属性显示有多少条线程。
将耗时操作放在子线程中(后台线程,非主线程);
多线程好处在哪?
在用户点击按钮那一刻就有反应
能同时处理耗时操作和用UI控件的事件。
多线程技术:
1,pthread 一套通用的多线程API(几乎不用)
适用于Unix\Linux\Windows等系统
跨平台\可移植
使用难度大。
需要程序员来管理线程的生命周期。
创建一个线程用
pthread_create(&pthread_t,NULL,(void *)(*)(void *),NULL);即可
2,NSThread 使用OC,(程序员管理)
使用更加面向对象
简单易用,可直接操作线程对象
创建一个线程用 [NSThread alloc] init……
[self start] 开启;
selector(mainThread) 返回主线程;
selector(isMainThread) 是否是主线程;
selector (currentThread); 判断当前线程;
可以设置线程优先级,
优先级越高,CUP的占有率越高。
线程有name属性,可以设置名字。
创建线程第2种方式:
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
分离一条线程,没有返回值。
创建线程第3种方式。
[self performSelectorInBackground:@selector(run:) withObject:@"abc参数"];
每一个时刻只有一个线程在执行。
[thread start]后进入一个可调度线程池(有机会被调度。状态为就绪或运行);
start
new ----》 就绪 -----》运行
《------
调用了sleep方法或等待同步锁 后就进入 阻塞状态(清出线程池) 条件满足后又回到调度池。
调用stop 线程进入的是dead 死亡状态。再也没有了。
+(void)sleepUntilDate:(NSDate *)date;
+(void) sleepForTimeInterval:(NSTimeInterval)ti;
线程运行完之后就会死亡, 不能能再次调用。
[thread exit]线程退出。
3,GCD C
旨在替代NSThread等线程技术
充分利用设备的多核。(自动)
不用管理线程。
GCD有2个核心概念
任务:执行什么样的操作。
队列:用来存放任务。
GCD的使用就2个步骤。
定制任务
确认想做的事情。
将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行。
任务取出遵循队列的FIFO原则,先进先出。后进后出
调用libdispach库才行
都是懿dispatch开头。
1,用同步的方式来执行任务。(有顺序执行)
dispatch_sync(queue,block);同步执行
queue:队列
block:任务
2,用异步的方式执行任务(在另一条线程中执行)
dispatch_async(queue,block);
GCD队列分2大类;
并发队列;
可以让任务同时执行。
串行队列
用函数给队列赋值任务;
队列分为并行和串行。
函数分为同步和异步。
异步函数加并行队列 == 开一条子线程。
同步:在当前线程中执行任务,不具备开启新线程能力
异步:在新的线程中执行任务,具备开启新线程的能力。
并发和串行决定了任务的执行方式。
并发:多个任务并发执行。 调用dispatch_queue_t queue = dispatch_get_global_queue(优先级,随意值);
添加任务到队列中,执行任务。
串行:一个任务执行完毕后,再执行下一个任务。
serial 英文,连续,串行的意思。。
dispatch_queue_t queue = dispatch_queue_create("com.itheima,queue",队列的类型);
队列名称在调试的时候可以看的到代码在哪个队列里面执行。
调用async函数会开启一个线程,但是如果函数的队列是串行的就不会开更多了。每加一个并行队列开启线程数会加1.
调用sync函数不会开线程。并行和串行队列都一样
MRC里面释放用dispatch_release(queue);
ARC里面不能用,会自动释放。
即使在ARC中
凡是函数名中带有create\copy\new\retain等字眼返回值的,都需要在不使用这个数据的时候进行release(有的话都写一下,报错就不写了)
GCD数据类型则不用,release。
CF(Core foundation)的数据类型在ARC环境下还是得需要再做release;
系统自带的用get ,自己创的用create 。(苹果规范);
主线程队列是不会开新线程的。
别在主线程中用同步函数添加主队列,会形成相互等待,死锁。
用异步函数可以(异步可以延缓执行,同步要马上执行)。
同步会在当前进程中执行,会停下来。异步会自己开一个线程。与当前线程并发执行,(只要队列不是主队列)
iOS常见的延时执行有2种方式;
1,[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
2秒之后调用run方法;
2,GCD方式
dispatch_after();//有个队列参数,表示多少秒后在哪个线程中执行。
//1.声明在哪个队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 2.计算任务执行的时间
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
// 3.会在when这个时间点, 执行queue中的任务
dispatch_after(when, queue, ^{ //会另外声明一个队列。
NSLog(@"----run----%@", [NSThread currentThread]);
});
需要整个程序执行过程中只能打印一次时。
static dispatch_once_t onceToken;
可以用 dispatch_once(&onceToken,^{
NSLog(@"--------touchesBegin");
});
队列组,:
有这么一种需求。
等2个线程都执行完之后再到主线程中执行操作。
就可以用队列组把2个线程封装。执行队列组。
创建group//比串行执行要快很多。。。
dispatch_group_t group = dispatch_group_create();
创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
把任务和队列放到异步组函数中
*/
dispatch_group_async(group, queue, ^{
for (int i=0; i<100; i++) {
NSLog(@"执行线程一:%d",i);
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<100; i++) {
NSLog(@"执行线程二:%d",i);
}
});
//线程组执行完成后执行:(重点,没有它,要组也没啥用了)
dispatch_group_notify(group, queue, ^{
NSLog(@"线程组执行完成");
});
//下面是重复执行:10代表重复执行次数。中间是执行队列。index是第几次执行;(注意执行是并发(这是与for循环的区别));
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
//index表示是第几次执行
NSLog(@"执行第%zu次",index);
});
栅栏barrier ,用来将线程分开,执行完上面的线程到执行它再到执行后面的线程;(有队列queue关键字,所以就是把queue队列的隔开,其它队列可不行,中间执行)
dispatch_barrier_async(queue, ^{
NSLog(@"barrier");
});
4,NSOperation OC
基于GCD
比GCD多了一些简单实用的功能
使用更加面向对带的用get ,自己创的用create 。(苹果规范);
, 配合使用NSOperation NSOperationQueue也能实现多线程编程
,NSOperation和NSOperationQueue 实现多线程的具体步骤
先将需要执行的操作封装到一个NSOperation 对象中。
然后将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来
将取出的NSOperation封装的操作放到一条新线程中执行。
NSOperation是一个抽象类,并不具备封装操作的能力,必须使用它的子类。
使用NSOperation子类的方式有3种。
NSInvocationOperation
NSBlockOperation
自定义的子类继承NSOperation,实现内部相应的方法。
NSInvocationOperation
创建操作对象,封装要执行的任务。
NSInvocationOperation * operation = [ alloc] initwithTarget…… 任务是方法;
可以[operation start]调用,但是这样是会在当前线程中调用,并无啥用,还是得放到方法中去;
NSBlockOperation
使用类方法来声明
NSBlockOperation * operation = [NSBlockOperation blockOperationWithBlock^{ }]; 任务是block ,NSBlockOperation 是block的封装
也可以 直接用start 调用,但也无啥用。
有方法: [operation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
只要operation封装的任务大于1,就会异步执行。
调用了addExecutionBlock 的话 operation的任务就有多个。 用start是会多线程执行的。
-(void) invocationOperation
{
}
NSOperatinQueue * queue =[ [NSOperationQueue alloc] init];
[queue addOperation:operation1];
[queue addOperation:operation2];//这样直接就会是异步执行。 (并发有着不确定性。但是概率是会有的)
队列可以设置 最大并发数, queue.maxConcurrentOperationCount = 1; 表示在这个队列中最多同时执行的并发任务数为1;
(不要设置太多,不然可能会卡, 2-3就好,5以内,不然UI会卡);
- (void) cancelAllOperations; 队列的全部线程都停掉
也可以调用NSOperation 的-(void)cancel 方法取消单个操作。
-(void)setSuspended:(BOOL)b;//YES 代表暂停队列。
//(一个比较好的地方是 : cancel代表取消的意思,suspended,表示已经挂起,这些英文记住了用来命名挺好的,看看别人的过去时都是加ed的,就是这么强,所以语法不好的话,多关注ios的命名规则时很好的);
当下载时,同时开启多条子线程,当用户操作UI时会发生卡顿现象,那么我们就可以在用户滚动时挂起队列。不操作时就继续队列/
下载时同时开启多条线程下载会比较快。
有个有趣的地方是 NSOperation 居然也有一个队列优先级, 我了个去, 不愧是GCD的封装啊 ,就是那么强大。
还可以设置依赖。来保证执行的顺序;
[operationB addDependency: operationA]; B依赖A 那么B只能在A之后执行。(灵活应用可以做很多事情。)
A-》B B -》C C—》A
小心相互依赖问题。
可以在不同的queue的NSOperation之间创建依赖关系。但是不能相互依赖。
操作的监听。
operation.completionBlock = ^{
//下载完图片后想做的事情。
};
自定义Operation(继承自Operation)
{
要在operation里面实现 -(void)main{}方法
}
多线程安全隐患:1,一块资源可能会被多个线程共享,也就是多个线程可能会访问同一个资源
比如说多个线程访问同一个对象、同一个变量、同一个文件。
使用线程锁来解决,同一时间只能有一个线程访问。