iOS多线程iOS开发记录

多线程(上)

2015-10-31  本文已影响457人  iOS_Cqlee

多线程

进程与线程

多线程概念

多线程的原理

多线程的优缺点


iOS中多线程的实现方案

pthread

 + 创建pthread
    * pthread_create
 + 只要create一次就会创建一个新的线程
 + 系统会自动在子线程中调用传入的函数
     第一个参数: 线程的代号(当做就是线程)
     第二个参数: 线程的属性
     第三个参数: 指向函数的指针, 就是将来线程需要执行的方法
     第四个参数: 给第三个参数的指向函数的指针 传递的参数
     void *(*functionP)(void *)
     void *  == id
         pthread_create(<#pthread_t *restrict#>, <#const pthread_attr_t *restrict#>, <#void *(*)(void *)#>, <#void *restrict#>)
     一般情况下C语言中的类型都是以 _t或者Ref结尾
    pthread_t threadId;
    // 只要create一次就会创建一个新的线程
    pthread_create(&threadId , NULL, &demo, "name");

NSThread

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
// 线程一启动,就会在线程thread中执行self的run方法

主线程相关用法
+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程
NSThread *current = [NSThread currentThread];

线程的名字
- (void)setName:(NSString *)n;
- (NSString *)name;
创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];

上述2种创建线程方式的优缺点
优点:简单快捷
缺点:无法对线程进行更详细的设置
启动线程
- (void)start; 
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态

强制停止线程
+ (void)exit;
// 进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务
//第一种创建线程的方式:alloc init.
//特点:需要手动开启线程,可以拿到线程对象进行详细设置
    //创建线程
    /*
     第一个参数:目标对象
     第二个参数:选择器,线程启动要调用哪个方法
     第三个参数:前面方法要接收的参数(最多只能接收一个参数,没有则传nil)
     */
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"wendingding"];
     //启动线程
    [thread start];

//第二种创建线程的方式:分离出一条子线程
//特点:自动启动线程,无法对线程进行更详细的设置
    /*
     第一个参数:线程启动调用的方法
     第二个参数:目标对象
     第三个参数:传递给调用方法的参数
     */
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是分离出来的子线程"];

//第三种创建线程的方式:后台线程
//特点:自动启动线程,无法进行更详细设置
[self performSelectorInBackground:@selector(run:) withObject:@"我是后台线程"];
   //设置线程的属性
    //设置线程的名称
    thread.name = @"线程A";

    //设置线程的优先级,注意线程优先级的取值范围为0.0~1.0之间,1.0表示线程的优先级最高,如果不设置该值,那么理想状态下默认为0.5
    thread.threadPriority = 1.0;
//线程的各种状态:新建-就绪-运行-阻塞-死亡
//常用的控制线程状态的方法
[NSThread exit];//退出当前线程
[NSThread sleepForTimeInterval:2.0];//阻塞线程
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];//阻塞线程
//注意:线程死了不能复生
-(void)touchesBegan:(nonnull NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event
{
//    [self download2];

    //开启一条子线程来下载图片
    [NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}

-(void)downloadImage
{
    //1.确定要下载网络图片的url地址,一个url唯一对应着网络上的一个资源
    NSURL *url = [NSURL URLWithString:@"http://图片的url地址"];

    //2.根据url地址下载图片数据到本地(二进制数据
    NSData *data = [NSData dataWithContentsOfURL:url];

    //3.把下载到本地的二进制数据转换成图片
    UIImage *image = [UIImage imageWithData:data];

    //4.回到主线程刷新UI
    //4.1 第一种方式
//    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

    //4.2 第二种方式
//    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];

    //4.3 第三种方式
    [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
//第一种方法
    NSDate *start = [NSDate date];
    //2.根据url地址下载图片数据到本地(二进制数据)
    NSData *data = [NSData dataWithContentsOfURL:url];

    NSDate *end = [NSDate date];
    NSLog(@"第二步操作花费的时间为%f",[end timeIntervalSinceDate:start]);

//第二种方法
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
    NSData *data = [NSData dataWithContentsOfURL:url];

    CFTimeInterval end = CFAbsoluteTimeGetCurrent();
    NSLog(@"第二步操作花费的时间为%f",end - start);

多线程的安全隐患

原子和非原子

线程间通信

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
从子线程回到主线程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行耗时的异步操作...
      dispatch_async(dispatch_get_main_queue(), ^{
        // 回到主线程,执行UI刷新操作
        });
});

GCD

简介

执行任务

用同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务

用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD中还有个用来执行任务的函数:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行,这个queue不能是全局的并发队列

使用dispatch_queue_create函数创建队列
dispatch_queue_t
dispatch_queue_create(const char *label, // 队列名称 
dispatch_queue_attr_t attr); // 队列的类型

创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);


GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 队列的优先级
unsigned long flags); // 此参数暂时无用,用0即可

获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

使用dispatch_queue_create函数创建串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("com.name", NULL); 

使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
01 异步函数+并发队列:开启多条线程,并发执行任务
1.获取全局的并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0 , 0);
    
 2.添加任务到队列
文档说明是FIFO原则, 先进先出
打印结果不正确的原因: 线程的执行速度可能不一样, 有得快一些, 有的慢一些
    dispatch_async(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });

02 异步函数+串行队列:开启一条线程,串行执行任务
 异步 + 串行 = 会创建新的线程, 但是只会创建一个新的线程, 所有的任务都在这一个新的线程中执行
 异步任务, 会先执行完所有的代码, 再在子线程中执行任务
- (void)asyncSerial
{
 1.创建队列
    dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
    
2.添加任务
    dispatch_async(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });
    NSLog(@"%s", __func__);
}


03 同步函数+并发队列:不开线程,串行执行任务
 同步 + 并行 = 不会开启新的线程
 注意: 能不能开启新的线程, 和并行/串行没有关系, 只要函数是同步还是异步
- (void)syncConcurrent
{
    // 1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 2.添加任务
    dispatch_sync(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });
    NSLog(@"%s", __func__);
}
04 同步函数+串行队列:不开线程,串行执行任务
 同步 + 串行 = 不会创建新的线程
 注意: 如果是同步函数, 只要代码执行到了同步函数的那一行, 就会立即执行任务, 只有任务执行完毕才会继续往后执行
- (void)syncSerial
{
    // 1.创建队列
     正是因为线程默认就是串行, 所以创建串行队列的时候, 队列类型可以不传值
    //    dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue = dispatch_queue_create("name", NULL);
    
    // 2.添加任务
    dispatch_sync(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });
    
    NSLog(@"%s", __func__);
}

05 异步函数+主队列:不开线程,在主线程中串行执行任务
- (void)asyncMain
{
 主队列, 只要将任务放到主队列中, 那么任务就会在主线程中执行
    dispatch_queue_t queue = dispatch_get_main_queue();
 如果任务放在主队列中, 哪怕是异步方法也不会创建新的线程
    dispatch_async(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });
}



06 同步函数+主队列:不开线程,串行执行任务(注意死锁发生)
 同步 + 主队列 = 需要记住的就一点: 同步函数不能搭配主队列使用
 注意: 如果是在子线程中调用同步函数 + 主对列 是可以执行的
- (void)syncMian
{
    // 主队列, 只要将任务放到主队列中, 那么任务就会在主线程中执行
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 需要记住的就一点: 同步函数不能搭配主队列使用
    dispatch_sync(queue, ^{
        NSLog(@"1 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2 - %@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3 - %@", [NSThread currentThread]);
    });
    
    NSLog(@"++++++++++++++");
}

07 注意同步函数和异步函数在执行顺序上面的差异
 //0.获取一个全局的队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    //1.先开启一个线程,把下载图片的操作放在子线程中处理
    dispatch_async(queue, ^{

       //2.下载图片
        NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        NSLog(@"下载操作所在的线程--%@",[NSThread currentThread]);

        //3.回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
           self.imageView.image = image;
           //打印查看当前线程
            NSLog(@"刷新UI---%@",[NSThread currentThread]);
        });

    });

    01 栅栏函数(控制任务的执行顺序)
    dispatch_barrier_async(queue, ^{
        NSLog(@"--dispatch_barrier_async-");
    });

    02 延迟执行(延迟·控制在哪个线程执行)
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"---%@",[NSThread currentThread]);
    });
    iOS常见的延时执行
    调用NSObject的方法
    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    // 2秒后再调用self的run方法

    使用GCD函数
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后执行这里的代码...
});

    使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];

    03 一次性代码(注意不能放到懒加载)
    -(void)once
    {
        //整个程序运行过程中只会执行一次
        //onceToken用来记录该部分的代码是否被执行过
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{

            NSLog(@"-----");
        });
    }

    使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

    04 快速迭代(开多个线程并发完成迭代操作)
       dispatch_apply(subpaths.count, queue, ^(size_t index) {
    });
    使用dispatch_apply函数能进行快速迭代遍历
    dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    // 执行10次代码,index顺序不确定
});

    05 队列组(同栅栏函数)
    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    //队列组中的任务执行完毕之后,执行该函数
    dispatch_group_notify(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);
    
    有这么1种需求
    首先:分别异步执行2个耗时的操作
    其次:等2个异步操作都执行完毕后,再回到主线程执行操作

    如果想要快速高效地实现上述需求,可以考虑用队列组
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

    
    

补充

使用Crearte函数创建的并发队列和全局并发队列的主要区别:
1.全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Crearte函数是实打实的从头开始去创建一个队列。
2.在iOS6.0之前,在GCD中凡是使用了带Crearte和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。
3.在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)
4.其它区别涉及到XUN内核的系统级线程编程,不一一列举。
5.给出一些参考资料(可以自行研究):

GCDAPI:https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create

Libdispatch版本源码:http://www.opensource.apple.com/source/libdispatch/libdispatch-187.5/

XCODE7 补充

在想从网上下载的,显示真实的图片,必须在info.plist添加1个key


上一篇 下一篇

猜你喜欢

热点阅读