我正在参与创作者训练营第4期,点击了解活动详情,一同学习吧!
前语
最近在参与创作者训练营,听了1节课,收益良多,其间印象比较深入的一句话是10篇水文不如1篇高质量文章
。豁然大悟,往自己前面写的一些文章,的确是有点水。有时分忧虑写的过长,会看的累,有时分是自己没有那么多时刻,就分开成几部分写。
现在想想,我自己写文章终究想干啥,是为了加薪吗,是为了让咱们点赞有自豪感吗,好像是有点。总的来说无非便是想把自己学习了东西进行好好总结,让自己成长,当然过程中能帮到他人更好,总而言之不要违背初心就好。
NSThread
NSThread
是一个对pthread
目标化的封装,是苹果官方提供面向目标操作线程的技术,简略易用,能够直接操作线程目标,不过需求咱们自己办理线程的生命周期。
NSThread线程创立
咱们直接经过initWithBlock
进行初始化。其间[thread start]
便是发动线程。
- (void)nsthreadDemo
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
// 打印当时线程
NSLog(@"%@",[NSThread currentThread]);
}];
[thread start];
}
实际上除了这种,咱们还有其它初始化办法。
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
还有类办法也能够进行线程创立,并且不需求start
。
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
线程睡觉
sleepUntilDate
办法是指睡觉当某个date
。
+ (void)sleepUntilDate:(NSDate *)date;
sleepForTimeInterval
是指让线程睡觉几秒。
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
线程控制
线程发动,咱们用start
办法。
- (void)start;
线程撤销,咱们用cancel
办法。
- (void)cancel;
获取当时线程,咱们用currentThread
办法。
[NSThread currentThread]
主线程履行
咱们能够直接用NSObject的办法。用performSelectorOnMainThread
回到主线程。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
子线程履行,回到主线程改写UI
这儿咱们用个例子来运用NSThread
。
- (void)nsthreadDemo
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
// 打印当时线程
NSLog(@"%@",[NSThread currentThread]);
//让当时线程睡2秒
[NSThread sleepForTimeInterval:2.0];
// 回到主线程
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
}];
[thread start];
}
- (void)updateUI
{
NSLog(@"%@",[NSThread currentThread]);
self.view.backgroundColor = [UIColor systemRedColor];
}
NSThread
平常仍是很少用这个来操作线程的,那咱们就看下面的。
NSOperation
NSOperation
是基于 GCD
更高一层的封装,运用更加面向目标。比 GCD
多了一些更简略的功能。主动办理生命周期,这个我喜欢。那下面咱们就一同来看看吧。
NSOperation和NSOperationQueue运用
在 GCD 中,咱们创立一个行列,然后把使命增加到 Block 上。然后由行列进行调度使命。
既然是基于 GCD ,那 NSOperation 大体上逻辑也会和 GCD 相同。
其间 NSOperationQueue
是操作行列,NSOperation
是操作使命。
具体步骤如下:
- 创立操作:先将需求履行的操作封装到一个 NSOperation 目标中。
- 创立行列:创立 NSOperationQueue 目标。
- 将操作加入到行列中:将 NSOperation 目标增加到 NSOperationQueue 目标中。
- (void)operationdemo
{
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创立操作使命
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
//行列增加操作使命
[queue addOperation:operation];
}
行列控制
cancelAllOperations
:撤销当时行列的一切操作使命。
- (void)cancelAllOperations;
suspended
:行列挂起。
@property (getter=isSuspended) BOOL suspended;
mainQueue
:主行列。
[NSOperationQueue mainQueue]
currentQueue
:当时行列。
[NSOperationQueue currentQueue]
waitUntilAllOperationsAreFinished
:会等当时行列履行完一切使命,才会持续走,会堵塞当时线程。
- (void)waitUntilAllOperationsAreFinished;
NSOperationQueue控制并发数
这儿咱们就直接用addOperationWithBlock
来完成行列增加操作使命。
咱们设置最大并发为1,当时行列便是串行行列了。
- (void)operationdemo
{
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创立串行行列
queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < 5; i++) {
//行列增加操作使命
[queue addOperationWithBlock:^{
NSLog(@"%d: %@",i,[NSThread currentThread]);
}];
}
}
打印成果:
2022-02-17 16:17:38.379723+0800 多线程[80881:1209674] 0: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380011+0800 多线程[80881:1209674] 1: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380219+0800 多线程[80881:1209674] 2: <NSThread: 0x600003108380>{number = 6, name = (null)}
2022-02-17 16:17:38.380460+0800 多线程[80881:1209672] 3: <NSThread: 0x6000031023c0>{number = 3, name = (null)}
2022-02-17 16:17:38.380838+0800 多线程[80881:1209672] 4: <NSThread: 0x6000031023c0>{number = 3, name = (null)}
成果剖析:打印成果的确是按次序履行,也便是说操作使命是1个1个履行的。
咱们设置最大并发数大于1,当时行列便是并发行列了。
- (void)operationdemo
{
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创立并发行列
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
//行列增加操作使命
[queue addOperationWithBlock:^{
NSLog(@"%d: %@",i,[NSThread currentThread]);
}];
}
}
看下打印成果:
能够看到是履行次序是无序。也记住一点便是最大并发数不是敞开了多少条线程,敞开线程数量是由系统决定的,不需求咱们来办理。
操作依靠和优先级
使命之间能够相互依靠,没有依靠关系,那就看谁的优先级高,就先履行谁。
- (void)operationdemo1
{
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//创立并发行列
queue.maxConcurrentOperationCount = 2;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d: %@",1,[NSThread currentThread]);
}];
//设置一般优先级
[operation1 setQueuePriority:NSOperationQueuePriorityNormal];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d: %@",2,[NSThread currentThread]);
}];
//2依靠1
[operation2 addDependency:operation1];
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d: %@",3,[NSThread currentThread]);
}];
//设置高的优先级
[operation3 setQueuePriority:NSOperationQueuePriorityHigh];
NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%d: %@",4,[NSThread currentThread]);
}];
//4依靠3
[operation4 addDependency:operation3];
// 行列增加操作
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation4];
}
咱们先剖析一波:创立了1个行列,最大并发数是2,操作使命2依靠操作使命1,操作使命4依靠操作使命3。所以履行后,2是排在1后边,4是排在3后边。1的优先级是一般,3的优先级是高。所以3会先履行,1会在3后边。
咱们看下打印成果:
3 -> 1 -> 4 -> 2 ,是不是和咱们剖析的相同样啊。
子线程履行,回到主线程改写UI
最终,咱们也来下子线程履行使命,然后回到主线程履行代码。
- (void)operationdemo2
{
// 创立行列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 行列增加使命
[queue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
sleep(2);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
}];
}
看起来也还OK吧,那下面咱们再看下GCD吧。
GCD
GCD 是也是主动办理生命周期的,也是咱们日常处理线程用的最多的一个方式。 GCD 的核心便是行列+履行方式,首要创立一个行列,然后向行列中追加使命,系统会依据使命的类型履行使命。
在前面写的一篇 iOS多线程之一:进程,线程,行列的关系 ,就已经写了关于同步串行,同步并发,异步串行,异步并发了。所以这章节,我就写下GCD其它相关的内容。
子线程履行,回到主线程改写UI
看下面的代码,感觉和 NSOperation 的代码是差不多的。这个也是咱们常用的办法。
- (void)gcddemo1
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(2);
NSLog(@"%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@",[NSThread currentThread]);
});
});
}
这儿用的是异步并发履行使命,用的是大局行列,改成dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
并发行列也是能够的。
dispatch_once
咱们能够用dispatch_once
来创立1个单例。
@implementation GCDDemo
+ (instancetype)shareInstance {
static GCDDemo *demo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
demo = [GCDDemo new];
});
return demo;
}
@end
dispatch_once
下的代码,在整个程序运转过程中只履行一次。这个在多线程的时分也能够确保线程安全的。
dispatch_after
咱们能够运用dispatch_after
来履行推迟办法。
- (void)gcddemo2 {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2:%@",[NSThread currentThread]);
});
NSLog(@"1:%@",[NSThread currentThread]);
}
能够看出来,dispatch_after
是不会堵塞线程的,并且延时后,会回到主线程履行使命。
dispatch_barrier_async
dispatch_barrier_async
是一个栅门函数,这儿咱们解说的是异步栅门,所以并不会堵塞当时线程。那它有什么用呢?
咱们看下这个例子:
- (void)dispatch_barrier_async_request
{
dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3:%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"4:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"5:%@", [NSThread currentThread]);
});
NSLog(@"1:%@", [NSThread currentThread]);
}
既然是异步栅门函数,那也便是说需求等前面的行列使命履行完,再履行自己的,然后再履行后边的。咱们看下成果:
看成果,的确是没有堵塞主线程,并且dispatch_barrier_async
的确起到栅门作用。
除了这个用法,咱们还在多读单写
方面起到重要作用。咱们看下代码
// 界说一个并发行列
@property (nonatomic, strong) dispatch_queue_t concurrent_queue;
// 多个线程需求数据拜访
@property (nonatomic, strong) NSMutableDictionary *dataCenterDic;
先界说一个并发行列和一个字典,字典是可能多个线程需求数据拜访。
- (instancetype)init{
self = [super init];
if (self){
// 创立一个并发行列:
self.concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创立数据字典:
self.dataCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
先初始化,咱们直接增加一个读和写办法。
#pragma mark - 读数据
- (id)jj_objectForKey:(NSString *)key{
__block id obj;
// 同步读取指定数据:
dispatch_sync(self.concurrent_queue, ^{
obj = [self.dataCenterDic objectForKey:key];
});
return obj;
}
#pragma mark - 写数据
- (void)jj_setObject:(id)obj forKey:(NSString *)key{
// 异步栅门调用设置数据
dispatch_barrier_async(self.concurrent_queue, ^{
NSLog(@"写--%@",obj);
[self.dataCenterDic setObject:obj forKey:key];
});
}
咱们外部开端调用写读和写办法。
- (void)readWriteLock {
dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
[self.rwLock jj_setObject:[NSString stringWithFormat:@"jj---%d",i] forKey:@"key"];
});
}
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
NSLog(@"读1--%@",[self.rwLock jj_objectForKey:@"key"]);
});
}
}
咱们看下打印成果:
读和写的输出并没有问题,这个就相当于起到读写锁的作用,除了这个还能够用pthread_rwlock_rdlock
,这个咱们下一篇会详细说明。
dispatch_group
一般来说,行列组适用于在恳求几个异步使命,然后等使命履行完后,再到dispatch_group_notify
履行地点的使命。
- (void)dispatch_group_request
{
// 创立1个行列组
dispatch_group_t group = dispatch_group_create();
// 创立1个并发行列
dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
// 异步增加1个并发行列
dispatch_group_async(group, queue, ^{
// 推迟模仿
sleep(1);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
// 推迟模仿
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
});
// 上面的使命履行完后会来到这儿
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
NSLog(@"0--%@",[NSThread currentThread]);
}
咱们看下输出成果:
很明显能够看出来,并没有堵塞当时线程,并且是等前面2个使命履行结束后,再履行dispatch_group_notify
的使命。
很多人会想,我 dispatch_group_async
里边要是用的是第三方网络结构调取异步网络恳求,异步网络恳求是在其结构的并发行列中,这时分数据还没恳求返回,dispatch_group_async
就走完了,这时分就履行了dispatch_group_notify
,达不到想要的作用。
那咱们难道用信号量去控制吗,既然要用信号量的话,咱们干嘛还要用dispatch_group_t去做呢,这儿咱们直接用 dispatch_group_enter
和 dispatch_group_leave
就好了。
dispatch_group_enter
、 dispatch_group_leave
是等同于dispatch_group_async
这个作用的,但用法便是这么美妙。
- (void)dispatch_group_request1
{
// 创立1个行列组
dispatch_group_t group = dispatch_group_create();
// 创立1个并发行列
dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
// 进组
dispatch_group_enter(group);
//异步网络恳求
dispatch_async(queue, ^{
//模仿网络恳求
sleep(1);
NSLog(@"1--%@",[NSThread currentThread]);
//出组
dispatch_group_leave(group);
});
// 进组
dispatch_group_enter(group);
//异步网络恳求
dispatch_async(queue, ^{
//模仿网络恳求
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
//出组
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
NSLog(@"0--%@",[NSThread currentThread]);
}
咱们先看下打印成果:
看到作用了吗,dispatch_group_enter
和 dispatch_group_leave
有必要是成对出现的,一旦有进组了,dispatch_group_notify
就不会调用,直到dispatch_group_leave
调用后,才会调取。和信号量其实是差不多的作用的。
dispatch_semaphore
dispatch_semaphore
用来初始化信号量,经过信号量的值能够控制线程哪个履行,哪个需求等候。并且设置GCD的最大并发数。值为1的时分,还能达到同步锁的作用,这个下一篇文章会详细说明。
-
dispatch_semaphore_create
:创立一个Semaphore
并初始化信号的总量。 -
dispatch_semaphore_signal
:发送一个信号,让信号总量加 1。 -
dispatch_semaphore_wait
:如果信号量大于0,则正常履行,并且信号量会减 1 ;如果信号量为 0 ,则会一直等候,等接收到通知信号量大于 0 后才能够正常履行。如果等候的时分会起到堵塞当时线程的作用。
咱们看个例子,如何完成2个使命履行完后,才履行第三个使命。
- (void)dispatch_semaphore_t_request
{
//创立1个信号量,且信号量的值为0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// 并发行列
dispatch_queue_t queue = dispatch_queue_create("jj.com", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"1--%@",[NSThread currentThread]);
//信号值+1
dispatch_semaphore_signal(sema);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
//信号值+1
dispatch_semaphore_signal(sema);
});
dispatch_async(dispatch_get_main_queue(), ^{
// 等候2个
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(@"0--%@",[NSThread currentThread]);
});
}
咱们先看下打印成果:
的确是完成了2个使命履行完,才走后边的使命。
咱们简略的剖析一下:3个都是异步函数调用,所以3个都是并发进行的,使命1进去后,睡觉1s,使命2进去后,也睡觉1s,使命0进去后,按理其它2个都睡觉,它就直接走dispatch_semaphore_wait
了。
这时分,咱们创立的信号量的值为0,只能等候了。
等使命2睡醒了,履行了dispatch_semaphore_signal
,信号值为1了,所以第一个dispatch_semaphore_wait
就能够走了,且信号量的值减1。这时分又遇到第二个dispatch_semaphore_wait
,也只能等。直到使命1的履行dispatch_semaphore_signal
后,信号量加1了,能够持续履行使命0。
咱们不管使命1和使命2谁先履行完,咱们最终的使命都需求等候他们履行完才能够履行,由于dispatch_semaphore_wait
和dispatch_semaphore_signal
是一一对应的。
Dispatch_source
一般咱们用Dispatch_source
,用的做多便是计时器了。dispatch_source_t
的定时器不受RunLoop
影响,并且dispatch_source_t
是系统等级的源事情,精度很高,系统主动触发。
- (void)dispatch_source_request
{
if (_timer) {
//计时器在运转,不动
return;
}
// 创立定时器
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
// 设置定时器时刻
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 设置事情触发的回调
dispatch_source_set_event_handler(_timer, ^{
if (self.timeout <= 0) {
dispatch_source_cancel(self.timer);
self.timer = nil;
}else {
dispatch_async(dispatch_get_main_queue(), ^{
self.timeout--;
NSLog(@"计时--%ld", self.timeout);
});
}
});
// 开端履行
dispatch_resume(_timer);
// 暂停
// dispatch_suspend(timer);
}
总结
-
NSThread
:运用更加面向目标,简略易用,可直接操作线程目标,需求手动办理生命周期。 -
NSOperation
:基于 GCD 封装,运用更加面向目标,可操作依靠关系,优先级,以及最大并发数,主动办理生命周期。 -
GCD
:旨在替换 NSTread 等线程技术,可灵敏操作线程和行列,附有其它强大功能,主动办理生命周期。
参考资料
- iOS进阶之多线程–NSThread详解
- iOS多线程:『NSOperation、NSOperationQueue』详尽总结
- iOS多线程:『GCD』详尽总结