iOS 开发我的测试收藏多线程

浅析 iOS 多线程

2016-06-23  本文已影响344人  _凉风_

一、基本知识

1. 进程

2. 线程

I. 线程的状态

// 让线程阻塞多久不做任务
+ (void)sleepUntilDate:(NSDate *)date;
// 示例 1:
[NSThread sleepUntilDate: [NSDate distanceFuture]]; //永远阻塞「当前语句所在的线程」
[NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:2]]; //阻塞 2秒「单位是秒,当前语句所在的线程」

+ (void)sleepForTimeInterval:(NSTimeInterval) ti;
// 示例2:
[NSThread sleepForTimeInterval:2]; //阻塞 2秒「单位是秒,当前语句所在线程」

II. 线程的通信

定义:

线程间的通信方法:

// 跳转回 主线程 的 哪个方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 跳转到 哪个线程的 哪个方法
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;

// 利用 NSPort(端口对象) 通讯「了解」
// A线程操作 B线程,需要 B将 1个Port对象 给A,A通过 Port对象 操作 B

3. 多线程「掌握」

定义:1个进程 开启多条线程,每条线程可以 并行「同时」执行不同的任务
原理:

优点:适当提高 程序执行效率 和 资源利用率「CPU、内存利用率」
缺点:

I. 主线程

定义:

注意:耗时操作放在主线程会卡住主线程,严重影响UI流畅度

II. 互斥锁「使用线程同步技术」

线程同步 定义:多条线程在同一条线上执行「按顺序的执行任务」
互斥锁 格式:@synchronized ( 锁对象 ){ 需要锁定的代码 }
注意:

优点:防止多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的 CPU资源「一般不提倡使用」

III. 原子和非原子属性

在 @property 的属性中有

注:加锁、资源抢夺一般交给服务器处理,减少客户端压力

二、iOS中多线程的实现方案

1. pthread「了解」

// 创建线程
#import<pthread.h>
pthread_t thread;
pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict);

2. NSThread 「掌握」

I. 基本用法

II. 创建和启动线程

// 方法1. 创建线程
NSThread *thread_1 = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"run方法的参数,obj类型"];
// 以下这步可以省略
thread_1.name = @"这条线程的名字"; 
// 启动线程「线程一启动,就会在线程thread中执行 self的 run方法」
[thread start];

// 注:以下两种方法 不返回线程对象
// 优点:简单快捷
// 缺点:无法对线程进行详细的设置

// 方法2. 创建后自动分离出线程,并启动
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"run方法的参数, obj类型"];

// 方法3. 隐式创建,并启动后台线程
[self performSelectorInBackground:@selector(run) withObject:nil];

3. GCD「Grand Central Dispatch 宏大的中枢调度器」

优势

使用步骤

  1. 创建队列或使用系统提供的队列
  2. 定制任务「线程操作」
  3. 将任务添加到 队列「存放任务」

1) 任务

任务的执行方式「GCD会自动从队列中取出任务,放到对应线程中执行」

I. 同步任务「synchronize」

简介

执行步骤

  1. 阻塞当前线程,保持和当前线程的同步
  2. 执行同步任务中的任务
  3. 回到当前队列,使当前队列不在阻塞,执行当前队列的任务

示例结果:1、2、3

// block:需要执行的任务
// dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

II. 异步任务「asynchronous」

简介

执行步骤

  1. 执行当前线程「不会阻塞当前线程」
  2. 执行异步任务

示例结果:1、3、2

// dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 每个有block的函数都有对应的 function函数
// dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);

NSLog(@"1. %@", [NSThread currentThread]);
dispatch_queue_t newQueue = dispatch_queue_create("test.testName.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(newQueue, ^{
    NSLog(@"2. %@", [NSThread currentThread]);
});
NSLog(@"3. %@", [NSThread currentThread]);

2)队列

I. 串行队列「Serial Dispatch Queue」

// 1. 获得主队列
dispatch_queue_t qu = dispatch_get_main_queue();

// 2. 将任务加入队列
// 2.1 主队列+异步函数:串行执行,不能开启新线程
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 主队列+同步函数:串行执行,不能开启新线程「如果执行的函数也在主队列则,队列阻塞,都无法执行」
dispatch_sync(qu, ^{ /*任务*/ });
// 1. 创建 1个串行队列
// label: 队列名称「格式: 作用.名称.queue」,队列名称会在 CrashLog 里标明崩溃的队列名称
// dispatch_queue_attr_t: 队列类型「并发/同步」
// dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
// 属性:DISPATCH_QUEUE_SERIAL 等价于 NULL
dispatch_queue_t qu = dispatch_queue_create("down.Miao.queue", DISPATCH_QUEUE_SERIAL);

// 2. 将任务加入队列
// 2.1 串行队列+异步函数:不能并发,可以开启多线程
// 异步函数:任务执行完成后,不等待 任务执行的队列结束就返回得出的结果
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 串行队列+同步函数:能并发,不会开启新线程
// 同步函数:任务执行完成后,等待 任务执行的队列结束就返回得出的结果
dispatch_sync(qu, ^{ /*任务*/ });

// 3. 自己创建的队列需要手动释放「MRC模式」
dispatch_release(qu); // 当然 也有 dispatch_retain() 方法

II. 并发队列「Concurrent Dispatch Queue」

// 有 4 个执行优先级:
// - 高  :DISPATCH_QUEUE_PRIORITY_HIGH
// - 默认:DISPATCH_QUEUE_PRIORITY_DEFAULT
// - 低  :DISPATCH_QUEUE_PRIORITY_LOW
// - 后台:DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
// 不是自己创建的对象无法手动释放
// 1. 创建 1个并发队列
dispatch_queue_t qu = dispatch_queue_create("com.Miao.queue", DISPATCH_QUEUE_CONCURRENT);

// 2. 将任务加入并发队列
// 2.1 并行队列+异步函数:可以并发,可以开启新线程
dispatch_async(qu, ^{ /*任务*/ });

// 2.2 并行队列+同步函数:不能并发,不会开启新线程
dispatch_sync(qu, ^{ /*任务*/ });

III. 变更线程的优先级

dispatch_set_target_queue(/*要设置优先级的对列*/, /*与期望的优先级相同的队列*/);

IV. 线程通讯

示例:下载图片

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 1. 耗时的异步操作
    // 获取图片网络路径
    NSURL *url = [NSURL URLWithString:@"图片网络路径/图片名.图片格式"];
    // 加载图片
    NSData *data = [NSData dataWithContentsOfURL: url];
    // 生成图片
    UIImage *image = [UIImage imageWithData:data];

    // 2. 回到主线程「最好用异步函数」
    dispatch_async( dispatch_get_main_queue(), ^{
        self.imageView.image = image;
    });
});

3)信号量

信号:多线程中的计数器,计数为 0 时线程等待,计数为 1 或者大于 1 时,减去 1 而不等待
示例:由于信号量为 10 在这个并行队列里,最多会有 10 个任务被执行

// 创建信号量,其值设为 10
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);

for (int i = 0; i < 100; i++) {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 超时后,信号量会失效
        // 信号量为 0,线程被阻塞;信号量 不为 0,信号总量 -1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);        
        NSLog(@"__%d__",i);
        // 休眠
        [NSThread sleepForTimeInterval:3];
        // 信号量 +1
        dispatch_semaphore_signal(semaphore);
    });
}

4) GCD 其他方法

I. 障碍函数

使用限制

// 此方法阻塞的是 传入的 queue「不是当前线程」
// 参数 queue 不能是 全局的并发队列
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

执行步骤

  1. 阻塞传入的线程 queue
  2. queue 前面的任务执行完毕,执行 障碍函数
  3. 取消对线程 queue 的阻塞
dispatch_async(queue, ^{ /* 读 */ });
dispatch_async(queue, ^{ /* 读 */ });
dispatch_barrier_async(queue, ^{ /* 并行读入完成,开始并行写入 */ });
dispatch_async(queue, ^{ /* 写 */ });
dispatch_async(queue, ^{ /* 写 */ });

II. 延时函数「不影响现有的线程继续执行」

dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), 
    // 2秒后到主线程执行任务
    dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});
int count = 0;
// 1. 获得队列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue();
    
// 2. 创建一个定时器(dispatch_source_t本质还是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
// 3. 设置定时器的各种属性(几时开始任务,每隔多长时间执行一次)
//    GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
//    何时开始执行第一个任务
//    dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比当前时间晚3秒
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(self.timer, start, interval, 0);
    
// 4. 设置回调
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"------------%@", [NSThread currentThread]);
    count++;
    if (count == 4) {
        // 6. 取消定时器
        dispatch_cancel(self.timer);
        self.timer = nil;
     }
});    
// 5. 启动定时器
dispatch_resume(self.timer);

III. 一次性代码「保证代码在程序运行中,只执行一次」

static dispatch_one_t onceToken; // 标记是否执行的过
dispatch_once(&onceToken, ^{ /*要只执行一次的代码块,线程安全的已经加锁了*/ });

IV. 快读迭代「可以多线程同时遍历」

dispatch_queue_t qu = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 10为遍历的数组长度
dispatch_apply(10, qu,^(size_t index){
    // 遍历需要的操作
});

VI. 队列组

作用

示例:

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), ^{
    // 执行2个耗时的异步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

4. NSOperation「底层是GCD,做了面向对象的封装」

作用

1)NSOperation 的子类「了解」

I. NSInovcationOperation

注意:

// 创建NSInvocationOperation对象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

// 调用start方法开始执行操作
// 一旦执行操作,就会调用target的sel方法
- (void)start;

II. NSBlockOperation

注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作

// 创建NSBlockOperation对象
// 默认在当前线程执行
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 通过addExecutionBlock:方法添加更多的操作
// 添加额外的任务会开启新的线程异步执行
- (void)addExecutionBlock:(void (^)(void))block;

III. 自定义类「继承自NSOperation」

还有很多等等

2)NSOperationQueue「掌握」

作用:将 NSOperation 添加到 NSOperationQueue 中,系统会自动异步执行 NSOperation 中的操作

I. NSOperationQueue的队列类型

// 1. 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 设置最大并发数,如果为 1 则为串行
queue.maxConcurrentOperationCount = 1;

// 2. 创建操作(任务)
// 2.1 创建NSInvocationOperation
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
   
// 2.2 创建NSBlockOperation
// 快速创建并添加方式
[queue addOperationWithBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];
// 普通方式
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
}];
[op2 addExecutionBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
}];

// 2.3 创建自定义任务
Person *op3 = [[Person alloc] init];
  
// 3. 添加任务到队列中
[queue addOperation:op1]; // 不用调用start方法,自动开启线程
[queue addOperation:op2]; 
[queue addOperation:op3]; 

II. NSOperationQueue 的方法

III. 线程的依赖和监听

// 线程操作B 会在 线程操作A执行完后 在执行
[operationB addDependency:operationA]; // 操作B依赖于操作A
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}];

op2.completionBlock = ^{
    NSLog(@"监听线程操作 op2执行完毕后要执行的操作");
};

[queue addOperation:op2]; 

IV. 线程间的通讯

[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
   // 图片的网络路径
   NSURL *url = [NSURL URLWithString:@"图片路径/图片名.格式"];
   // 加载图片
   NSData *data = [NSData dataWithContentsOfURL:url];
   // 生成图片
   UIImage *image = [UIImage imageWithData:data];
   // 回到主线程
   [[NSOperationQueue mainQueue] addOperationWithBlock:^{
       self.imageView.image = image;
   }];
}];

5. 多线程的应用:单例设计模式「Singleton」

定义:

使用情景:「操作频繁的时候,提升效率」

  1. 类只能有一个实例,必需从一个为人熟知的访问点访问,比如工厂方法
  2. 整个应用程序中,共享一份资源

注意:

  1. 某个类只能有一个实例
  2. 为保证实例唯一性,这个方法必须是静态类方法
  3. 单例方法名称一般以 share 或 default 开头
  4. 这个类必须自行创建这个对象
  5. 必须自行向整个系统提供这个实例

示例代码一、使用GCD

// 实现之外,先定义一个实例,为了防止野指针,赋空值
// 实现之外的静态变量,一个类只有一个「不能用继承来实现不同类的单例」
static Sample *_instance = nil;
@implement Sample
// alloc、allocWithZone方法都会调用 allocWithZone方法,所以只重写 allocWithZone方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    // 确保多线程的线程安全
    static dispach_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    return _instance;
}

// 直接返回单例对象
+ (instancetype)shareInstance{
    // 确保多线程的线程安全
    static dispatch_one_t onceToken; 
    dispach_once(&onceToken, ^{
        _instance = [[self alloc] init];
    });
    return _instance;
}

// 防止 Copy方法的误用
-(id)copyWithZone:(NSZone *)zone{
    return _instance; 
}
-(id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}

// 若是MRC模式下,需要添加一下代码
- (oneway void)release{
    // 为保证单例,只重写父类方法,什么都不需要做
}
- (instancetype)retain{
    return _instance;
}
- (NSInteger)retainCount{
    return MAXFLOAT; //为了方便程序员交流,这里返回一个很大的数    
}
@end

示例代码二、不使用GCD

static Sample _instance = nil;
@implement Sample

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    // 添加同步锁,注意加锁的位置
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}

+ (instancetype)sharedInstance {
    // 添加同步锁
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
    }
    return _instance;
}

// 以下代码与使用GCD时重复,略
@end

将单例作为宏定义使用

// 声明方法,
// #define a(b) is_##b  的作用是 将 a(hehe) 自动替换为 is_hehe
#define interfaceSingleton(name) +(instancetype)share##name;

// 定义方法
// 系统自带 宏定义 判断是否为 arc模式
#if !__has_feature(obj_arc)

#define implememtationSingleton(name) \
static id _instance = nil;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
+ (instancetype)share##name{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [[self alloc] init];\
    });\
    return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{ return _instance; }\
- (id)mutableCopyWithZone:(NSZone *)zone{ return _instance; }\
- (oneway void)release{}\
- (instancetype)retain{ return _instance; }\
- (NSInteger)retainCount{ return (unsigned long)MAXFLOAT}

#else

#define implememtationSingleton(name) \
static id _instance = nil;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [super allocWithZone:zone];\
    });\
    return _instance;\
}\
+ (instancetype)share##name{\
    static dispatch_once_t onceToken; \
    dispatch_once(&onceToken, ^{\
        _instance = [[self alloc] init];\
    });\
    return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{ return _instance; }\
- (id)mutableCopyWithZone:(NSZone *)zone{ return _instance; }
#endif
interfaceSingleton(className)   // 这里没有「;」因为是宏定义的使用
implementationSingleton(className) 
上一篇下一篇

猜你喜欢

热点阅读