美文网首页
iOS多线程(一)

iOS多线程(一)

作者: 漆黑烈焰武士G | 来源:发表于2019-08-21 23:05 被阅读0次

多线程(一)

一、多线程的作用

1、线程的定义

· 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行

· 进程若想要执行任务,必须得有线程,进程至少要有一条线程

· 程序启动会默认开启一条线程,这条线程被称为主线程或UI线程

2、进程的定义

· 进程是指在系统中正在运行的一个应用程序

· 每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存

(安卓可支持多进程,iOS只能支持单进程)

3、进程与线程的关系 (都是书上的原话)

· 地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

(疑问:啥是地址空间)

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

· 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃会导致整个进程都死掉。所以多进程要比多线程健壮。

· 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。

· 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

· 线程是处理器调度的基本单位,但是进程不是。

4、多线程的意义

· 优点:

· 能适当提高程序的执行效率 (一些操作会阻塞主线程,影响用户体验,所以要放在子线程)

· 能适当提高资源的利用率(CPU,内存)

· 线程上的任务执行完成后,线程会自动销毁

· 缺点:

· 开启线程需要占用一定的内存空间(默认情况下,每一个线程都占521kb)

· 如果开启大量的线程,会占用大量的内存空间,降低程序的性能

· 线程越多,CPU在调用线程上的开销就越大

· 程序设计更加复杂,比如线程间的通信、多线程的数据共享

· 疑问:为何ui要在主线程更新?

答:UIKit的设计模式要求这么做。

二、多线程的原理

1、概览
方案 简介 语言 线程生命周期 使用频率
pthread · 一套通用的多线程API
· 适用于 Unix / Linux / Windows 等系统
· 跨平台\可移植
· 使用难度大
C 程序员管理 几乎不用
NSThread · 使用更加面向对象
· 简单易用,可直接操作线程对象
OC 程序员管理 偶尔使用
GCD · 旨在替代 NSTread 等线程技术
· 充分利用设备的多核
C 自动管理 经常使用
NSOperation · 基于 GCD (底层是GCD)
· 比 GCD 多了一些更简单实用的功能
· 实用更加面向对象
OC 自动管理 经常使用
2、开辟空间
//主线程 开辟空间
    NSLog(@"主线程 : %lu",[NSThread currentThread].stackSize / 1024);
    
    // 1:开辟线程
    NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@100];
    // 2. 启动线程
    [t start];
    t.name = @"线程NSThreadOne";//名字方便用于查看调用堆栈
    NSLog(@"子线程 : %lu",t.stackSize / 1024);
--------------------------------
  [1450:26273] 主线程 : 512
    [1450:26273] 子线程 : 512
3、退出线程
- (void)viewDidLoad 
{
    [super viewDidLoad];
    self.p = [[Person alloc] init];
    //A: 1:开辟线程
    NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@100];
    // 2. 启动线程
    [t start];
    t.name = @"线程NSThreadOne";
  
  
    // [NSThread exit]; //退出主线程  --  因为在主线程作用域  
    //程序不会崩溃,但是会卡住,引起野指针等一系列问题
    //此时主runloop会休眠
}
4、 线程的生命周期(重点)
img1.png
  • 线程只能start一次,不能重复start
  • 多线程—切换到其他时间片—调度其他线程
  • 多核是真正的并发,单核是虚拟的并发
  • 当一个任务需要执行时:


    img2.png
  • 饱和策略(中止Abort、抛弃Discard、抛弃最旧的Discard-Oldest、调用者运行Caller-Runs)

三、线程安全

1、互共享资源需要锁
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event 
{
    self.tickets = 20;

    // 1. 开启一条售票线程
    NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t1.name = @"售票 A";
    [t1 start];
    
    // 2. 再开启一条售票线程
    NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil];
    t2.name = @"售票 B";
    [t2 start];
}

- (void)saleTickets 
{
    while (YES) {
        // 0. 模拟延时
        //NSObject *obj = [[NSObject alloc] init];
        //obj 是自己的临时对象,对其他访问该区域的无影响
        //可以锁self 那么访问该方法的时候所有的都锁住,可以根据需求特定锁
        @synchronized(self){
        // 递归 非递归
            [NSThread sleepForTimeInterval:1];
            // 1. 判断是否还有票
            if (self.tickets > 0) 
            {
                // 2. 如果有票,卖一张,提示用户
                self.tickets--;
                NSLog(@"剩余票数 %zd %@", self.tickets, [NSThread currentThread]);
            }
            else 
            {
                // 3. 如果没票,退出循环
                NSLog(@"没票了,来晚了 %@", [NSThread currentThread]);
                break;
            }
            //在锁里面操作其他的变量的影响
            [self.mArray addObject:[NSDate date]];
            NSLog(@"%@ *** %@",[NSThread currentThread],self.mArray);
        }
    }
}
2、信号量控制并发
        // GCD 控制并发数
        dispatch_semaphore_t lock =  dispatch_semaphore_create(2);
    for (int i = 0; i < 10; i++)
    {
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(5);
            NSLog(@"%d-%@",i,[NSThread currentThread]);
            dispatch_semaphore_signal(lock);
            NSLog(@"*************");
        });
    }
3、自旋锁

3.1 、自旋锁和互斥锁的区别:

自旋锁:忙等 --- 代码量较小 --- 耗时较少,因为一直醒着等

互斥锁:睡觉 --- 唤醒需要时间

读写锁:多读单写

//读写锁举例:
- (NSString *)name 
{
    return _name;
}
- (void)setName:(NSString *)name 
{
    /**
     * 增加一把锁,就能够保证一条线程在同一时间写入!
     */
    @synchronized (self) {
        _name = name;
    }
}

3.2、 atomic与nonatomic 的区别:

• nonatomic非原子属性

• atomic原子属性(线程安全),针对多线程设计的,默认值

  • 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)

  • atomic 本身就有一把锁(自旋锁)

  • 单写多读:单个线程写入,多个线程可以读取

• atomic:线程安全,需要消耗大量的资源

• nonatomic:非线程安全,适合内存小的移动设备

iOS开发的建议

• 所有属性都声明为nonatomic

• 尽量避免多线程抢夺同一块资源

• 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

四、线程和Runloop的关系

1:runloop与线程是一一对应的,一个runloop对应一个核心的线程,为什么说是核心的,是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里。

2:runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。

3:runloop在第一次获取时被创建,在线程结束时被销毁。

4:对于主线程来说,runloop在程序一启动就默认创建好了。

5:对于子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调。

疑问: 常驻线程、一般线程

五、线程间通讯

1、更新ui

2、管道间port通信

相关文章

网友评论

      本文标题:iOS多线程(一)

      本文链接:https://www.haomeiwen.com/subject/vywwsctx.html