多线程 线程安全 线程间通信
一、什么是多线程
一个iOS程序就像一个圆,不断循环,直到将它切断。一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。iOS中的程序启动,创建好一个进程的同时,一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他线程不同,它是其他线程最终的父线程,且所有界面的显示操作即AppKit或UIKit的操作必须在主线程进行。 系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每创建一个新的线程,都需要一些内存和消耗一定的CPU时间。另外当多个线程对同一个资源出现争夺的时候需要注意线程安全问题。
二、创建线程
1. 使用NSThread
,创建一个NSThread的对象,调用其start方法。
// 创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"http://b.png"];
//线程名字
thread.name = @"下载线程";
// 启动线程(调用self的download方法)
[thread start];
2. 使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
这个类方法创建一个线程并且会自动启动线程。
3. 使用NSObject 其实NSObject直接就加入了多线程的支持,允许对象的某个方法在后台运行。如:
//(调用self的download方法)
[self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];
三、线程安全
多个线程可能会访问同一块资源很容易引发数据错乱和数据安全问题
解决方法:
互斥锁使用格式
@synchronized(锁对象) { // 需要锁定的代码 }
OC在定义属性时有nonatomic和atomic两种选择
atomic
:原子属性,为setter方法加锁(默认就是atomic)
nonatomic
:非原子属性,不会为setter方法加锁
atomic加锁原理
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
atomic
:线程安全,需要消耗大量的资源
nonatomic
:非线程安全,适合内存小的移动设备
iOS开发的建议:
1 .所有属性都声明为nonatomic
2 .尽量避免多线程抢夺同一块资源
3 .尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
四、线程间的通信
- 线程间通信的体现
1 .一个线程传递数据给另一个线程
2 .在一个线程中执行完特定任务后,转到另一个线程继续执行任务
- 线程间通信常用的方法
1. `NSThread`可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在主线程执行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
用法如下:
//点击屏幕开始执行下载方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelectorInBackground:@selector(download) withObject:nil];
}
//下载图片
- (void)download
{
// 1.图片地址
NSString *urlStr = @"http://d.jpg";
NSURL *url = [NSURL URLWithString:urlStr];
// 2.根据地址下载图片的二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(@"---end");
// 3.设置图片
UIImage *image = [UIImage imageWithData:data];
// 4.回到主线程,刷新UI界面(为了线程安全)
[self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:NO];
// [self performSelector:@selector(downloadFinished:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
- (void)downloadFinished:(UIImage *)image
{
self.imageView.image = image;
NSLog(@"downloadFinished---%@", [NSThread currentThread]);
}
2. `GCD`一个线程传递数据给另一个线程,如:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"donwload---%@", [NSThread currentThread]);
// 1.子线程下载图片
NSURL *url = [NSURL URLWithString:@"http://d.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 2.回到主线程设置图片
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"setting---%@ %@", [NSThread currentThread], image);
[self.button setImage:image forState:UIControlStateNormal];
});
});
}