iOS开发进阶

多线程简介

2019-11-02  本文已影响0人  我叫Vincent
多线程简介

iOS系统 中,每一个应用都是一个进程。具体了解Runloop底层原理:https://www.jianshu.com/p/9cb4edc0670d,除了Runloop底层原理还介绍了线程间的通讯等。

进程与线程

进程与线程的关系

多线程的意义

多线程的原理其实就是CPU在单位时间片里快速在各个线程之间切换。一般情况下无论多核还是单核,我们的线程运行总是 "并发" 的,这时候我们所说的"并发"是一种模拟出来的状态,CPU在单位时间片里快速在各个线程之间切换,每个线程执行一小段时间,让多个线程看起来就像在同时运行。只有当cpu数量大于等于线程数量,这个时候是真正并发,可以多个线程同时执行计算。

优点
缺点

线程的生命周期

线程在创建后,start开始进入Runnable就绪的状态,这时会执行很多初始化的操作;接下进入running状态,CPU会调度当前线程,如果线程池中有当前线程会直接执行,在时间片的影响后CPU再次调度其他线程,直到当前线程任务执行完毕或强制退出,或者堵塞(调用sleep、等待同步锁或者从可调度线程池中移除)结束再次回到Runnable状态。

多线程相关补充

多线程技术方案
image.png
线程池

线程池可以使线程得到复用,所谓线程复用就是线程在执行完一个任务后并不被销毁,该线程可以继续执行其他的任务。在线程池大小小于核心线程池大小的时候,如果小于则直接创建线程执行任务。如果超出核心线程池大小则依赖队列,此时线程池会判断当前队列是不是已经满了,如果没有满,则提交任务到工作队列中,等待线程池调度执行任务。如果满了,并且当前工作队列所依赖的线程没有执行工作,那么则可以利用当前线程执行任务,如果此时线程都在工作,接下来会交给饱和策略。饱和策略一般默认都是中止策略,调用者可以捕获到该异常;还有抛弃策略,会悄悄抛弃该任务,不过一般会抛弃最旧的任务或者优先级比较低的任务等;还有调用者运行策略,实现了一种机制,这个机制不会抛弃任务也不会抛出异常,而是将任务回退到调用者,来达到降低新任务的流量。还有等待策略,也就是需要排队等候执行。

线程安全--锁

在开发高性能程序的时候几乎都会用到多线程,但是用到多线程也会碰到一些安全问题。比如多个线程同时对一块内存发生读和写的操作,或者程序执行的顺序会被打乱,可能造成提前释放一个变量,造成计算结果错误等,所以我们经常会用到锁。我们用锁来保证代码操作的原子性,让多线程对同一个数据或者资源进行访问同步。

锁的分类

这里简单说下锁,根据锁的状态、锁的特性和锁的设计等分为:

atomic与nonatomic

说到原子性就会想到属性关键字中atomic和nonatomic。设置atomic之后,默认生成的getter和setter方法执行是原子的,它只保证了自身的读/写操作,却不能说是线程安全。

  • 保证同一时间只有一个线程能够写入(但是同一个时间多个线程都可以取值)
  • atomic 本身就有一把锁(自旋锁)
  • 单写多读:单个线程写入,多个线程可以读取

atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备
我们可以用读写锁来解决,例如:

// 在 OC 中,如果同时重写 了 setter & getter 方法,系统不再提供 _成员变量,需要使用合成指令
// @synthesize name 取个别名:_name
@synthesize name = _name;
- (NSString *)name {
    return _name;
}
- (void)setName:(NSString *)name {
    /**
     * 增加一把锁,就能够保证一条线程在同一时间写入!
     */
    @synchronized (self) {
        _name = name;
    }
}

关于线程安全问题,多线程安全比多线程性能更重要,建议用@synchronizedNSLock,可保证可读性和安全性。

线程的创建

/**
 线程创建的方式
 */
- (void)creatThreadMethod{
    
    NSLog(@"%@", [NSThread currentThread]);
    
    //A: 1:开辟线程
    NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@3];
    // 2. 启动线程
    [t start];
    t.name = @"学习线程";
    
    // detach 分离,不需要启动,直接分离出新的线程执行
    [NSThread detachNewThreadSelector:@selector(study:) toTarget:self.p withObject:@5];
    
    //NSObject (NSThreadPerformAdditions)的分类
    //C : `隐式`的多线程调用方法,没有thread,也没有 start
    self.p = [[Person alloc] init];
    [self.p performSelectorInBackground:@selector(study:) withObject:@10];
    

    // GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self study;];
    });
    
    // NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
    NSLog(@"%@", [NSThread currentThread]);
}

Person.m文件实现

- (void)study:(id)time{
    for (int i = 0; i<[time intValue]; i++) {
        NSLog(@"%@ 开始学习了 %d分钟",[NSThread currentThread],i);
    }
}

或者

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//0: pthread
        
    /**
     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       创建线程失败的错误码,失败有多种可能!
     */
    // pthread
    pthread_t threadId = NULL;
    //c字符串
    char *cString = "HelloCode";
    // OC prethread -- 跨平台
    // 锁
    int result = pthread_create(&threadId, NULL, pthreadTest, cString);
    if (result == 0) {
        NSLog(@"成功");
    } else {
        NSLog(@"失败");
    }
    // GCD
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self threadTest];
    });
    
    // NSOperation
    [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
        [self threadTest];
    }];
}

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

TIP:C与OC的桥接

  • __bridge只做类型转换,但是不修改对象(内存)管理权;
  • __bridge_retained(也可以使用CFBridgingRetain)将Objective-C的对象转换为Core Foundation的对象,同时将对象(内存)的管理权交给我们,> 后- 续需要使用CFRelease或者相关方法来释放对象;
  • __bridge_transfer(也可以使用CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。

该文章为记录本人的学习路程,也希望能够帮助大家,知识共享,共同成长,共同进步!!!文章地址:https://www.jianshu.com/p/1e69a01c9bfd

上一篇 下一篇

猜你喜欢

热点阅读