一.多线程的基本概念
-
进程:可以理解成一个运行中的应用程序,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,主要管理资源。
-
线程:是进程的基本执行单元,一个进程对应多个线程。同一进程中的多条线程将共享该进程中的全部系统资源,但是自有调用堆栈和寄存器环境。
-
主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。
-
多线程:在同一时刻,一个CPU只能处理1条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程多线程并发执行的假象。
-
使用多线程的目的:将耗时的操作放在后台执行!
-
注意:如果线程非常多,CPU会在N多线程之间切换,CPU消耗就会特别大,每条线程被调用额频率就会被降低,所有要适当使用多线程
![](https://img.haomeiwen.com/i13294749/8966d49ee4de5778.png)
二.多线程的生命周期
线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡
![](https://img.haomeiwen.com/i13294749/138da2e361a3ef38.png)
-
新建:实例化线程对象。
-
就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
-
运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
-
阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
-
死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象。
三.多线程的四种解决方案
![](https://img.haomeiwen.com/i13294749/22e7fb9080800a48.png)
四.phread
1.创建pthread_create与释放pthread_detach线程
char *str = "...";
pthread_t thread;
//1.创建一个线程
pthread_create(&thread, NULL, task, str);
//2.线程执行完毕 自动释放内存资源
pthread_detach(thread);
void *task(void *str){
}
创建方法pthread_create(<#pthread_t _Nullable restrict _Nonnull#>, <#const pthread_attr_t restrict _Nullable#>, <#void _Nullable ( _Nonnull)(void * _Nullable)#>, <#void *restrict _Nullable#>)
可以看出,第一个参数是需要一个Pthread 对象指针,第三个是需要一个C语言函数方法(就当于OC中绑定的执行方法),至于第二个和第四个参数,暂时没有什么用,可以直接传入NULL
2.创建pthread_mutex_init与销毁pthread_mutex_destroy信号量(互斥锁)
pthread_mutex_t mutex;
//创建信号量(互斥锁)
pthread_mutex_init(&mutex, NULL);
//销毁
pthread_mutex_destroy(&mutex);
4.上锁pthread_mutex_lock与解锁pthread_mutex_unlock
//上锁
pthread_mutex_lock(&mutex);
//锁住的代码
//解锁
pthread_mutex_unlock(&mutex);
5.利用[NSThread currentThread]
打印当前线程
6.代码使用
#import "ViewController.h"
#import <pthread.h>
/**信号量*/
static pthread_mutex_t mutex;
static int total = 20;
@interface ViewController ()
@end
@implementation ViewController
-(void)dealloc{
//销毁
pthread_mutex_destroy(&mutex);
}
- (void)viewDidLoad {
[super viewDidLoad];
//创建信号量(互斥锁)
pthread_mutex_init(&mutex, NULL);
//循环创建
for (int i = 0; i < 20; i++) {
NSString *threadID = [NSString stringWithFormat:@"%d",i+1];
char *str = (char *)threadID.UTF8String;
pthread_t thread;
//1.创建一个线程
pthread_create(&thread, NULL, task, str);
//2.线程执行完毕 自动释放内存资源
pthread_detach(thread);
}
}
void *task(void *str){
//上锁
pthread_mutex_lock(&mutex);
if (total > 0) {
NSLog(@"线程%s 购票成功 总共:%d张 剩余:%d张",str,total,total-1);
total -= 1;
}
//解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
@end
五.NSThread
1.四种创建方式
//1.alloc init
NSThread *thread = [[NSThread alloc]
initWithTarget:self selector:@selector(test:) object:@"str"];
thread.name = @"子线程1";
[thread start];
//2.block
thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
thread.name = @"子线程2";
[thread start];
//3.通过detach方式创建好之后自动启动
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//4.隐式创建,直接启动
[self performSelectorInBackground:@selector(test:) withObject:@"perform"];
-(void)test:(NSString *)str{
NSLog(@"%@",str);
NSLog(@"%@",[NSThread currentThread]);
}
2.阻塞休眠
//休眠多久
[NSThread sleepForTimeInterval:2];
//休眠到指定时间
[NSThread sleepUntilDate:[NSDate date]];
3.类方法补充
//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];
4.NSThread的一些属性
//线程是否在执行
thread.isExecuting;
//线程是否被取消
thread.isCancelled;
//线程是否完成
thread.isFinished;
//是否是主线程
thread.isMainThread;
//线程的优先级,取值范围0.0到1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
thread.threadPriority;
5.互斥锁
//方法1
@synchronized(self){
//被锁住的代码
}
//方法2
NSLock *lock; //创建锁
[self.lock lock]; //加锁
//被锁住的代码
[self.lock unlock]; //解锁
6.线程间通信
在一个进程中,线程往往不是孤立存在,多个线程需要经常进行通信,一般表现为一个线程传递数据给另外一个线程,或者在一个线程中执行完一个特定任务后,转到另一个线程继续执行任务
//方法
- (void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr
withObject:(id)arg waitUntilDone:(BOOL)wait;
![](https://img.haomeiwen.com/i13294749/8c0fdbec785a9827.png)
7.代码例子
#import "ViewController.h"
static int total = 20;
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
/**锁*/
@property (nonatomic,strong) NSLock *lock;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//创建锁
self.lock = [[NSLock alloc] init];
//1.alloc init
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test:) object:@"str"];
thread.name = @"子线程1";
[thread start];
//2.block
thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
thread.name = @"子线程2";
[thread start];
//3.detach 自动开启线程
[NSThread detachNewThreadWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
NSURL *url = [NSURL URLWithString:@"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1558750668&di=eb9d96f21b34ff93dafdfbdb7338677f&src=http://img3.duitang.com/uploads/item/201503/07/20150307203046_nRfZw.thumb.700_0.jpeg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
[self performSelectorOnMainThread:@selector(updateUI:) withObject:img waitUntilDone:YES];
}];
//4.performSelector
[self performSelectorInBackground:@selector(test:) withObject:@"perform"];
//安全问题
for (int i = 0; i < 20; i++) {
//创建线程
[NSThread detachNewThreadWithBlock:^{
NSObject *obj = [NSObject new];
@synchronized(obj){
NSLog(@"%d线程 总共:%d张 剩余:%d张",i,total,total-1);
total -= 1;
}
//加锁
[self.lock lock];
NSLog(@"---%d线程 总共:%d张 剩余:%d张",i,total,total-1);
total -= 1;
//解锁
[self.lock unlock];
}];
}
}
-(void)test:(NSString *)str{
NSLog(@"%@",str);
NSLog(@"%@",[NSThread currentThread]);
}
-(void)updateUI:(UIImage *)img{
self.imageView.image = img;
}
@end
六.GCD
1.使用GCD的好处
- GCD 可用于多核的并行运算
- GCD 会自动利用更多的 CPU 内核(比如双核、四核)
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
2.队列和任务
1.队列:指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列:
每次只有一个任务被执行。让任务一个接着一个地执行。
只开启一个线程,一个任务执行完毕后,再执行下一个任务。
并发队列:
可以让多个任务并发(同时)执行。
可以开启多个线程,并且同时执行任务。
并发队列的并发功能只有在异步函数下才有效
2.任务:线程中执行的操作,执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
1.同步执行(sync):
同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之
后再继续执行。
只能在当前线程中执行任务,不具备开启新线程的能力。
2.异步执行(async):
异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
可以在新的线程中执行任务,具备开启新线程的能力。
异步虽然具有开启新线程的能力,但是并不一定开启新线程。
3.GCD的使用
-
1.创建一个队列(串行队列或并发队列)
-
2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
4.队列的创建
1.串、并行队列的创建
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
2.特殊的串行队列-主队列
//所有放在主队列中的任务,都会放到主线程中执行
// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();
3.全局并发队列
//第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
5.任务的创建
1.同步、异步任务的创建
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
2.队列与任务的搭配
//1.创建队列
//2.创建任务
//串行队列 所有的任务都是按照顺序执行
dispatch_queue_t queue1 = dispatch_queue_create("identifier1", DISPATCH_QUEUE_SERIAL);
//创建任务 同步 异步
//串行队列添加同步任务
//没有开辟新的线程
dispatch_sync(queue1, ^{
NSLog(@"串-同步:%@",[NSThread currentThread]);
});
//串行队列添加异步任务
//先添加的任务先执行,在同一个线程
dispatch_async(queue1, ^{
NSLog(@"串-异步:%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"串-异步:%@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"串-异步:%@",[NSThread currentThread]);
});
//并行队列
dispatch_queue_t queue2 = dispatch_queue_create("identifier1", DISPATCH_QUEUE_CONCURRENT);
//创建任务 同步 异步
//并行队列添加同步任务
//没有开辟新的线程
dispatch_sync(queue2, ^{
NSLog(@"并-同步:%@",[NSThread currentThread]);
});
//并行队列添加异步任务
//并发执行,每个任务都开辟一个线程
dispatch_async(queue2, ^{
NSLog(@"并-异步:%@",[NSThread currentThread]);
});
dispatch_async(queue2, ^{
NSLog(@"并-异步:%@",[NSThread currentThread]);
});
//在主队列
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主-异步:%@",[NSThread currentThread]);
});
主队列加同步任务的情况出现死锁的解释请看后续的死锁
6.GCD 线程间的通信
/**
* 线程间通信
*/
- (void)communication {
// 获取全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 异步追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
}
// 回到主线程
dispatch_async(mainQueue, ^{
// 追加在主线程中执行的任务
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
});
});
}
7. GCD 的其他方法
1.栅栏方法:dispatch_barrier_async
2.延时执行方法:dispatch_after
3.一次性代码(只执行一次):dispatch_once
4.快速迭代方法:dispatch_apply
5.队列组:dispatch_group
6.信号量:dispatch_semaphore
七.NSOperation
八.线程安全问题
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。就好比几个人在同一时修改同一个表格,造成数据的错乱
解决多线程安全问题的方法
1.互斥锁(同步锁)
@synchronized(锁对象) {
// 需要锁定的代码
}
如果代码中只有一个地方需要加锁,大多都使用self作为锁对象,这样可以避免单独再创建一个锁对象。
如果多个地方需要加锁,建议创建一个对象,防止死锁。
NSObject *obj = [NSObject new];
@synchronized(obj){
// 需要锁定的代码
}
2.自旋锁
加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能。
属性修饰atomic本身就有一把自旋锁。
属性修饰nonatomic 和 atomic
nonatomic 非原子属性(非线程安全),同一时间可以有很多线程读和写,效率高
atomic 原子属性(线程安全),保证同一时间只有一个线程能够写入
(但是同一个时间多个线程都可以取值),atomic 本身就有一把锁(自旋锁),需要消耗大量的资源
九.死锁问题
@synchronized产生死锁场景
/** A锁 */
static NSString* A = @"A";
/** B锁 */
static NSString* B = @"B";
dispatch_async(queue, ^{
// NSLog(@"%@",[self sourceOut]) ;
@synchronized(A){
NSLog(@"锁A0");
sleep(2);
@synchronized(B){
NSLog(@"锁B0");
}
}
});
dispatch_async(queue, ^{
@synchronized(B){
NSLog(@"锁B1");
@synchronized(A){
NSLog(@"锁A1");
}
}
});
打印:2018-04-06 15:35:56.206903+0800 COCOCOCO[13309:566143] 锁A0
2018-04-06 15:35:56.206939+0800 COCOCOCO[13309:566145] 锁B1
NSLock产生死锁场景
[self.lock lock];
//由于当前线程加锁,现在再次加同样的锁,需等待当前线程解锁,把当前线程挂起,不能解锁
[self.lock lock];
[_lock unlock];
[_lock unlock];
GCD产生死锁场景
参考文章:
GCD主要参考文章:https://www.jianshu.com/p/2d57c72016c6
其它内容:https://blog.csdn.net/hejiasu/article/details/82768913
网友评论