多线程

iOS基础

最近工作,相对来说没那么忙了。自认为网络和多线程是我比较弱的模块,所以
趁这段时间有空,找了一些网络多线程相关的视频看了一下,这里做一下总结。

这是第一篇,主要介绍了线程的基本概念、队列 的相关知识

线程的基本概念

1、创建线程的三种方法
  • 1、alloc init
1
2
3
4
5
6
NSThread *thread = [[NSThread alloc]
initWithTarget:self selector:@selector(demo:)
object:@"Thread"]

[thread start];

  • 2、detachNewThreadSelector
1
2
[NSThread detachNewThreadSelector:@selector(demo:) 		toTarget:self withObject:@"Detach"];

  • 3、performSelectorInBackground
1
2
[self performSelectorInBackground:@selector(demo:) withObject:@"background"];

2、线程的常用属性
  • 1、name
1
2
线程的名字,在多个线程开发时,可以判断到底是谁在执行任务

  • 2、threadPriority
1
2
3
4
线程优先级(0~1递增)默认优先级是0.5

注意:优先级高并不意味着会先被调用,只是表示CUP调用的频 率相对较高

  • 3、state
1
2
3
4
isExecuting(只读属性,是否正在执行)
isFinished(只读属性,线程是否完成)
isCancelled(只读属性,线程是否被取消)

  • 4、stackSize
1
2
堆栈的大小,线程执行前,堆栈大小为512k,线程执行完毕之后,堆栈大小为0k 内存空间被释放,注意,线程执行完毕之后,由于内存空间已经被释放,不能再次启动

3、线程常用的方法
  • 1、currentThread
1
2
当前线程(1表示主线程,非1表示其他线程)

  • 2、阻塞方法
1
2
3
4
sleepUntilDate:休眠到指定时间

sleepForTimeInterval:休眠指定时长

  • 3、exit
1
2
终止当前线程的执行(注意不要再主线程中执行这个方法)

  • 4、其他方法
1
2
3
4
+isMainThread 是否为主线程
+mainThread 返回主线程对象
+isMultiThreaded 是否是多线程

4、线程状态详解
1
2
3
4
5
6
7
8
9
10
11
12
13
新建:实例化线程对象

就绪:向对象发送start消息,将线程添加到“可调度线程池”等待CUP的调度,其他两种线程的创建方法,都是直接实例化一个线程对象,并将其加入“可调度线程池中”

运行:CUP负责调度“可调度线程池”中的内容,在线程执行完成前可能会进行多次的运行和就绪的转换,这个转换由CPU负责,程序员不干预

阻塞:满足某一个预定条件时,可以使用休眠或者锁阻塞线程,具体可以使用下面的几个方法:sleepForTimeInterval、sleepUntilDate、@synchronized(self)

死亡:死亡方式:1、正常死亡 2、非正常死亡
1、正常死亡 :线程执行完毕
2、线程内死亡 : [NSThread exit]
线程外死亡 :[threadObj cancel]

5、线程相关注意点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1、不要使用exit方法杀死主线程

2、一旦强行终止线程后续的所有代码都不会被执行,因此,在线程终止之前,
应该注意释放之前分配的对象,如果是ARC开发,需要注意,清理C语言框架创建的对象,否则会出现内存泄露

3、多线程的目的:将耗时操作放在后台,不阻塞主线程和用户之间的交互,线
程的优先级不要修改,多线程开发要尽量简单,多线程的优点是通过并发提高程
序的执行效率

4、在大型的商业项目中,通常会给线程起名字,因为他们希望如果程序崩溃可
以准确的定位到,是哪一个线程出现了问题

5、多线程的优缺点:
优点:能适当提高程序的执行效率,能适当的提高资源利用率(CPU、内存利用
率);
缺点:开启线程要占用一定的内存空间,默认情况下一条县城占用512KB,因此
如果开启大量的线程,会占用大量的内存空间,降低程序性能

7、线程安全:在多线程进行读写操作时,仍然能保证数据正确

8、线程间通讯:performSelectorOnMainThread "线程间通讯"
1. 在主线程执行的方法
2. 传递给方法的参数
3. 是否等待被调用方法执行完成,有可能也会等待调用方法的执行完成!几率
极少!

6、锁
  • 1、互斥锁
1
2
3
4
同时时间内,只有一个线程能够执行锁定的代码

在锁定的时候,其他线程会睡眠,等待条件满足后,再被唤醒

  • 2、自旋锁
1
2
3
4
同时时间内,只有一个线程能够执行锁定的代码

在锁定的时候,其他线程会做死循环,一直等待条件满足,性能会好(适合执行非常快,非常短的代码)

7、资源抢夺
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
现象:多个线程访问同一块资源 进行读写操作 
解决方法: 加锁

1、加互斥锁

@synchronized(self):

- 使用互斥锁,在同一个时间,只允许一条线程执行锁中的代码
- 互斥锁的代价非常昂贵
- 互斥锁的代码范围应该尽可能的小,只要锁住资源读写部分的代码即可
- 使用互斥锁会影响并发性能

2、使用原子属性

能够实现“单写多读”的数据保护

- 同一时间只允许一个线程修改属性值,但允许多个线程读取
- 有可能出现脏数据--读取数据的值可能会不正确
- 原子属性是默认属性,如果不需要考虑线程安全,指定nonatomic

2、队列

1、队列的类型
  • 1、串行队列
1
2
3
dispatch_queue_create("leewong", NULL);
一次只调度一个任务执行,一个任务执行完成后,在调度下一个任务

  • 2、并发队列
1
2
3
dispatch_queue_create("LeeWong",DISPATCH_QUEUE_CONCURRENT);
可以同时调度多个任务

  • 3、主队列
1
2
负责在主线程上调度任务,用于线程间通讯(所有的UI更新都应该在主线程上进行)

  • 4、全局队列
1
2
dispatch_get_global_queue(0, 0);与并发队列相比:调度任务的方式相同,全局队列没有队列名称,在MRC开发中,全局队列不需要释放

2、队列的执行方式
  • 1、同步 dispatch_sync
1
2
3
4
5
串行队列:会在当前线程上同步执行
并发队列:会在当前线程上同步执行
主 队列 :在主线程上添加同步任务会造成死锁,利用同步任务,可以建立任务
之间的“依赖”关系

  • 2、异步 dispatch_async
1
2
3
4
串行队列:会开启新的线程调度任务
并发队列:会开启新的线程调度任务
主队列:等待主线程空闲时调度任务

3、队列的选择
1
2
3
串行队列:对执行效率要求不高,对执行顺序要求高,性能消耗小;
并发队列:对执行效率要求高,对执行顺序要求不高,性能消耗大

4、GCD
1、延时
1
2
3
4
5
6
dispatch_after(when, queue, block);

参数:从现在起经过多少纳秒;调度任务的队列;异步执行任务

注意:dispatch_after中的参数是保留dispatch_after这句话执行的时候的参数,如果之后再代码块中语句执行之前,之中包含的参数发生变化,在之后代码块执行的时候,其值不改变

2、调度组

使用场景:需要在多个耗时操作执行完毕之后,在统一做后续处理

创建调度组:
dispatch_group_t group = dispatch_group_create();

  • 1、dispatch_group_leave、dispatch_group_enter
    icon
  • 2、dispatch_group_notify

icon

3、一次性
1
2
3
保证某段代码在程序运行过程中只被执行一次,在单例设计模式中被广泛使用
dispatch_once是线程安全的

4、GCD的排列组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- 1、串行队列,同步执行:不会开线程,顺序执行

- 2、串行队列,异步执行:会开线程(1条),顺序执行

- 3、并发队列,异步执行:会开线程,不会顺序执行,具体开几条线程取决于
队列

- 4、并发队列,同步执行:和串行队列同步执行效果一样

- 5、主队列,异步执行:不开线程,异步任务必须等待主线程上的任务完成之后
才会被调用

- 6、主队列、同步执行:会发生死锁,因为,同步任务要求必须顺序执行,但
是同步任务必须等待主队列中没有任务可以被调用的时候才会被执行,因此
这两方会造成死锁的情况

- 7、同步任务的特点:可以再多个异步任务调度前,指定一个同步任务,让所
有的异步任务,等待同步任务执行完成,这就是所谓的依赖关系

- 8、全局队列,系统提供给程序员,方便程序员使用的全局队列,有关服务质
量的问题,使用下面的代码能够做到IOS7&IOS8的适配,全局队列本质上
就是一个异步队列


5、全局队列与串行队列的选择
1
2
3
4
5
6
7
8
9
全局队列:并发,能够调度多个线程,执行效率高    但是费电

串行队列:一个接一个,只能够开启一条县城,执行效率地
但是如果任务之间有依赖关系,可以使用串行队列
省电,省钱,省流量
判断的依据:用户的上网方式
-WIFI 可以开多条线程 6条
-3G/4G 尽量少开线程2~3条

5、NSOperation
1、NSInvocationOperation和NSBlockOperation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NSInvocationOperation:

NSOperationQueue *q = [[NSOperationQueue alloc] init];

for (int i = 0; i < 10; i++) {
NSInvocationOperation *op = [[NSInvocationOperation
alloc] initWithTarget:self
selector:@selector(downloadImage:)
object:@(i)];

[q addOperation:op];
}


NSBlockOperation:

NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i<10; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@ ---- %d ", [NSThread currentThread], i);
}];
[q addOperation:op];
}







执行结果:开启多个线程,不会顺序执行->GCD并发队列,异步执行任务

2、队列的取消操作
1
2
3
4
5
6
7
8
9
10
11
判断队列是否挂起: isSuspended

在暂停的时候,队列中的操作数,是包含正在执行的操作的

再次继续的时候,如果之前执行的操作已经完成,队列中的操作数就只有没有调
度的操作

在设置度列的挂起属性的时候,并不会判断队列中是否有操作,如果不希望用户产生困惑,可以提前判断,判断队列中当前是否有操作

取消所有操作cancelAllOperations,不会取消正在执行的操作

3、操作的依赖关系
1
2
3
4
5
6
7
8
9
10
11
12
13
NSOperation 提供了依赖关系,NSOperation的所有操作都是异步执行的,但
是为了建立任务之间的依赖,提供了dependency的功能,GCD中,可以直接通过
同步任务来实现,可以通过串行队列

[op2 addDependency:op1]; op1执行完毕之后才可以执行op2

添加依赖关系之后可以调用 waitUntilFinished 来决定是否等待所有操作完
成,但是这种等待是阻塞式的,类似于

dispatch_group_wait(g, DISPATCH_TIME_FOREVER);

依赖关系可以跨队列指定

但是要注意不要指定循环依赖,一旦指定了循环依赖,队列就不工作了

4、自定义Operation的实现

目的:

1
2
3
4
5
6
7
1、将操作部分的代码重构到一个单独的子类完成工作

2、主要是在开发第三方框架时,会使用,日常开发直接使用operation会比较
方便

3、自定义操作,可以支持队列的取消操作

自定义NSOperation的子类的步骤:

1
2
3
4

-重写main方法
-添加自动释放池,因为子线程运行循环默认不工作

重要提示:

1
2
3
4
5
6
7
NSOperation默认提供了一个completionBlock的参数,一旦设置了,就会在操作执行完毕之后自动执行

completionBlock 会在后台线程执行,不会再主线程执行,所以如果需要更新UI,需要注意线程

completionBlock 不能接受参数,没有返回值,因此在使用的时候,会有很多限制


注意:

1
2
3
4
5
6
7
8
9
10
11
12
作为所有c语言程序的入口,自定义操作也需要有自己的main函数,同时子线程
的运行循环默认是不开启的,因此也不会主动的区创建自动释放池,因此自定义
NSOperation的子类的时候,需要自己添加自动释放池

- (void)main {
@autoreleasepool {}
}

每一次运行循环的开启,都会创建一个自动释放池,当对象出了他们的作用域之
后就会被添加到自动释放池中,在运行循环即将结束的时候,会被销毁(每一个
线程都有自己的runloop但是之后主线程的默认被开启)

5、NSOperation和GCD的区别
  • 1、GCD
1
2
3
4
5
6
7
8
9
10
11
12
13
- GCD在iOS4.0推出,主要针对多核处理器做了优化的并发计数,是C语言的

- 将任务[block]添加到度列[串行/并发/主队列/全局队列],并且制定执行任
务的函数[同步/异步]

- 线程间通讯dispatch_get_main_queue

- 提供了一些NSOperation不具备的功能
- 一次性执行
- 延迟执行
- 调度组(在op中也可以做到,就是有点麻烦)


  • 2、NSOperation
1
2
3
4
5
6
7
8
9
10
11

- NSOperation在iOS2.0之后推出,苹果推出GCD之后,对NSOperation底
层重写了一遍
- 将操作[异步执行的任务]添加到队列[并发队列],就会立即异步执行
- mainQueue
- 提供了一些GCD实现起来比较困难的功能
- 最大并发操作数
- 队列的暂停/继续
- 取消所有的操作
- 制定操作之间的依赖关系(GCD用同步实现)