app开发者平台在数字化时代的重要性与发展趋势解析
747
2022-09-30
【iOS】多线程梳理
【iOS】多线程梳理
Pthreads(不常用)
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。
实践
用Pthreads创建一个线程去执行一个任务
- (void)touchesPthread { NSLog(@"====================== touchesPthread START ======================"); pthread_t thread = NULL; NSString *params = @"Hello World"; int result = pthread_create(&thread, NULL, threadTask, (__bridge void *)(params)); result == 0 ? NSLog(@"creat thread success") : NSLog(@"creat thread failure"); // 设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源 // 销毁延迟, 用来延迟使用(sleep) [NSThread sleepForTimeInterval:5]; pthread_detach(thread); NSLog(@"====================== touchesPthread END ======================");}void *threadTask(void *params) { NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params)); NSLog(@"threadTask end"); return NULL;}
销毁不延迟
2022-08-08 23:39:39.018287+0800 OneLiveIOS[2909:62998] ====================== touchesPthread START ======================2022-08-08 23:39:39.018549+0800 OneLiveIOS[2909:62998] creat thread success2022-08-08 23:39:39.018585+0800 OneLiveIOS[2909:62998] ====================== touchesPthread END ======================
销毁延迟
2022-08-08 23:43:35.245019+0800 OneLiveIOS[3035:67075] ====================== touchesPthread START ======================2022-08-08 23:43:35.245300+0800 OneLiveIOS[3035:67075] creat thread success2022-08-08 23:43:35.245434+0800 OneLiveIOS[3035:67099]
NSThread
NSThread的基本使用比较简单,可以动态创建初始化NSThread对象,对其进行设置然后启动;也可以通过NSThread的静态方法快速创建并启动新线程;此外NSObject基类对象还提供了隐式快速创建NSThread线程的performSelector系列类别扩展工具方法;NSThread还提供了一些静态工具接口来控制当前线程以及获取当前线程的一些信息。
系统API
@interface NSThread : NSObject // 当前线程 @property (class, readonly, strong) NSThread *currentThread; // 使用类方法创建线程执行任务 + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; // 判断当前是否为多线程 + (BOOL)isMultiThreaded; // 指定线程的线程参数,例如设置当前线程的断言处理器。 @property (readonly, retain) NSMutableDictionary *threadDictionary; // 当前线程暂停到某个时间 + (void)sleepUntilDate:(NSDate *)date; // 当前线程暂停一段时间 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 退出当前线程 + (void)exit; // 当前线程优先级 + (double)threadPriority; // 设置当前线程优先级 + (BOOL)setThreadPriority:(double)p; // 指定线程对象优先级 0.0~1.0,默认值为0.5 @property double threadPriority NS_AVAILABLE(10_6, 4_0); // 服务质量 @property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // 线程名称 @property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0); // 栈区大小 @property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0); // 是否为主线程 @property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0); // 获取主线程 @property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0); // 初始化 - (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER; // 实例方法初始化,需要再调用start方法 - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0); - (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); // 线程状态,正在执行 @property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0); // 线程状态,正在完成 @property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0); //线程状态,已经取消 @property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0); // 取消,仅仅改变线程状态,并不能像exist一样真正的终止线程 - (void)cancel NS_AVAILABLE(10_5, 2_0); // 开始 - (void)start NS_AVAILABLE(10_5, 2_0); // 线程需要执行的代码,一般写子类的时候会用到 - (void)main NS_AVAILABLE(10_5, 2_0); @end // NSObject的分类@interface NSObject (NSThreadPerformAdditions) // 隐式的创建并启动线程,并在指定的线程(主线程或子线程)上执行方法。 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray
实践
- (void)touchesNSthread { NSLog(@"====================== touchesNSthread START ======================"); NSLog(@"thread%@", [NSThread currentThread]); // NSThread静态工具方法 // 1 是否开启了多线程 BOOL isMultiThreaded = [NSThread isMultiThreaded]; NSLog(@"是否开启了多线程, isMultiThreaded: %d", isMultiThreaded); // 2 获取当前线程 NSThread *currentThread = [NSThread currentThread]; // 3 获取主线程 NSThread *mainThread = [NSThread mainThread]; //NSLog(@"main thread, name: %@", [mainThread currentThread]); // 4 睡眠当前线程 // 4.1 线程睡眠1s钟 [NSThread sleepForTimeInterval:1]; // 4.2 线程睡眠到指定时间,效果同上 [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; // 5 退出当前线程,注意不要在主线程调用,防止主线程被kill掉 //[NSThread exit]; NSLog(@"main thread"); // NSThread线程对象基本创建,target为入口函数所在的对象,selector为线程入口函数 // 1 线程实例对象创建与设置 NSThread *newThread= [[NSThread alloc] initWithTarget:self selector:@selector(NSthreadRun) object:nil]; // 设置线程优先级threadPriority(0~1.0),即将被抛弃,将使用qualityOfService代替 newThread.threadPriority = 1.0; newThread.qualityOfService = NSQualityOfServiceUserInteractive; // 开启线程 [newThread start]; // 2 静态方法快速创建并开启新线程 [NSThread detachNewThreadSelector:@selector(NSthreadRun) toTarget:self withObject:nil]; [NSThread detachNewThreadWithBlock:^{ NSLog(@"静态方法 block run..."); }]; // NSObejct基类隐式创建线程的一些静态工具方法 // 1 在当前线程上执行方法,延迟2s [self performSelector:@selector(NSthreadRun) withObject:nil afterDelay:2.0]; // 2 在指定线程上执行方法,不等待当前线程 [self performSelector:@selector(NSthreadRun) onThread:newThread withObject:nil waitUntilDone:NO]; // 3 后台异步执行函数 [self performSelectorInBackground:@selector(NSthreadRun) withObject:nil]; // 4 在主线程上执行函数 [self performSelectorOnMainThread:@selector(NSthreadRun) withObject:nil waitUntilDone:NO]; NSLog(@"====================== touchesNSthread END ======================");}- (void)NSthreadRun { NSLog(@"mxz NSthreadRun...");}
2022-08-08 23:54:25.307552+0800 OneLiveIOS[3323:75768] ====================== touchesNSthread START ======================2022-08-08 23:54:25.307916+0800 OneLiveIOS[3323:75768] thread
GCD
GCD,全名Grand Central Dispatch,大中枢派发,是基于C语言的一套多线程开发API,是目前苹果官方推荐的多线程开发方式。总体来说,他解决直接操作线程带来的难题,它自动帮你管理了线程的生命周期以及任务的执行规则。下面会频繁的说道一个词,那就是任务,说白了,任务其实就是你要执行的那段代码。
任务管理方式——队列
当要管理多个任务时,线程开发给带来了一定的技术难度,或者说不方便性,GCD给出了统一管理任务的方式,那就是队列。看一下iOS多线程操作中的队列:(⚠️不管是串行还是并行,队列都是按照FIFO的原则依次触发任务)
两个通用队列
串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。(等待完成)并行队列:可以开启多条线程并行执行任务(但不一定会开启新的线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了。(等待发生)
两个特殊队列
主队列:系统创建好的一个串行队列,牛逼之处在于它管理必须在主线程中执行的任务,属于有劳保的。全局队列:系统创建好的一个并行队列,使用起来与自己创建的并行队列无本质差别。
任务执行方式
说完队列,相应的,任务除了管理,还得执行。并且在GCD中并不能直接开辟线程执行任务,所以在任务加入队列之后,GCD给出了两种执行方式——同步执行(sync)和异步执行(async)。
同步执行:在当前线程执行任务,不会开辟新的线程。必须等到Block函数执行完毕后,dispatch函数才会返回。异步执行:可以在新的线程中执行任务,但不一定会开辟新的线程。dispatch函数会立即返回, 然后Block在后台异步执行。
任务队列组合方式
线程死锁
- (void)touchesDeadLock { NSLog(@"touchesDeadLock, 1========%@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); }); NSLog(@"touchesDeadLock, 3========%@",[NSThread currentThread]);}
分析一下为什么会死锁 先做一个定义:- (void)touchesDeadLock{} —> 任务A,GCD同步任务 —>任务B。 总而言之呢,大概是这样的,首先,任务A在主队列,并且已经开始执行,在主线程打印出touchesDeadLock, 1===... ...,然后这时任务B被加入到主队列中,并且同步执行,系统说,同步执行啊,那我不开新的线程了,任务B说我要等我里面的Block函数执行完成,要不我就不返回,但是主队列是串行的,得等A执行完才能轮到B,不能坏了规矩,同时,任务B作为任务A的内部函数,必须等任务B执行完函数返回才能执行下一个任务。那就造成了,任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不能让任务B在任务A未完成之前开始执行,所以任务A等着任务B完成,任务B等着任务A完成,等待,永久的等待,所以就死锁了。
不死锁
- (void)touchesNoDeadLock { NSLog(@"touchesNoDeadLock, 1========%@",[NSThread currentThread]); NSLog(@"touchesNoDeadLock, 2========%@",[NSThread currentThread]); NSLog(@"touchesNoDeadLock, 3========%@",[NSThread currentThread]);}
2022-08-09 00:08:31.004427+0800 OneLiveIOS[3562:83930] touchesNoDeadLock, 1========
其实这里有一个误区,那就是任务在主线程顺序执行就是主队列。其实一点关系都没有,如果当前在主线程,同步执行任务,不管在什么队列任务都是顺序执行。把所有任务都以异步执行的方式加入到主队列中,你会发现它们也是顺序执行的。
改一下
- (void)touchesDeadLock { NSLog(@"touchesDeadLock, 1========%@",[NSThread currentThread]); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); }); // dispatch_sync(dispatch_get_main_queue(), ^{ // NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); // }); NSLog(@"touchesDeadLock, 3========%@",[NSThread currentThread]);}
2022-08-09 00:10:08.977420+0800 OneLiveIOS[3598:85028] touchesDeadLock, 1========
发现正常执行了,并且是顺序执行的,和上诉情况一样,任务A在主队列中,但是任务B加入到了全局队列,这时候,任务A和任务B没有队列的约束,所以任务B就先执行,执行完毕之后函数返回,任务A接着执行。
再改一下
- (void)touchesDeadLock { NSLog(@"touchesDeadLock, 1========%@",[NSThread currentThread]); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); }); // dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); // }); // dispatch_sync(dispatch_get_main_queue(), ^{ // NSLog(@"touchesDeadLock, 2========%@",[NSThread currentThread]); // }); NSLog(@"touchesDeadLock, 3========%@",[NSThread currentThread]);}
2022-08-09 00:12:58.484627+0800 OneLiveIOS[3674:87913] touchesDeadLock, 1========
发现不是顺序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当任务A在执行时,任务B加入到了主队列,注意,是异步执行,所以dispatch函数不会等到Block执行完成才返回,dispatch函数返回后,那任务A可以继续执行,Block任务可以认为在下一帧顺序加入队列,并且默认无限下一帧执行。
队列与执行方式的搭配
//串行队列self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);//并行队列self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);
1. 串行队列 + 同步执行
- (void)queue_taskTest{ dispatch_sync(self.serialQueue, ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(self.serialQueue, ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(self.serialQueue, ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}- (void)queue_taskTest { dispatch_sync(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}
2022-08-09 10:58:52.928519+0800 OneLiveIOS[3431:55258] queue_taskTest, 1========
全部都在当前线程顺序执行,也就是说,同步执行不具备开辟新线程的能力。
2. 串行队列 + 异步执行
- (void)queue_taskTest { dispatch_async(self.serialQueue, ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(self.serialQueue, ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(self.serialQueue, ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}- (void)queue_taskTest_ { dispatch_async(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL), ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}
2022-08-09 11:01:10.928031+0800 OneLiveIOS[3512:57482] queue_taskTest, 2========
先打印了2,然后顺序在子线程中打印3,4,1。说明异步执行具有开辟新线程的能力,并且串行队列必须等到前一个任务执行完才能开始执行下一个任务,同时,异步执行会使内部函数率先返回,不会与正在执行的外部函数发生死锁。
并行队列 + 同步执行
- (void)queue_taskTest { dispatch_sync(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}- (void)queue_taskTest__ { dispatch_sync(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}
2022-08-09 11:03:19.053886+0800 OneLiveIOS[3570:59273] queue_taskTest, 1========
未开启新的线程执行任务,并且Block函数执行完成后dispatch函数才会返回,才能继续向下执行,所以看到的结果是顺序打印的。
4. 并行队列 + 异步执行
- (void)queue_taskTest { dispatch_async(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}- (void)queue_taskTest___ { dispatch_async(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT), ^{ NSLog(@"queue_taskTest, 3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"queue_taskTest, 4========%@",[NSThread currentThread]);}
2022-08-09 11:04:32.532527+0800 OneLiveIOS[3606:60355] queue_taskTest, 2========
开辟了多个线程,触发任务的时机是顺序的,但是看到完成任务的时间却是随机的,这取决于CPU对于不同线程的调度分配,但是,线程不是无条件无限开辟的,当任务量足够大时,线程是会重复利用的。
总结
对于单核CPU来说,不存在真正意义上的并行,所以,多线程执行任务,其实也只是一个人在干活,CPU的调度决定了非等待任务的执行速率,同时对于非等待任务,多线程并没有真正意义提高效率。线程可以简单的认为就是一段代码+运行时数据。同步执行会在当前线程执行任务,不具备开辟线程的能力或者说没有必要开辟新的线程。并且,同步执行必须等到Block函数执行完毕,dispatch函数才会返回,从而阻塞同一串行队列中外部方法的执行。异步执行dispatch函数会直接返回,Block函数可以认为它会在下一帧加入队列,并根据所在队列目前的任务情况无限下一帧执行,从而不会阻塞当前外部任务的执行。同时,只有异步执行才有开辟新线程的必要,但是异步执行不一定会开辟新线程。只要是队列,肯定是FIFO(先进先出),但是谁先执行完要看第1条。只要是串行队列,肯定要等上一个任务执行完成,才能开始下一个任务。但是并行队列当上一个任务开始执行后,下一个任务就可以开始执行。想要开辟新线程必须让任务在异步执行,想要开辟多个线程,只有让任务在并行队列中异步执行才可以。执行方式和队列类型多层组合在一定程度上能够实现对于代码执行顺序的调度。同步+串行:未开辟新线程,串行执行任务;同步+并行:未开辟新线程,串行执行任务;异步+串行:新开辟一条线程,串行执行任务;异步+并行:开辟多条新线程,并行执行任务;在主线程中同步使用主队列执行任务,会造成死锁。对于多核CPU来说,线程数量也不能无限开辟,线程的开辟同样会消耗资源,过多线程同时处理任务并不是想象中的人多力量大。
比喻
任务的管理方式——队列,串行队列和并行队列就像是人以什么规则打电话,排队一个等一个去,还是抢着去;
任务的执行方式——同步或异步执行,就像提供当前一个电话机,还是可以申请新的电话机。 而多线程的运作就等于是这些人去打电话。 同步执行的时候不能开辟新的线程,异步执行的时候可以开辟新的线程,但不一定开辟。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~