您的位置:首页 > 移动开发 > IOS开发

ios开发之多线程---GCD

2016-08-09 09:25 369 查看
一:基本概念

1:进程:正在运行的程序为进程。

2:线程:每个进程要想执行任务必须得有线程,进程中任务的执行都是在线程中。

3:线程的串行:一条线程里任务的执行都是串行的,假如有一个进程开辟了一条线程,此条线程里有ABC三个任务,则ABC三个任务的执行是串行的,ABC三个任务按顺序一个个执行

4:多线程:一个进程可以开辟多条线程去并行(同时)去执行任务,假如有ABC三个任务,则此进程会开辟多条线程去并发执行ABC三个任务,可以提高运行效率

5:线程的原理:同一时间,cpu只能处理一条线程,而且也只有一条线程在工作,多线程的并发执行,也就是cpu在多条线程间爱会切换调度。进程一启动,就默认创建了一条线程为主线程大约为1M,所创建的子线程大约为512KB。

6:多线程的缺点:1:大量开线程,会大量占用cpu资源,cpu在调度线程上的开销也就越大 2:大量开线程,会使每条线程的执行效率降低,程序的性能降低,对于移动开发来说,适当开辟线程,大量开线程会使程序的想能降低

7:ios开发中多线程的应用:程序一启动就默认会开辟一条线程,成为主线程,也就是UI线程,主线程主要负责,刷新和显示UI,处理按钮的点击事件,表格的滚动拖拽事件,尽量避免把耗时的操作放在主线程。凡在主线程执行的任务都是串行的,按照顺序一个个执行,若把耗时的操作放在主线程,则会卡主主线程,影响主线程的其他操作,造成很差的用户体验。正确的做法是:将比较耗时的操作放在子线程中去执行,不要影响主线程的其他操作。

8:获得当前线程:[NSThread currentThread]; 获得主线程:[NSThread mainThread];

9:ios中的多线程方案:1:Pthread:跨平台,适用于各个系统,线程的生命周期由程序员去管理。 2:NSThread 3:GCD:由系统自动去管理线程的声明周期 4:NSOperation:基于GCD封装,也是右系统去管理线程的生命周期。

二:线程的安全:

1:线程安全:当同一块资源被多条线程同时访问的时候,会涉及到线程安全的问题,例如卖票,银行取钱问题。解决方法:加互斥锁

@synchronized(self) {被锁住的代码 }(加锁是很耗性能的,当只有多条线程访问统一资源的时候,才考虑去加互斥锁)。加锁的工作原理是:加互斥锁后,同一时间只许可一条线程访问该资源,其他线程无法访问。当某条线程访问该资源时,会加一把锁,被锁住的代码执行完毕后,锁会被打开,下个线程此时才可以访问该资源,注意:锁对象必须是唯一的,否则多条线程依然可以访问该资源。锁对象一般用self,因为在控制器中,控制器对象就是唯一的,保证了锁对象的唯一性。

2:线程同步:加锁用的就是线程同步技术,多条线程同一条线程上按顺序依次执行 线程异步:多条线程异步去执行

3:原子与非原子属性:atomic是原子属性,默认是线程安全的,在执行setter方法赋值的时候,会加一把互斥锁,等待赋值结束后,所才被打开,防止了多条线程同时访问造成的线程安全问题。虽然是线程安全的,但是加锁消耗了大量的资源 。nonatomic:非原子属性,线程不安全的,没有加锁,因为在实际移动开发中,调用setter方法大部分都是在主线程,所以不涉及多条线程同时访问的问题。若涉及了多线程同时访问该资源,可以加一把互斥锁,还可以用atomic原子属性去修饰

三:GDC

1:GCD有两个核心的概念:1:队列 2:任务,其中队列是用来存放任务,GCD的使用:创建任务直接将任务放到队列里,GCD会自动将任务从队列里取出,按照FIFO原则(先进先出,后进后出,栈是现进后出)取出任务,放到相应的线程中去执行任务。

2:GCD有两个常用的函数用来执行任务:1:同步函数: dispatch_sync

dispatch_sync(queue, ^{

});

2:异步函数:dispatch_async

dispatch_async(queue, ^{

});

其中queue为队列,block为任务。其中同步函数:在当前线程中按顺序串行执行任务,不具备开启线程的能力 异步函数:在新的线程中执行,具备开启新线程的能力

3:GCD两大队列:1:串行队列:队列中的任务按顺序执行,一个任务执行完毕才会去执行下一个任务 2:并发队列:可以并发同时执行多个任务,并发队列只有在异步函数 dispatch_async中才能起到并发执行的作用

总结:1:同步和异步主要影响能不能开启新的线程 2:串行队列和并发队列主要影响任务执行的方式:串行:按顺序依次执行,并发队列:多个任务同时执行 3:看一条线程,先看最外层是同步还是异步,若是同步,不能开启新的线程,只在当前的线程中执行,若是异步,则具备开启新线程的能力,可以在新县城中执行 再看内层:若是串行队列:则任务按顺序执行 若是并发队列:则可以并发同时还行任务,但只有在异步函数中才会并发执行任务。

4:队列的创建:

1:dispatch_queue_t queue = dispatch_queue_create(a,b);a参数为队列名称,C语言字符串,用"",b参数为队列,串行队列:DISPATCH_QUEUE_SERIAL,也可以这么创建串行队列

dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

并发队列:DISPATCH_QUEUE_SERIAL NULL 注意:1:异步并发队列有三个任务,则并不会一定开启三条线程,开启线程的个数是由GCD内部去决定的。2:任务添加到队列后,会按FIFO原则将任务取出,并发执行,也就是三个任务的执行先后顺序是不确定的

2:并发队列的创建:1:可以获取到全局并发队列:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);第一个参数为队列优先级,默认为DEFAULT,也可以传0,第二个参数暂时用不到传0就可以,当涉及多条线程需要设定优先级时,可以设置第一个参数 2:也可以手动去创建并发队列dispatch_queue_create(a,b)

5:GCD的各种队列:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self syncMain];
}

/**
* 同步函数 + 主队列:只打印begin,是因为同步函数放在串行队列中会卡主当前线程,造成死锁。同步函数不开启线程在当前线程中执行,且立即执行,而主线程的任务end要先执行完再去执行同步函数中任务,而同步函数又要立即执行,所以会造成死锁
*/
- (void)syncMain
{
NSLog(@"syncMain ----- begin");

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

// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});

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

/**
* 异步函数 + 主队列:只在主线程中执行任务。无论同步还是异步,只要将任务放到追队列里,任务就会在主线程中执行,且是串行执行任务,主队列是一种特殊的串行队列。因为123任务是后添加到主队列里的任务,所以先执行完 start 和 end 在执行 1,2,3
*/
- (void)asyncMain
{   NSLog(@"syncConcurrent--------start");
// 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();

// 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});
NSLog(@"syncConcurrent--------end");
}

/**
* 同步函数 + 串行队列:同步不会开启新的线程,在当前线程串行执行任务,且立即执行任务。任务是串行的,执行完一个任务,再执行下一个任务。打印顺序:start 1,2,3 end
*/
- (void)syncSerial
{

NSLog(@"syncConcurrent--------start");
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);

// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});

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

/**
* 异步函数 + 串行队列:异步函数会开启新的线程,但是任务是串行的,任务按顺序执行执行完一个任务,再执行下一个任务。所以只开启一条线程(在一条线程中任务是按照顺序一个接一个执行)。打印顺序:statrt end 1 2 3。相当于把asyncSerial任务放到了主队列中执行,任务则在主线程中执行,又将任务123放到了串行队列中执行,所以先执行完主线程的任务,在执行串行队列的任务,所以先打印start end 再按照顺序依次执行任务1,2,3
*/
- (void)asyncSerial

{    NSLog(@"syncConcurrent--------start");
// 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_SERIAL);
//    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

// 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});

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

/**
* 同步函数 + 并发队列:不会开启新的线程,在当前线程中串行执行任务且立即执行任务,因为并发队列只有在异步函数中才会起作用,打印顺序:start  1,2,3,end
*/
- (void)syncConcurrent
{
NSLog(@"syncConcurrent--------start");
// 1.获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
});

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

/**
* 异步函数 + 并发队列:1:可以同时开启多条线程并发同时执行任务,开启线程的个数不确定,并且并发队列只有在异步函数下才有效 2:代码执行的顺序是先打印 satrt 在打印 end 最后再打印子线程中的内容,先执行主线程中的任务,在执行异步线程的任务,123任务执行完成的先后顺序是不确定的。原因是:相当于把asyncConcurrent任务放到了主队列里,在主线程执行,任务是按照FIFO原则从队列中取出,所以先执行完start后,分别将任务123添加到了全局并发队列里,123任务是后添加到队列中的,所以先执行完主队列任务,再异步并发执行子线程中的任务。
*/
- (void)asyncConcurrent
{
// 1.创建一个并发队列
// label : 相当于队列的名字
//    dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);

NSLog(@"asyncConcurrent--------start");

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

// 2.将任务加入队列
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"1-----%@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"2-----%@", [NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<10; i++) {
NSLog(@"3-----%@", [NSThread currentThread]);
}
});

NSLog(@"asyncConcurrent--------end");

}

@end


6:GCD中的线程间通信

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

NSLog(@"---------------------------------------1");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"---------------------------------------3");
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];

// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];

// 生成图片
UIImage *image = [UIImage imageWithData:data];

// 回到主线程
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"-------------------------------------5");
});

NSLog(@"---------------------------------------4");

});

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

@end


打印顺序:1,2,3,4,5,先执行主线程中的任务,1,2 主线程中的任务执行完毕后再去异步执行子线程中的任务,先执行完子线程中的任务3,4 后,再回到主线程执行任务5。原因:相当于把touchBegan任务放到了主队列里,任务在主线程中执行,子线程中的任务是后面添加到并发队列里的,所以先回执行完主线程中的任务1,2 当执行完毕后再去执行子线程中的任务,在子线程中的任务5是后面加入到主队列的,所以先回执行完子线程中的任务3,4,再回到主线程里执行任务5

6:GCD的其他函数

#import "ViewController.h"
#import "XMGPerson.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
/** 图片1 */
@property (nonatomic, strong) UIImage *image1;
/** 图片2 */
@property (nonatomic, strong) UIImage *image2;
@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

XMGPerson *person1 = [[XMGPerson alloc] init];
XMGPerson *person2 = [[XMGPerson alloc] init];
XMGPerson *person3 = [[XMGPerson alloc] init];
XMGPerson *person4 = [[XMGPerson alloc] init];

//    XMGPerson *p1 = [[XMGPerson alloc] init];
//    NSLog(@"%@", p1.books);
//
//    XMGPerson *p2 = [[XMGPerson alloc] init];
//    NSLog(@"%@", p2.books);

}

void download(void * data)
{

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//    dispatch_async(<#dispatch_queue_t queue#>, <#^(void)block#>);
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//    dispatch_async_f(queue, NULL, download);

//    [self group];
[self barrier];
}
/**
*    群组:1:群组中的任务是并发执行的,会等群组中的任务都执行完毕后,会调用 dispatch_group_notify,在并发队列中完成绘图操作,在主线程中去显示图片。1:首先创建群组: dispatch_group_t group = dispatch_group_create(); 2:将任务放到并发队列中,将队列放到群组中,12任务执行完不确定 3:都执行完调用dispatch_group_async,将耗时的绘图操作还放在异步线程,绘图完毕后,回到主线程显示UI
*/
- (void)group
{

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建一个队列组
dispatch_group_t group = dispatch_group_create();

// 1.下载图片1
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];

// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];

// 生成图片
self.image1 = [UIImage imageWithData:data];
});

// 2.下载图片2
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];

// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];

// 生成图片
self.image2 = [UIImage imageWithData:data];
});

// 3.将图片1、图片2合成一张新的图片
dispatch_group_notify(group, queue, ^{
// 开启新的图形上下文
UIGraphicsBeginImageContext(CGSizeMake(100, 100));

// 绘制图片
[self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
[self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];

// 取得上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

// 结束上下文
UIGraphicsEndImageContext();

// 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
// 4.将新图片显示出来
self.imageView.image = image;
});
});
}

/**
* 快速迭代:dispatch_apply(subpaths.count, queue, ^(size_t index):快速迭代函数:第一个参数为迭代对象的个数,第二个参数为队列,index为返回的索引,快速迭代,并发执行,提高迭代效率
*/
- (void)apply
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSString *from = @"/Users/xiaomage/Desktop/From";
NSString *to = @"/Users/xiaomage/Desktop/To";

NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from];

dispatch_apply(subpaths.count, queue, ^(size_t index) {
NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
// 剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];

NSLog(@"%@---%@", [NSThread currentThread], subpath);
});
}

/**
* 传统文件剪切
*/
- (void)moveFile
{
NSString *from = @"/Users/xiaomage/Desktop/From";
NSString *to = @"/Users/xiaomage/Desktop/To";

NSFileManager *mgr = [NSFileManager defaultManager];
//此方法获得的是from路径下的所有文件路径,包括子文件夹下的文件路径
NSArray *subpaths = [mgr subpathsAtPath:from];

for (NSString *subpath in subpaths) {
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 将文件从一个文件夹移动到另一个文件夹:是个耗时的操作,座椅在子线程中执行
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
});
}
}
/**
*    一次性函数:在整个项目中只执行一次
*/
- (void)once
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"------run");
});
}

/**
* 延迟执行:GCD延迟函数:dispatch_after所传的队列不同,就在不同的队列中执行延迟函数,延迟的方法有以下三种
*/
- (void)delay
{
NSLog(@"touchesBegan-----");
//    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];

//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"run-----");
//    });

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
}

- (void)run
{
NSLog(@"run-----");
}

/**
*    dispatch_barrier_async函数:能保证先12任务先执行完,在执行dispatch_barrier_async中的任务,最后在执行34任务,要想此函数起作用,queue不能是全局并发队列,因为queue是并发队列,所以12,34执行完的先后顺序不确定,但是能保证先执行12,在执行dispatch_barrier_async函数任务,最后执行34
*/
- (void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});

dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});

dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}

@end


7:GCD下完整的单例模式:GCD模式下的单例保证了线程的安全,dispatch_once是线程安全的,防止同一时间多条线程初始化同一个变量

#import "XMGPerson.h"

@interface XMGPerson() <NSCopying>

@end

@implementation XMGPerson

/**
*    模拟三种情况得到单例对象:
1:alloc创建对象 2:通过对象copy得到一个新对象 3:直接调用sharedPerson得到一个新对象
2:1:先用static定义一个下划线的成员变量 2:alloc方法内部会调用allocWithZone方法,用dispatch_once函数调用父类[super allocWithZone:zone]去开辟空间 3:当在类中实现copyWithZone方法时,需要类去遵守NSCopying协议,直接将成员变量返回,是因为调用copy之前该对象已经被初始化了
*/
static XMGPerson *_person;

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_person = [super allocWithZone:zone];
});
return _person;
}

+ (instancetype)sharedPerson
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_person = [[self alloc] init];
});
return _person;
}

- (id)copyWithZone:(NSZone *)zone
{
return _person;
}
@end


8:单例的宏定义:其中的\表示下一行也属于宏定义

// .h文件
#define XMGSingletonH(name) + (instancetype)shared##name;

// .m文件
#define XMGSingletonM(name) \
static id _instance; \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
\
+ (instancetype)shared##name \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
\
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instance; \
}


9:非GCD模式下的单例:加锁的目的是保证线程安全,防止同一时间多条线程访问初始化同一个变量

#import "XMGPerson.h"

@interface XMGPerson()

@end

@implementation XMGPerson

static id _instance;

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

- (id)copyWithZone:(NSZone *)zone
{
return _instance;
}
@end
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: