您的位置:首页 > 其它

GCD使用(多线程004)

2016-05-09 15:07 232 查看

1. RunLoop介绍

Runloop被称为消息循环或事件循环
每个线程里,都有一个消息循环
默认情况下,主线程开启消息循环,子线程不开启

目的

保证程序不退出
负责处理输入事件
如果没有事件处理,会让程序进行休眠

消息类型(事件类型)

Input Sources(输入源)

Input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects.

包括键盘鼠标事件,NSPort,NSConnection,等。

Timer Sources(定时源)

An NSRunLoop object also processes NSTimer events.

就是定时器NSTimer

常用的循环模式

NSDefaultRunLoopMode

The mode to deal with input sources other than NSConnection objects. This is the most commonly used run-loop mode. Available in iOS 2.0 and later.

NSRunLoopCommonModes

Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of CFRunLoopAddCommonMode for details.

如何使用

创建消息
把消息加入到消息循环中,并指定循环的模式
加入的2种方法

[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];


只有当消息的循环模式和当前线程中消息循环的模式像符合,消息才能被运行

子线程中的消息循环

子线程中默认不开启消息循环

开启方式

Run方法

[[NSRunLoop currentRunLoop] run];

缺点:开启之后无法关闭,并且在他之后的代码都不会被运行,因为他是一个死循环

runUntilDate

[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];

苹果推荐方法
BOOL shouldKeepRunning = YES;        // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);


问题演示

线程没有执行方法,线程立马销毁
NSThread *thread = [[NSThread alloc] init];
[thread start];
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];

线程指定方法,但是没有开启消息循环
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
[thread start];
[self performSelector:@selector(demo2) onThread:thread withObject:nil waitUntilDone:NO];

..........................

- (void)demo {
NSLog(@"over");
}


2. GCD简介

全称是Grand Central Dispatch
纯C语言,提供了非常多强大的函数

优点

GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

核心组成部分

任务&队列

任务:要干什么事情

队列:用来存放任务

如何做

确定要做的事情
把任务加到队列里
没了
GCD会自动将任务从队列里面取出,放到对应的贤臣各种去执行

任务取出的原则是先进先出FIFO(First in first out)

举例

//任务
dispatch_block_t block;
//队列
dispatch_queue_t queue;
//把任务放到队列里
dispatch_async(queue, block);

3. 队列执行任务的方式

同步执行任务

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务

异步执行任务

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

区别

同步:在当前线程上执行 异步:在其他线程上执行

4. 队列的种类

并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
dispatch_queue_t queue = dispatch_queue_create("CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
dispatch_queue_t queue = dispatch_queue_create("SERIAL", DISPATCH_QUEUE_SERIAL);


5. 同步、异步、并发、串行的基本概念

同步和异步决定了要不要开启新的线程

同步:在
当前
线程中执行任务,
不具备
开启新线程的能力
异步:在
新的
线程中执行任务,
具备
开启新线程的能力

并发和串行决定了任务的执行方式

并发:多个任务并发
(同时)
执行
串行:一个任务执行完毕后,再执行
下一个
任务

6. 串行队列的执行

串行队列的同步执行

不开线程,同步执行(在当前线程执行)
#define DISPATCH_QUEUE_SERIAL NULL
//串行队列
//dispatch_queue_t q = dispatch_queue_create("test", NULL);
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//同步执行
dispatch_sync(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}

串行队列的异步执行

开一个线程,顺序执行
//只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}


7. 并行队列的执行

并行队列,异步执行

开多个线程,异步执行,每次开启多少个线程是
不固定
的(线程数,不由我们控制),线程数是由gcd来决定的
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
//异步执行
dispatch_async(q, ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});
}

并行队列,同步执行

不开线程,顺序执行,与
串行队列的同步执行
一模一样

8. 系统提供的队列

iOS已经为我们准备好了2个常用队列
主队列
列和
全局并发队列
,开发中用的很多

主队列

主队列是负责在主线程调度任务的
会随着程序启动一起创建
主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();


主队列,异步任务

不开线程,同步执行
主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
主队列又叫 
全局串行队列

主队列,同步执行

程序执行不出来
(死锁)
死锁的原因
当程序执行到下面这段代码的时候

dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@ -- %d",[NSThread currentThread],i);
});

主队列:如果主线程正在执行代码,就不调度任务

同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)

全局并发队列

为了方便程序员开发提供的队列,与并发队列行为
相同


9. 同步异步串行并发的组合结果

 全局并发队列手动创建串行队列主队列
同步(sync)没有开启新线程,串行执行任务没有开启新线程,串行执行任务死锁
异步(async)有开启新线程,并行执行任务开启新线程,并行执行任务
没有开启新线程
,串行执行任务

10. GCD延时函数使用

dispatch_after的定义

dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);

dispatch_after的参数

参数1 dispatch_time_t when

多少纳秒之后执行

参数2 dispatch_queue_t queue
任务添加到那个队列

参数3 dispatch_block_t block
要执行的任务

例子

//延时操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});


11. GCD一次执行和单例实现

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”

// MARK: 一次性执行
- (void)once {
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);

dispatch_once(&onceToken, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"一次性吗?");
});
NSLog(@"come here");
}

dispatch_once是
线程安全
的,因为内部也有一把锁,苹果推荐使用该种方式来实现单例模式

测试

单例的2中实现

使用 dispatch_once 实现单例
+ (instancetype)sharedSingleton {
static id instance;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});

return instance;
}

使用互斥锁实现单例
+ (instancetype)sharedSync {
static id syncInstance;

@synchronized(self) {
if (syncInstance == nil) {
syncInstance = [[self alloc] init];
}
}

return syncInstance;
}

对比结果
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
long largeNumber = 1000 * 1000;

// 测试互斥锁
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSync];
}
NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);

// 测试 dispatch_once
start = CFAbsoluteTimeGetCurrent();
for (long i = 0; i < largeNumber; ++i) {
[Singleton sharedSingleton];
}
NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}


12. GCD任务组

使用场景:在组里的异步任务都执行完毕后,再去执行其他操作,例如下载歌曲,等所有的歌曲都下载完毕之后 转到 主线程提示用户

方式一:dispatch_group_async常用用法
/**
调度组-在一组异步代码执行完毕后,统一获得通知

应用场景:将一组图像异步缓存到本地之后统一获得通知!
*/
- (void)group1 {
// 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 调度组
dispatch_group_t group = dispatch_group_create();

// 添加异步
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download A %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"download B %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"download C %@", [NSThread currentThread]);
});

// 调度组通知 - 监听群组中,所有异步执行的代码完成后,得到通知
// 异步监听!
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"下载完成 %@", [NSThread currentThread]);
});

NSLog(@"come here");
}

方式二:添加enter & leave
// MARK: - 调度组 2
- (void)group2 {
// 1. 调度组
dispatch_group_t group = dispatch_group_create();

// 2. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);

// dispatch_group_enter & dispatch_group_leave 必须成对出现
dispatch_group_enter(group);
dispatch_group_async(group, q, ^{
NSLog(@"任务 1 %@", [NSThread currentThread]);

// dispatch_group_leave 必须是 block 的最后一句
dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_group_async(group, q, ^{
NSLog(@"任务 2 %@", [NSThread currentThread]);

// dispatch_group_leave 必须是 block 的最后一句
dispatch_group_leave(group);
});

// 4. 阻塞式等待调度组中所有任务执行完毕
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

// 5. 判断异步
NSLog(@"OVER %@", [NSThread currentThread]);
}

注意:
不要跟dispatch_barrier_async混淆,一个是所有任务都完成后,一个是某个任务中的一段代码需要交给另一个线程有序的串行执行
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息