多线程

339 阅读8分钟

一、线程和进程的基本概念

1、进程

是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存中,在iOS中属于单线程,andorid是多进程

例如我们打开mac的活动监视器页面,里面每一行都是一个进程,并且并不是每个进程都是有UI的

2、线程

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

3、线程和进程的关系和区别

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

4、线程、队列、任务的关系

打个比喻就是,线程是收银窗口,队列是窗口外面的排的队伍,任务就是排队的人。

任务执行的速度和这些方面有关系:

任务复杂度、CPU、线程数量、任务的优先级、队列的情况等有关系

二、多线程

1、多线程的优缺点

(1)优点

  • 能适当提高程序的执行效率
  • 能适当提高资源的利用率,例如CPU、内存
  • 线程上的任务执行完成后,线程会自动销毁

(2)缺点

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

2、多线程的原理

多线程其实就是CPU在单位时间片里快速的在各个线程之间切换

3、线程的声明周期

  • 新建:start
  • 就绪:runnable
  • 运行:running,CPU调度当前线程
  • 堵塞:blocked,调用sleep方法、等待同步锁、从可调度线程池中移出等
  • 死亡:任务执行完成、强制退出

4、线程池

当一个任务来了的时候,线程池会按照下面流程安排:

  • 线程池大小小于核心线程池大小的话,就创建线程执行任务
  • 线程够用时候就判断工作队列是否已经满了,如果没有,就直接将任务push到队列汇总
  • 假如工作队列都满了,就交给策略去处理

(1)Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获

(2)CallerRuns策略:为调解机制,及不抛弃任务也不抛出异常,而是将某些任务回退到调用者,不会在线程池中执行新的任务,而是在调用exector的线程中执行新的任务

(3)Discard策略:新提交的任务被抛弃

(4)DiscardOldest策略:丢掉等待最久的任务,然后尝试提交的新任务。不适合工作队列为优先队列场景

5、线程安全

当多个线程访问或者修改同一块内存数据时候,这样就会涉及到线程安全的问题了,解决方法就是用加锁方式保证一块内存同一时间只能用让一个线程去访问修改

6、线程间通信

一般可以通过NSPort进行端口通讯,下面我们来测试下vc通过NSPort跟person对象发送消息: VC代码:

@interface PortViewController ()<NSMachPortDelegate>
@property (nonatomic, strong) NSPort *myPort;
@property (nonatomic, strong) KCPerson *person;

@end

@implementation PortViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"Port线程通讯";
    self.view.backgroundColor = [UIColor whiteColor];

    //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];
    
}

#pragma mark - NSMachPortDelegate

- (void)handlePortMessage:(NSPortMessage *)message{
    
    NSLog(@"VC == %@",[NSThread currentThread]);
    
    NSLog(@"从person 传过来一些信息:");
//    NSLog(@"localPort == %@",[message valueForKey:@"localPort"]);
//    NSLog(@"remotePort == %@",[message valueForKey:@"remotePort"]);
//    NSLog(@"receivePort == %@",[message valueForKey:@"receivePort"]);
//    NSLog(@"sendPort == %@",[message valueForKey:@"sendPort"]);
//    NSLog(@"msgid == %@",[message valueForKey:@"msgid"]);
//    NSLog(@"components == %@",[message valueForKey:@"components"]);
    //会报错,没有这个隐藏属性
    //NSLog(@"from == %@",[message valueForKey:@"from"]);
    
    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);

}


- (void)getAllProperties:(id)somebody{
    
    u_int count = 0;
    objc_property_t *properties = class_copyPropertyList([somebody class], &count);
    for (int i = 0; i < count; i++) {
        const char *propertyName = property_getName(properties[i]);
         NSLog(@"%@",[NSString stringWithUTF8String:propertyName]);
    }
}

person代码


- (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 = [@"Gavin" dataUsingEncoding:NSUTF8StringEncoding];
    NSData *data2 = [@"Cooci" 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];
    
}

#pragma mark - NSMachPortDelegate

- (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"]);
}

7、线程和runloop的关系

  • 1、runloop与线程是一一对应的,一个runloop对应一个核心的线程,为啥说是核心的呢?是因为runloop是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里
  • 2、runloop是用来管理线程的,当线程的runloop被开启后,线程会在执行任务后进入休眠状态,有了任务就会被唤醒执行任务。
  • 3、runloop在第一次获取时被创建,在线程结束时被销毁
  • 4、对于主线程来说,runloop在程序一启动就默认创建好了
  • 5、对弈子线程来说,runloop是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意,确保子线程的runloop被创建,不然定时器不会回调

8、多线程的使用

内存分三个区:

堆区:效率差,但是空间大,alloc出来的对象放在这里,访问时候通知指针去访问相应的对象,指针保存在栈区,这样可以节省大量的栈区空间

栈区:临时变量,内存比较珍贵,假如有大量临时变量在栈区时候,我们就需要用@autorelease来释放临时变量

常量区:存放全局变量

代码区:存放代码的地方

野指针的产生:

当一个对象被创建时候,会有一个指针指向这个对象,并且通过修改链表空间表示该块内存已被使用,但是当对象被释放了时候,如果该指针还指向这块内存,这时候就产生了一个经典问题: 野指针

9、补充

a、C和OC的桥接

__bridge只做类型转换,但是不修改对象(内存管理权)

__bridge_retain(也可以使用CFBridgingRetain)将OC的对象转换成的Core Foundation的对象,同时也犟对象(内存)的管理权交给我们,后续需要使用CFRelease或者相关方法来释放对象

__bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换成Objective-C的对象,同时将对象(内存)的管理权交给ARC

b、atomic与nonatomic的区别

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

atomic:原子属性,针对多线程设计的,是属性的默认值,保证同一时间只有一个线程能够写入(但是同一时间能有多个线程取值),atomic本身就有一把锁(自旋锁),所以是线程安全的,但是需要消耗大量资源

开发建议:

  • 所有属性都声明为nonatomic
  • 尽量避免多线程抢夺同一快资源
  • 尽量将加锁、资源抢夺的业务逻辑交给服务器来处理,减小移动客户端的压力