多线程需要掌握的知识点
线程的注意点
-
不要同时开太多线程(1~3条即可,不要超过5条)
-
线程概念
-
主线程:UI线程,显示、刷新UI界面,处理UI控件的事件
-
子线程:后台线程,异步线程
-
不要把耗时操作放在主线程中,容易造成线程阻塞,要放在子线程中执行
线程阻塞的情况
#import<pthread.h>
//viewDidLoad
//创建线程
/*
参数1:线程id
参数2:NULL
参数3:线程执行的返回值为void *,参数为void *的函数
参数4:NULL
pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void *), void *restrict)
*/
NSLog(@"viewDidLoad------%@",[NSThread currentThread]);//打印线程1
pthread_t myRestrict;
pthread_create(myRestrict, NULL, run, NULL);
//run函数
void *run(void *data){
NSLog(@"run------%@",[NSThread currentThread]);//打印线程2
return NULL;
}
//touchesBegan
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
for(int i = 0;i < 10000;i++)
{
NSLog(@"touchesBegan----%d-----%@",i,[NSThread currentThread]);//在主线程中执行造成线程阻塞
}
}
子线程执行代码
#import<pthread.h>
//viewDidLoad
//run函数
void *run(void *data){
for(int i = 0;i < 10000;i++)
{
NSLog(@"touchesBegan----%d-----%@",i,[NSThread currentThread]);//在主线程中执行造成线程阻塞
}
return NULL;
}
//touchesBegan
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//创建线程
pthread_t myRestrict;
pthread_create(myRestrict, NULL, run, NULL);
}
NSThread
一个NSThread对象就代表一条线程
创建启动线程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self createThread1];
[self createThread2];
[self createThread3];
}
/**
* 创建线程方式1:创建并手动开启线程
*/
-(void)createThread1{
//创建线程,执行下载方法
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(download) object:nil];
//线程是有名称属性的,可以直接设置,打印时可以直接打印
thread.name = @"下载线程";
//判断此线程是否是主线程
NSLog(@"判断此线程是否是主线程:%d",[thread isMainThread]);
//启动线程(调用self的download方法)
[thread start];
//判断当前线程是否是主线程
NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}
-(void)download{
NSLog(@"下载东西---主线程:%@--当前线程%@",[NSThread mainThread],[NSThread currentThread]);
NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}
/**
* 创建线程方式2:创建并自动启动线程
*/
-(void)createThread2{
//最后一个参数object.用于传值
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"http://a.jpg"];
}
-(void)download:(NSString *)url{
NSLog(@"下载东西---主线程:%@--当前线程%@---url:%@",[NSThread mainThread],[NSThread currentThread],url);
NSLog(@"判断当前线程是否是主线程:%d",[NSThread isMainThread]);
}
/**
* 创建线程方式3:隐式创建线程
*/
-(void)createThread3{
//在主线程执行download:方法,相当于:[self download:@"http://c.gif"];
[self performSelector:@selector(download:) withObject:@"http://c.gif"];
//创建子线程执行download:方法
[self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];
}
线程的状态
注:一旦线程死亡,就不能再次开启任务
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
#pragma mark ------观察线程状态------
//新建线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//线程就绪
[thread start];
}
-(void)run {
//开始运行线程
NSLog(@"-------begin------");
//睡眠(阻塞)5秒钟
//[NSThread sleepForTimeInterval:5];
//从现在开始睡3秒
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
for (int i = 0; i < 100; i++) {
NSLog(@"-----%d-----",i);
//手动结束线程,一旦线程死亡,就不能再次开启任务
//return;
[NSThread exit];
}
NSLog(@"---------end---------");
//自动结束线程
}
多线程的安全隐患
资源共享造成多个线程访问同一块资源,引发数据错乱和数据安全问题(存取款、买卖票)
解决方式:互斥锁(同步锁)
注:锁定一份代码只需要一把锁,用多把锁是无效的
@property (strong, nonatomic) NSThread *thread1;
@property (strong, nonatomic) NSThread *thread2;
@property (strong, nonatomic) NSThread *thread3;
/**
* 剩余票数
*/
@property (assign, nonatomic) int leftTicketCount;
//viewDidLoad
self.leftTicketCount = 100;
self.thread1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"1号窗口";
self.thread2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"2号窗口";
self.thread3 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3.name = @"3号窗口";
/**
* 卖票
*/
-(void)saleTicket{
while (1) {
//加锁,()里面是锁对象,可以填任意对象,用self保证只创建一个锁对象
@synchronized(self) {
if (self.leftTicketCount > 0) {
//此时会造成线程阻塞,导致线程之间获取资源相同,导致每个线程卖票时票数相同,所以要给线程加锁解锁
[NSThread sleepForTimeInterval:0.1];
self.leftTicketCount--;
NSLog(@"%@卖了一张票,剩余%d张票",[NSThread currentThread].name,self.leftTicketCount);
}
else
{
return;//退出循环
}
}//解锁
}
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
互斥锁(同步锁)的优缺点
-
优点:能有效防止因多线程抢夺资源造成的数据安全问题,形成线程同步
-
缺点:需要消耗大量的CPU资源
原子和非原子属性
OC在定义属性时有nonatomic和atomic两种选择
-
atomic:原子属性,为setter方法加锁,且属性默认为atomic;线程安全,需要消耗大量的资源
-
nonatomic:非原子属性,不会为setter方法加锁;非线程安全,适合内存小的移动设备
-
IOS开发建议
-
所有属性都声明为nonatomic
-
尽量避免多线程抢夺同一块资源
-
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
-
1个线程传递数据给另1个线程
-
在1个线程中执行完特定任务后,转到另1个线程继续执行任务
线程间通信的方法
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelectorInBackground:@selector(download) withObject:nil];
}
/**
* 下载图片
*/
-(void)download{
NSLog(@"download-----------%@",[NSThread currentThread]);
//1.图片地址
NSString *urlStr = @"http://www.bz55.com/uploads/allimg/150309/139-150309101F2.jpg";
//2.根据地址下载图片的二进制数据
NSLog(@"---begin---");
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlStr]];
NSLog(@"---end---");
//3.设置图片
UIImage *image = [UIImage imageWithData:data];
//4.回到主线程,刷新UI界面(为了线程安全)
//[self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:YES];
//[self performSelector:@selector(download) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
//最后一个参数用于设置是否等待子线程完成
[self.imgView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
//-(void)downloadFinished:(UIImage *)image{
// self.imgView.image = image;
// NSLog(@"downloadFinished-----------%@",[NSThread currentThread]);
//}
GCD(Grand Central Dispatch--中枢调度器
优势
-
GCD是Apple为多核的并行运算提出的解决方案
-
GCD会自动利用更多的CPU内核(双核、四核)
-
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
-
程序员只要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
概念
-
任务:执行什么操作
-
队列:用来存放任务
步骤
-
定制任务:确定想做的操作
-
将任务添加到队列中
-
GCD自动将队列中的任务取出,放到对应的线程中执行
-
任务的取出遵循队列的FIFO原则:先进先出,后进后出
队列的类型
-
并发队列(Concurrent Dispatch Queue)
-
可以让多个任务并发执行
-
并发功能只有在异步函数下才有效
-
串行队列(Serial Dispatch Queue)
让任务一个接一个的执行
-
dispatch_sync:同步,不具备开启线程的能力
-
dispatch_async:异步,具备开启线程的能力
各种队列的执行效果
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self asyncGlobalQueue];
[self asyncSerialQueue];
[self asyncMainQueue];
[self syncGlobalQueue];
[self syncSerialQueue];
}
#pragma mark --------常用----------
/**
* async--并发队列(最常用)
* 会不会创建线程:会,一般同时创建多条线程
* 任务的执行方式是并发的
*/
-(void)asyncGlobalQueue{
//获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//将任务添加到全局队列中异步执行
dispatch_async(queue, ^{
NSLog(@"异步加载1-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载2-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载3-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
}
/**
* async--串行队列(有时候用)
* 会不会创建线程:会,一般只开一条线程
* 任务的执行方式是串行的
*/
-(void)asyncSerialQueue{
//1.创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("asyncSerialQueue", NULL);
//2.将任务添加到串行队列中异步执行
dispatch_async(queue, ^{
NSLog(@"异步加载1-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载2-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载3-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
//3.非ARC情况下,需要释放创建的队列;此时为ARC,不能用release
//dispatch_release(queue);
}
/**
* async--主队列(很常用,常用于线程之间传值)
*/
-(void)asyncMainQueue{
//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]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
}
/**
* sync--主队列(不能用)
*/
-(void)syncMainQueue{
//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_async(queue, ^{
NSLog(@"异步加载3-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
}
#pragma mark --------不常用----------
/**
* sync--并发队列
* 会不会创建线程:不会
* 任务的执行方式:串行执行(一个任务执行完之后再执行下一任务)
* 并发队列失去了并发的功能。因为不存在多线程
*/
-(void)syncGlobalQueue{
//获得全局的并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//将任务添加到全局队列中同步执行
dispatch_sync(queue, ^{
NSLog(@"异步加载1-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载2-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载3-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
}
/**
* sync--串行队列
* 会不会创建线程:不会
* 任务的执行方式是串行的
*/
-(void)syncSerialQueue{
//1.创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("syncSerialQueue", NULL);
//2.将任务添加到串行队列中异步执行
dispatch_sync(queue, ^{
NSLog(@"异步加载1-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载2-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载3-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载4-------%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"异步加载5-------%@",[NSThread currentThread]);
});
}
框架之间数据类型转换
Foundation:OC框架
Core Foundation:C语言框架
Foundation和Core Foundation框架的数据类型可以互相转换的
编号 | Foundation | Core Foundation |
---|---|---|
1 | NSString | CFStringRef |
2 | NSArray | CFArrayRef |
3 | NSDictionary | CFDictionaryRef |
4 | NSNumber | CFNumberRef |
强制转换语法:
NSString *str = @"123"; //Foundation框架
//不同框架数据类型转换需要进行强制转换:桥接__bridge
CFStringRef str2 = (__bridge CFStringRef)(str) ; //Core Foundation框架
NSString *str3 = (__bridge NSString *)(str2);
NSLog(@"str2 = %@",(__bridge id)(str2));
NSLog(@"str3 = %@",str3);
Core Foundation中手动创建的数据类型,都需要手动释放
总结:
-
凡是函数名中带有create\copy\new\retain等字眼,都应该在不需要使用这个数据时进行release
-
GCD的数据类型在ARC环境下不需要release
-
CF(Core Foundation)的数据类型在ARC|MRC环境下,均需要Release
CFArrayRef array = CFArrayCreate(NULL, NULL, 10, NULL);
//释放
CFRelease(array);
CGPathRef path = CGPathCreateMutable();
CGPathRelease(path);
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"-----Began------");
[self delay1];
[self delay2];
[self delay3];
NSLog(@"-----end------");
}
/**
* 方法一:sleep,不建议使用,因为会卡掉线程
*/
-(void)delay1{
[NSThread sleepForTimeInterval:3];
//延迟3秒执行
NSLog(@"-------delay1--------");
}
/**
* 方法二:延迟执行所要进行的方法,较常用
* 一旦定制好延迟任务后,不会卡当前线程
*/
-(void)delay2{
//写在哪个线程里就在那个线程执行
//3秒后执行事件download: 虽然是在主线程里执行的,但不会卡住主线程
[self performSelector:@selector(download:) withObject:@"haha" afterDelay:3];
}
-(void)download:(NSString *)str{
NSLog(@"-----download:---%@----%@--",str,[NSThread currentThread]);
}
/**
* 方法三:GCD,
*/
-(void)delay3{
//可以直接设置是在主线程中执行还是在子线程中执行
//从当前时间起延迟3秒 : 虽然是在主线程里执行的,但不会卡住主线程
//3秒后回到主线程,执行block中的代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----- GCD delay main ------%@--",[NSThread currentThread]);
});
//3秒后自动开启新线程,执行block中的代码
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), queue, ^{
NSLog(@"----- GCD delay global ------%@--",[NSThread currentThread]);
});
}
一次性代码(单例)
使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
可以用于单例
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//这里面默认是线程安全的
NSLog(@"----下载图片----");
});
单例模式
可以保证在程序运行期间,一个类只有一个实例,而且该实例供外界访问
-
懒汉式:第一次用到单例对象时,再创建
-
ARC
-
MRC
-
饿汉式:一进入程序就创建一个单例对象
static的作用
-
修饰全局变量:全局变量的作用域仅限于当前文件内部,此时外部文件extern该变量就引用不到了,防止其他文件恶意篡改变量值
-
修饰局部变量:保证局部变量永远只初始化一次,在程序运行过程中,永远只有一份内存。但是a还是只能用在局部变量作用域范围内
队列组(合并图片——水印)
将任务存放于队列中,将队列存放与队列组中,而队列组的特性是保证执行完组里面的所有任务后,再执行notify函数里面的block,故可以保证子线程执行完毕后执行主线程
/**
* 方法二:队列组
*/
-(void)test2{
//1.队列组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2.下载图片一
__block UIImage *img1 = nil;
dispatch_group_async(group, queue, ^{
//下载第一张图片:当图片地址不标准时会出错,例如网址中有‘,’等现象
NSData *dataBigImg = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://imgsrc.baidu.com/forum/pic/item/e3f7237f9e2f07084d0553f7e924b899a801f2e2.jpg"]];
img1 = [UIImage imageWithData:dataBigImg];
});
//3.下载图片二
__block UIImage *img2 = nil;
dispatch_group_async(group, queue, ^{
//下载第二张图片
NSData *dataSmallImg = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://img1.bdstatic.com/static/home/widget/search_box_home/logo/home_white_logo_0ddf152.png"]];
img2 = [UIImage imageWithData:dataSmallImg];
});
//4.合并图片(保证执行完组里面的所有任务后,再执行notify函数里面的block)
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContextWithOptions(img1.size, NO, 0.0);
[img1 drawInRect:CGRectMake(0, 0, img1.size.width, img1.size.width)];
[img2 drawInRect:CGRectMake(0, 0, img2.size.width, img2.size.height)];
UIImage *fullImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//5.跳转主线程,加载图片
dispatch_async(dispatch_get_main_queue(), ^{
self.imgView.image = fullImg;
});
});
//ARC环境下就不需要release了
//dispatch_release(group);
}