线程的定义:
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程。
通常线程用于处理哪些耗时的操作,来缓解主线程的压力。
线程和进程是有很大区别的:
1、同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
2、同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。
线程的优点:
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
缺点:
- 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
多线程的原理
cpu具有调度的能力,可以调度该进程内的线程,在很小的时间片内切换线程;
但是只有多核cpu才能来回切换处理线程,单核的话一次只能处理一个线程。
时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片
多线程同时执行:
- 是 CPU 快速的在多个线程之间的切换
- CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
如果线程数非常多 - CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源
- 每个线程被调度的次数会降低,线程的执行效率降低
线程的生命周期:
iShot2020-10-31 14.46.20.png线程池:
iShot2020-10-31 14.51.15.png
线程的优先级:
线程的优先级threadPriority
是一个double
类型,它被一个NSQualityOfService
所取代,字面意思是服务质量。
NSQualityOfService
有5个类型:
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21, //用户交互,优先级最高
NSQualityOfServiceUserInitiated = 0x19, //初始化,第二
NSQualityOfServiceUtility = 0x11, //使用工具,第三
NSQualityOfServiceBackground = 0x09, //后台,第四
NSQualityOfServiceDefault = -1 //默认情况,第五
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
优先级越高,并不是执行越快,它还要看资源大小(任务复杂度)和CPU的调度。
锁
1、原子锁atomic
和非原子锁nonatomic
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
iOS开发的建议:
- 所有属性都声明为 nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
互斥锁:
当发现其他线程执行,当前线程休眠(就绪状态),一直在等打开唤醒执行
互斥锁小节:
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
互斥锁参数 - 能够加锁的任意 NSObject 对象
- 注意:锁对象一定要保证所有的线程都能够访问
- 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个
自旋锁:
当发现其他线程执行,当前线程询问(一直询问),一直等待,耗费性能比较高。
所以说,当需求所需时间短,可以使用自旋锁,但是比较耗时的,一般使用互斥锁。
线程通讯
线程通讯通常使用的方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
// equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
这些方法功能基本上没啥好说的,从字面意思上就可以理解,下面通过部分代码来演示一下:
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] init];
// 1. 准备 URL
NSString *urlString = @"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1529488045873&di=7c1cc40bec638406ef48717386a08094&imgtype=0&src=http%3A%2F%2Fpic2.ooopic.com%2F12%2F46%2F41%2F95bOOOPIC74_1024.jpg";
NSURL *url = [NSURL URLWithString:urlString];
// 2. 下载图像
//[self downloadImageWithURL:url];
// 异步下载图像
[self performSelectorInBackground:@selector(downloadImageWithURL:) withObject:url];
}
/**
* 下载 URL 指定的网络图片
*/
- (void)downloadImageWithURL:(NSURL *)url {
NSLog(@"%@", [NSThread currentThread]);
// 1. 所有从网络返回的都是二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
// 2. 将二进制数据转换成 image
UIImage *image = [UIImage imageWithData:data];
// 3. 在主线程更新 UI
// waitUntilDone: 是否等待 updateImage: 执行完成
[self performSelectorOnMainThread:@selector(updateImage:) withObject:image waitUntilDone:YES];
NSLog(@"完成");
}
/**
* 更新图像 UI
*/
- (void)updateImage:(UIImage *)image {
NSLog(@"更新 UI -> %@", [NSThread currentThread]);
// 3. 设置图像
_imageView.image = image;
// 4. 调整大小
[_imageView sizeToFit];
// 5. 设置 contentSize
_scrollView.contentSize = image.size;
}
还有一个点就是关于NSPort
,这个在通常开发中基本不同,只说一个值得注意的点,当需要NSPort
执行事件时,需要将NSPort
加入到Runloop
;
线程与Runloop的关系
1:runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心 的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局 的字典里。
2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休 眠状态,有了任务就会被唤醒去执行任务。
3:runloop在第一次获取时被创建,在线程结束时被销毁。
4:对于主线程来说,runloop在程序一启动就默认创建好了。
5:对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线 程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。
关于iOS线程的一些基本的使用和概念就结束了;
GCD的使用和一些底层将在下一篇讲述。
网友评论