OC基础知识点之-多线程(一)多线程基础

线程与进程

线程

  • 1.线程是进程的基本执⾏单元,⼀个进程的所有任务都在线程中执⾏
  • 2.进程要想执⾏任务,必须得有线程,进程⾄少要有⼀条线程
  • 3.程序启动会默认开启⼀条线程,这条线程被称为主线程或 UI 线程

进程

  • 1.进程是指在系统中正在运⾏的⼀个应⽤程序
  • 2.每个进程之间是独⽴的,每个进程均运⾏在其专⽤的且受保护的内存空间内
  • 3.通过“活动监视器”可以查看 Mac 系统中所开启的进程

线程和进程的联系

  • 1.地址空间:同⼀进程的线程共享本进程的地址空间,⽽进程之间则是独⽴的地址空间。
  • 2.资源拥有:同⼀进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独⽴的。
    • 1: ⼀个进程崩溃后,在保护模式下不会对其他进程产⽣影响,但是⼀个线程崩溃整个进程都死掉。所以多进程要⽐多线程健壮。
    • 2:
      进程切换时,消耗的资源⼤,效率⾼。所以涉及到频繁的切换时,使⽤线程要好于进程。同样如果要求同时进⾏并且⼜要共享某些变量的并发操作,只能⽤线程不能⽤进程
    • 3:
      执⾏过程:每个独⽴的进程有⼀个程序运⾏的⼊⼝、顺序执⾏序列和程序⼊⼝。但是线程不能独⽴执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。
    • 4: 线程是处理器调度的基本单位,但是进程不是。
    • 5: 线程没有地址空间,线程包含在进程地址空间中。

多线程的优点和缺点

优点

  • 能适当提⾼程序的执⾏效率
  • 能适当提⾼资源的利⽤率(CPU,内存)
  • 线程上的任务执⾏完成后,线程会⾃动销毁

缺点

  • 开启线程需要占⽤⼀定的内存空间(默认情况下,每⼀个线程都占 512 KB)
  • 如果开启⼤量的线程,会占⽤⼤量的内存空间,降低程序的性能
  • 线程越多,CPU 在调⽤线程上的开销就越⼤
  • 程序设计更加复杂,⽐如线程间的通信、多线程的数据共享

多线程的实际实现

先介绍一个概念:时间片。时间片:CPU在多个任务之间进行快速切换,这个时间间隔就是时间片。

单核和多核

  • 1.单核:同一时间,CPU只能处理一个线程,也就是说同一时间只有一个线程在执行。
  • 2.多线程同时执行:CPU快速的在多个线程之间切换。CPU调度线程的时间足够快,就造成了多线程’同时’执行的效果
  • 3.线程非常多:CPU会在N个线程之间切换,消耗大量的CPU资源,每个线程的被调度次数会降低,线程的执行效率降低

多线程的内存消耗

官方文档对多线程的内存消耗有个说明官方文档传送门

在这里插入图片描述

  • 1.线程内核数据结构大约占1KB,主要用于存储线程数据结构和属性,其中大部分是作为连接内存分配的,因此不能分页到磁盘。
  • 2.栈的控件大小:子线程512kb,OS X的主线程是8MB,iOS的主线程是1MB。子线程允许的最小堆栈大小是16 KB,堆栈大小必须是4 KB的倍数。该内存的空间是在线程创建时在进程空间中留出的,但是只有在需要时才创建与该内存关联的实际页面。
  • 3.创建时间大概90微秒,这个值反映了从创建线程的初始调用到线程入口点例程开始执行的时间之间的时间。这些数字是通过分析在运行OS X v10.5、配置2 GHz双核处理器和1 GB RAM、基于intel的iMac上创建线程时生成的平均值和中值得出的。

多线程处理方案

在这里插入图片描述

线程生命周期

在这里插入图片描述

线程池的原理

在这里插入图片描述

饱和策略

  • 1.AbortPolicy 直接抛出RejectedExecutionExeception异常来阻⽌系统正常运⾏
  • 2.CallerRunsPolicy 将任务回退到调⽤者
  • 3.DisOldestPolicy 丢掉等待最久的任务
  • 4.DisCardPolicy 直接丢弃任务

这四种拒绝策略均实现了RejectedExecutionHandler接⼝

线程优先级

在这里插入图片描述

地址越高,其优先级越高,也就是说用户操作行为的优先级是最高的。但注意:

  • 1.优先级越高,执行速度不一定越快,跟资源大小(任务复杂度)和CPU的调度(多任务)
  • 2.多任务就会出现资源抢夺问题(会导致数据出错),此时需要锁来防止这种情况出现

锁作用

  • 1.保证锁内的代码,同⼀时间,只有⼀条线程能够执⾏!

  • 2.锁的锁定范围,应该尽量⼩,锁定范围越⼤,效率越差!

锁使用的注意点

  • 1.能够加锁的任意 NSObject 对象
  • 2.注意:锁对象⼀定要保证所有的线程都能够访问
  • 3.如果代码中只有⼀个地⽅需要加锁,⼤多都使⽤ self,这样可以避免单独再创建⼀个锁对象

互斥锁和自旋锁

自旋锁

自旋锁成员

自旋锁包含:atomic, OSSpinLock, dispatch_semaphore_t等

定义:

定义:是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。

自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。

使用场景

  • 1.对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。
  • 2.加锁代码(临界区)经常被调用,但竞争情况很少发生
  • 3.CPU资源不紧张
  • 4.多核处理器

互斥锁

互斥锁成员

互斥锁包含:@synchronized,NSLock, pthread_mutex, NSConditionLock, NSCondition, NSRecursiveLock等

定义

定义:互斥锁当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。

互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。

使用场景

  • 1.预计线程等待锁的时间较长
  • 2.单核处理器
  • 3.临界区有IO操作
  • 4.临界区代码复杂或者循环量大
  • 5.临界区竞争非常激烈

atomic与nonatomic 的区别

nonatomic ⾮原⼦属性

  • 1.nonatomic:⾮线程安全,适合内存⼩的移动设备

atomic 原⼦属性

  • 1.读取值线程安全
  • 2…atomic:读写安全,需要消耗⼤量的资源(至少是nonatomic的10倍)

说明:使用atomic修饰属性时,编译器在生成getter和setter方法时,在getter和setter方法内部实现进行加锁操作,这么做的目的是为了保证属性读写的安全性和完整性,也就是说对于属性值得存取是线程安全的。但这个不能保证操作这个属性的时候是线程安全的。

iOS 开发的建议

  • 1.所有属性都声明为 nonatomic
  • 2.尽量避免多线程抢夺同⼀块资源,尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减⼩移动客户端的压⼒

线程和Runloop的关系

  • 1.runloop与线程是⼀⼀对应的,⼀个runloop对应⼀个核⼼的线程,为什么说是核⼼的,是因为runloop是可以嵌套的,但是核⼼的只能有⼀个,他们的关系保存在⼀个全局的字典⾥。
  • 2.runloop是来管理线程的,当线程的runloop被开启后,线程会在执⾏完任务后进⼊休眠状态,有了任务就会被唤醒去执⾏任务。
  • 3.runloop在第⼀次获取时被创建,在线程结束时被销毁。
  • 4.对于主线程来说,runloop在程序⼀启动就默认创建好了。
  • 5.对于⼦线程来说,runloop是懒加载的,只有当我们使⽤的时候才会创建,所以在⼦线程⽤定时器要注意:确保⼦线程的runloop被创建,不然定时器不会回调。

延伸

端口通讯:NSPort

下面我们举例子来展示多线程的端口通讯,通过KCPerson对象跟ViewController进行发送消息的例子

// ViewController.m
    //1\. 创建主线程的port
    // 子线程通过此端口发送消息给主线程
    self.myPort = [NSMachPort port];
    //2\. 设置port的代理回调对象
    self.myPort.delegate = self;
    //3\. 把port加入runloop,接收port消息
    [[NSRunLoop currentRunLoop] addPort:self.myPort forMode:NSDefaultRunLoopMode];
    self.person = [[KCPerson alloc] init];
    [NSThread detachNewThreadSelector:@selector(personLaunchThreadWithPort:)
                             toTarget:self.person
                           withObject:self.myPort];

- (void)handlePortMessage:(NSPortMessage *)message {
    NSLog(@"VC == %@",[NSThread currentThread]);
    NSArray *messageArr = [message valueForKey:@"components"];
    NSString *dataStr   = [[NSString alloc] initWithData:messageArr.firstObject  encoding:NSUTF8StringEncoding];
    NSLog(@"传过来一些信息 :%@",dataStr);
    NSPort  *destinPort = [message valueForKey:@"remotePort"];
    if(!destinPort || ![destinPort isKindOfClass:[NSPort class]]){
        NSLog(@"传过来的数据有误");
        return;
    }
    NSData *data = [@"VC收到!!!" dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data,self.myPort]];
    // 非常重要,如果你想在Person的port接受信息,必须加入到当前主线程的runloop
    [[NSRunLoop currentRunLoop] addPort:destinPort forMode:NSDefaultRunLoopMode];

    NSLog(@"VC == %@",[NSThread currentThread]);

    BOOL success = [destinPort sendBeforeDate:[NSDate date]
                                        msgid:10010
                                   components:array
                                         from:self.myPort
                                     reserved:0];
    NSLog(@"%d",success);

}
// Person.m
- (void)personLaunchThreadWithPort:(NSPort *)port{

    NSLog(@"VC 响应了Person里面");
    @autoreleasepool {
        //1\. 保存主线程传入的port
        self.vcPort = port;
        //2\. 设置子线程名字
        [[NSThread currentThread] setName:@"KCPersonThread"];
        //3\. 开启runloop
        [[NSRunLoop currentRunLoop] run];
        //4\. 创建自己port
        self.myPort = [NSMachPort port];
        //5\. 设置port的代理回调对象
        self.myPort.delegate = self;
        //6\. 完成向主线程port发送消息
        [self sendPortMessage];
    }
}
/**
 *   完成向主线程发送port消息
 */

- (void)sendPortMessage {
    NSData *data1 = [@"AAAA" dataUsingEncoding:NSUTF8StringEncoding];
    NSData *data2 = [@"BBBB" dataUsingEncoding:NSUTF8StringEncoding];
    NSMutableArray *array  =[[NSMutableArray alloc]initWithArray:@[data1,self.myPort]];
    // 发送消息到VC的主线程
    // 第一个参数:发送时间。
    // msgid 消息标识。
    // components,发送消息附带参数。
    // reserved:为头部预留的字节数
    [self.vcPort sendBeforeDate:[NSDate date]
                          msgid:10086
                     components:array
                           from:self.myPort
                       reserved:0];
}

- (void)handlePortMessage:(NSPortMessage *)message{

    NSLog(@"person:handlePortMessage  == %@",[NSThread currentThread]);

    NSLog(@"从VC 传过来一些信息:");
    NSLog(@"components == %@",[message valueForKey:@"components"]);
    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
}

运行打印:
在这里插入图片描述

注意:

1.NSPort对象必须添加到要接受的线程的runLoop中
2.接收消息的对象实现NSPortDelegate协议的-handlePortMessage:方法来获取消息内容

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值