iOS-底层原理22-多线程

《iOS底层原理文章汇总》

1.进程和线程

  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行 * 进程要想执行任务,必须得有线程,进程至少要有一条线程
  • 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程
  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
  • 通过“活动监视器”可以查看 Mac 系统中所开启的进程

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。

1: 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进 程都死掉。所以多进程要比多线程健壮。
2: 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进 程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程 3: 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是 线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
4: 线程是处理器调度的基本单位,但是进程不是。 5: 线程没有地址空间,线程包含在进程地址空间中

  • 优点
  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率(CPU,内存)
  • 线程上的任务执行完成后,线程会自动销毁
  • 缺点
  • 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512 KB)
  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
  • 线程越多,CPU 在调用线程上的开销就越大
  • 程序设计更加复杂,比如线程间的通信、多线程的数据共享
/**
     pthread_create 创建线程
     参数:
     1. pthread_t:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀 `_t / Ref` 结尾
     同时不需要 `*`
     2. 线程的属性,nil(空对象 - OC 使用的) / NULL(空地址,0 C 使用的)
     3. 线程要执行的`函数地址`
     void *: 返回类型,表示指向任意对象的指针,和 OC 中的 id 类似
     (*): 函数名
     (void *): 参数类型,void *
     4. 传递给第三个参数(函数)的`参数`
     
     返回值:C 语言框架中非常常见
     int
     0          创建线程成功!成功只有一种可能
     非 0       创建线程失败的错误码,失败有多种可能!
     */
    
    // 1: pthread
    pthread_t threadId = NULL;
    //c字符串
    char *cString = "HelloCode";
//    NSString *ocString = @"Gavin";
    //延伸到: OC--C的混编 尤其在智能家居,SDK封装
    //抛出一个问题: 在ARC需要这样操作,在MRC不需要
    // OC prethread -- 跨平台
    // 锁
    int result = pthread_create(&threadId, NULL, pthreadTest, cString);
    if (result == 0) {
        NSLog(@"成功");
    } else {
        NSLog(@"失败");
    }
    // 2: NSThread
    [NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
    // 3: GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self threadTest];
    });
    
    // 4: NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
    
/**
 1. 循环的执行速度很快
 2. 栈区/常量区的内存操作也挺快
 3. 堆区的内存操作有点慢
 4. I(Input输入) / O(Output 输出) 操作的速度是最慢的!
 * 会严重的造成界面的卡顿,影响用户体验!
 * 多线程:开启一条线程,将耗时的操作放在新的线程中执行
 */
- (void)threadTest{
    NSLog(@"begin");
    NSInteger count = 1000 * 100;
    for (NSInteger i = 0; i < count; i++) {
        // 栈区
        NSInteger num = i;
        // 常量区
        NSString *name = @"zhang";
        // 堆区
        NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
        NSLog(@"%@", myName);
    }
    NSLog(@"over");
}

void *pthreadTest(void *para){
    // 接 C 语言的字符串
    //    NSLog(@"===> %@ %s", [NSThread currentThread], para);
    // __bridge 将 C 语言的类型桥接到 OC 的类型
    NSString *name = (__bridge NSString *)(para);
    
    NSLog(@"===>%@ %@", [NSThread currentThread], name);
    
    return NULL;
}

单核CPU,在内存中的多线程是快速切换,用户无法感知到切换


iOS-底层原理22-多线程_第1张图片
image.png

时间片的概念:CPU在多个任务直接进行快速的切换,这个时间间隔就是时间片

  • (单核CPU)同一时间,CPU 只能处理 1 个线程 * 换言之,同一时间只有 1 个线程在执行
  • 多线程同时执行:
  • 是 CPU 快速的在多个线程之间的切换
  • CPU 调度线程的时间足够快,就造成了多线程的“同时”执行的效果
  • 如果线程数非常多
  • CPU 会在 N 个线程之间切换,消耗大量的 CPU 资源 * 每个线程被调度的次数会降低,线程的执行效率降低
    多核CPU,才是多个线程同时执行

2.线程属性,命名name

iOS-底层原理22-多线程_第2张图片
image.png

3.线程生命周期

iOS-底层原理22-多线程_第3张图片
image.png

线程池


iOS-底层原理22-多线程_第4张图片
image.png

饱和策略


iOS-底层原理22-多线程_第5张图片
image.png

iOS-底层原理22-多线程_第6张图片
image.png

线程中的所有任务都能cancel掉吗?正在执行的任务无法cancel,正在执行的任务已经被调度上去了
iOS-底层原理22-多线程_第7张图片
image.png

4.threadPriority优先级qualityOfService,优先级越高表明执行速度越快,还与任务的复杂度有关

5.多任务在一条线程,会有资源抢夺的问题

  • 1.买票问题加一把锁:@synchronized(self),同样可以加NSLock,信号量,栅栏函数,同步


    iOS-底层原理22-多线程_第8张图片
    image.png

    互斥锁小结
    保证锁内的代码,同一时间,只有一条线程能够执行!
    互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
    互斥锁参数,能够加锁的任意 NSObject 对象
    注意:锁对象一定要保证所有的线程都能够访问
    如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再建一个锁对象

  • 2.atomic和nonatomic
    nonatomic 非原子属性
    atomic 原子属性(线程安全),针对多线程设计的,默认值
    保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值) atomic 本身就有一把锁(自旋锁) 单写多读:单个线程写入,多个线程可以读取
    atomic:线程安全,需要消耗大量的资源 nonatomic:非线程安全,适合内存小的移动设备
    iOS 开发的建议
    所有属性都声明为 nonatomic
    尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力


    iOS-底层原理22-多线程_第9张图片
    image.png
  • 3.自旋锁和互斥锁
    互斥锁:发现其他线程执行,当前线程 休眠(就绪状态) 一直在等打开 唤醒执行
    自旋锁:发现其他线程执行,当前线程 询问 - 忙等 耗费性能比较高
    短小用自旋锁
    任务复杂度比较高用互斥锁

6.线程间通讯

- (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;
}

7.端口间通信

把port加入runloop,[[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode],接收port消息的端口加入runloop里面


iOS-底层原理22-多线程_第10张图片
image.png

iOS-底层原理22-多线程_第11张图片
image.png

8.UIImageView用weak修饰

weak修饰ImageView,一创建就释放了,栈区的空间系统自动管理回收,无法进行添加,可以通过添加中间变量进行赋值,引用计数进行传递,在函数viewDidLoad中都不会被销毁


iOS-底层原理22-多线程_第12张图片
image.png

iOS-底层原理22-多线程_第13张图片
image.png

你可能感兴趣的:(iOS-底层原理22-多线程)