iOS多线程

线程的定义:

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,程序启动会默认开启一条线程,这条线程被称为主线程或 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的使用和一些底层将在下一篇讲述。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335