关于锁:iOS之多线程漫谈

51次阅读

共计 53729 个字符,预计需要花费 135 分钟才能阅读完成。

前言

  • 提到线程,那就不得不提 CPU,古代的 CPU 有一个很重要的个性,就是工夫片,每一个取得 CPU 的工作只能运行一个工夫片规定的工夫。
  • 其实线程对操作系统来说就是一段代码以及运行时数据。操作系统会为每个线程保留相干的数据,当接管到来自 CPU 的工夫片中断事件时,就会按肯定规定从这些线程中抉择一个,复原它的运行时数据,这样 CPU 就能够继续执行这个线程了。
  • 也就是其实就单核 CUP 而言,并没有方法实现真正意义上的并发执行,只是 CPU 疾速地在多条线程之间调度,CPU 调度线程的工夫足够快,就造成了多线程并发执行的假象。并且就单核 CPU 而言多线程能够解决线程阻塞的问题,然而其自身运行效率并没有进步,多 CPU 的并行运算才真正解决了运行效率问题。
  • 零碎中正在运行的每一个应用程序都是一个过程,每个过程零碎都会调配给它独立的内存运行。也就是说,在 iOS 零碎中中,每一个利用都是一个过程。
  • 一个过程的所有工作都在线程中进行,因而每个过程至多要有一个线程,也就是主线程。那多线程其实就是一个过程开启多条线程,让所有工作并发执行。
  • 多线程在肯定意义上实现了过程内的资源共享,以及效率的晋升。同时,在肯定水平上绝对独立,它是程序执行流的最小单元,是过程中的一个实体,是执行程序最根本的单元,有本人栈和寄存器。

明天要讲的内容

1、iOS 中的多线程
2、iOS 中的各种线程锁
3、你不得不知的 runloop

1.1 Pthreads

POSIX 线程(POSIX threads),简称 Pthreads,是线程的 POSIX 规范。该规范定义了创立和操纵线程的一整套 API。在类 Unix 操作系统(Unix、Linux、Mac OS X 等)中,都应用 Pthreads 作为操作系统的线程。

咱们来用 Pthreads 创立一个线程去执行一个工作:

记得引入头文件 `#import "pthread.h"`

-(void)pthreadsDoTask{
    /*
     pthread_t:线程指针
     pthread_attr_t:线程属性
     pthread_mutex_t:互斥对象
     pthread_mutexattr_t:互斥属性对象
     pthread_cond_t:条件变量
     pthread_condattr_t:条件属性对象
     pthread_key_t:线程数据键
     pthread_rwlock_t:读写锁
     //
     pthread_create():创立一个线程
     pthread_exit():终止以后线程
     pthread_cancel():中断另外一个线程的运行
     pthread_join():阻塞以后的线程,直到另外一个线程运行完结
     pthread_attr_init():初始化线程的属性
     pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否能够被联合)pthread_attr_getdetachstate():获取脱离状态的属性
     pthread_attr_destroy():删除线程的属性
     pthread_kill():向线程发送一个信号
     pthread_equal(): 对两个线程的线程标识号进行比拟
     pthread_detach(): 拆散线程
     pthread_self(): 查问线程本身线程标识号
     //
     * 创立线程
     int pthread_create(pthread_t _Nullable * _Nonnull __restrict, // 指向新建线程标识符的指针
     const pthread_attr_t * _Nullable __restrict,  // 设置线程属性。默认值 NULL。void * _Nullable (* _Nonnull)(void * _Nullable),  // 该线程运行函数的地址
     void * _Nullable __restrict);  // 运行函数所需的参数
     * 返回值:* 若线程创立胜利,则返回 0
     * 若线程创立失败,则返回出错编号
     */
    
    //
    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, 则该线程运行完结后会主动开释所有资源
    pthread_detach(thread);
}

void *threadTask(void *params) {NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params));
    return NULL;
}

输入后果:

ThreadDemo[1197:143578] creat thread success
ThreadDemo[1197:143649] <NSThread: 0x600000262e40>{number = 3, name = (null)} - Hello World

从打印后果来看,该工作是在新开拓的线程中执行的,然而感觉用起来超不敌对,很多货色须要本人治理,单单是工作队列以及线程生命周期的治理就够你头疼的,那你写出的代码还能是艺术么!其实之所以摈弃这套 API 很少用,是因为咱们有更好的抉择:NSThread

1.2 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<NSString *> *)array;
- (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 modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
@end

上面以在一个 UIViewController 中为例展现 NSThread 的应用办法:

- (void)viewDidLoad {[super viewDidLoad];

    /** NSThread 动态工具办法 **/
    /* 1 是否开启了多线程 */
    BOOL isMultiThreaded = [NSThread isMultiThreaded];
    /* 2 获取以后线程 */
    NSThread *currentThread = [NSThread currentThread];
    /* 3 获取主线程 */
    NSThread *mainThread = [NSThread mainThread];
    NSLog(@"main thread");
    /* 4 睡眠以后线程 */
    /* 4.1 线程睡眠 5s 钟 */
    [NSThread sleepForTimeInterval:5];
    /* 4.2 线程睡眠到指定工夫,成果同上 */
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    /* 5 退出以后线程,留神不要在主线程调用,避免主线程被 kill 掉 */
    //[NSThread exit];     NSLog(@"main thread");
    
    /** NSThread 线程对象根本创立,target 为入口函数所在的对象,selector 为线程入口函数 **/
    /* 1 线程实例对象创立与设置 */
    NSThread *newThread= [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    /* 设置线程优先级 threadPriority(0~1.0),行将被摈弃,将应用 qualityOfService 代替 */
    newThread.threadPriority = 1.0;
    newThread.qualityOfService = NSQualityOfServiceUserInteractive;
    /* 开启线程 */
    [newThread start];
    /* 2 静态方法疾速创立并开启新线程 */
    [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    [NSThread detachNewThreadWithBlock:^{NSLog(@"block run...");
    }];
    
    /** NSObejct 基类隐式创立线程的一些动态工具办法 **/
    /* 1 在以后线程上执行办法,提早 2s */
    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    /* 2 在指定线程上执行办法,不期待以后线程 */
    [self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
    /* 3 后盾异步执行函数 */
    [self performSelectorInBackground:@selector(run) withObject:nil];
    /* 4 在主线程上执行函数 */
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
}

- (void)run {NSLog(@"run...");
}

1.2.1 performSelecor 补充

根底用法

performSelecor 响应了 OC 语言的动态性: 提早到运行时才绑定办法。当咱们在应用以下办法时:

[obj performSelector:@selector(play)];
[obj performSelector:@selector(play:) withObject:@"张三"];
[obj performSelector:@selector(play:with:) withObject:@"张三" withObject:@"李四"];

编译阶段并不会去查看办法是否无效存在,只会给出正告
如果要执行的办法名也是动静不确定的一个参数
编译器也只会提醒说因为以后办法名未知可能会引起内存泄露相干问题:

PerformSelector may cause a leak because its selector is unknown
提早执行
[obj performSelector:@selector(play) withObject:@"张三" afterDelay:4.f];

该办法将提早 4 秒后再执行 play 办法。其实说到对工夫方面的解决在我的项目中常常用到的是 NSTimer:当一个 NSTimer 注册到 Runloop 后,Runloop 会反复的在相应的工夫点注册事件,当然 Runloop 为了节俭资源并不会在精确的工夫点触发事件。
而 performSelector:withObject:afterDelay: 其实就是在外部创立了一个 NSTimer,而后会增加到以后线程的 Runloop 中。所以当该办法增加到子线程中时,须要分外的留神两个中央:

① 在子线程中执行会不会调用 test 办法

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{[self performSelector:@selector(test) withObject:nil afterDelay:2];
});

会发现 test 办法并没有被调用,因为子线程中的 runloop 默认是没有启动的状态。应用 run 办法开启以后线程的 runloop,然而肯定要留神 run 办法和执行该提早办法的程序。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{[[NSRunLoop currentRunLoop] run];
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
});

会发现即使增加了 run 办法,然而 test 办法还是没有被调用,在最初打印以后线程的 runloop,会发现:

timers = <CFArray 0x6000002a8100 [0x109f67bb0]>{type = mutable-small, count = 1, values = (0 : <CFRunLoopTimer 0x6000001711c0 [0x109f67bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 544280547 (1.98647892 @ 3795501066754), callout = (Delayed Perform) lZLearningFromInterviewController test (0x105ea0d9c / 0x104b2e2c0) (), context = <CFRunLoopTimer context 0x600000470080>}

子线程的 runloop 中的确增加了一个 CFRunLoopTimer 的事件,然而到最初都不会被执行。
将 run 办法和 performSelector 提早办法调换程序后运行:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{[self performSelector:@selector(test) withObject:nil afterDelay:2];
        [[NSRunLoop currentRunLoop] run];
});

此时 test 办法会被调用,别离打印执行完 performSelecor 和 run 办法之后,发现在执行完 performSelector 办法后该 timer 事件会被增加到子线程的 runloop 中:

timers = <CFArray 0x6000000b3c80 [0x112956bb0]>{type = mutable-small, count = 1, values = (0 : <CFRunLoopTimer 0x60000016fc00 [0x112956bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 544280800 (1.98171604 @ 4048676578329), callout = (Delayed Perform) lZLearningFromInterviewController test (0x10e88fd9c / 0x1

然而当执行完 run 办法之后,runloop 中的 timer 事件曾经是执行完的状态:

timers = <CFArray 0x6000000b3c80 [0x112956bb0]>{type = mutable-small, count = 0, values = ()},

所以在子线程中两者的程序必须是先执行 performSelector 提早办法之后再执行 run 办法。因为 run 办法只是尝试想要开启以后线程中的 runloop,然而如果该线程中并没有任何事件 (source、timer、observer) 的话,并不会胜利的开启。
② test 办法中执行的线程

 [self performSelector:@selector(test) withObject:nil afterDelay:2];

如果在子线程中调用该 performSelector 提早办法,会发现调用该提早办法的子线程和 test 办法中执行的子线程是同一个,也就是说:

对于该 performSelector 提早办法而言,如果在主线程中调用,那么 test 办法也是在主线程中执行;如果是在子线程中调用,那么 test 也会在该子线程中执行。

在答复完提早办法之后,会将该办法和 performSelector:withObject: 作比照,那么 performSelector:withObject: 在不增加到子线程的 Runloop 中时是否能执行?
我过后想的是,performSelector:withObject: 办法和提早办法相似,只不过是马上执行而已,所以也须要增加到子线程的 RunLoop 中。

这么想是错的,performSelector:withObject: 只是一个单纯的音讯发送,和工夫没有一点关系。所以不须要增加到子线程的 Runloop 中也能执行。

所以能间接应用 NSThread 的三个办法:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{NSLog(@"block 中的线程 ---- %@",[NSThread currentThread]);
}];
performSelector 如何进行多值传输?

问题一听马上就能答复应用 NSArray 或者 NSDictionary 或者自定义 Model 的模式,然而我查到了一个很妙的办法:
因为在 OC 中调用一个办法实际上就是发送音讯 objc_msgSend:

{NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"谢华华",@"亚呼呼"];

    SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];

    ((void(*)(id,SEL,NSNumber*,NSString*,NSString*,NSArray*)) objc_msgSend)(self,selector,age,name,gender,friends);

}

- (void)getAge:(NSNumber *)age name:(NSString *)name gender:(NSString *)gender friends:(NSArray *)friends
{NSLog(@"%d----%@---%@---%@",[age intValue],name,gender,friends[0]);
}

导入 #import <objc/message.h> 即可。然而这种形式并不是 oc 封装的办法所以应用非常的不不便。
第二种办法其实也是以 NSArray 的模式传值,而后创立 NSInvocation 的形式,将参数一一绑定。

-(id)performSelector:(SEL)aSelector withObject:(NSArray *)object
{
    // 取得办法签名
    NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];
    
    if (signature == nil) {return nil;}
    
    // 应用 NSInvocation 进行参数的封装
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
    invocation.target = self;
    invocation.selector = aSelector;
    
    // 减去 self _cmd
    NSInteger paramtersCount = signature.numberOfArguments - 2;
    paramtersCount = MIN(object.count, paramtersCount); 
    
    for (int i = 0; i < paramtersCount; i++) {id obj = object[i];
        
        if ([obj isKindOfClass:[NSNull class]]) continue;
        [invocation setArgument:&obj atIndex:i+2];
    }
    
    [invocation invoke];
    
    id returnValue = nil;
    if (signature.methodReturnLength > 0) { // 如果有返回值的话,才须要去取得返回值
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
    
}

    NSNumber *age = [NSNumber numberWithInt:20];
    NSString *name = @"李周";
    NSString *gender = @"女";
    NSArray *friends = @[@"谢华华",@"亚呼呼"];
 SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:");
    NSArray *array = @[age,name,gender,friends];
    
    [self performSelector:selector withObject:array];

1.3 GCD

GCD,全名 Grand Central Dispatch,大中枢派发,是基于 C 语言的一套多线程开发 API,是目前苹果官网举荐的多线程开发方式。总体来说,他解决我提到的下面间接操作线程带来的难题,它主动帮你治理了线程的生命周期以及工作的执行规定。上面咱们会频繁的说道一个词,那就是 工作 ,说白了, 工作 其实就是 你要执行的那段代码

工作治理形式——队列

当咱们要治理多个工作时,线程开发给咱们带来了肯定的技术难度,或者说不方便性,GCD 给出了咱们对立治理工作的形式,那就是队列。咱们来看一下 iOS 多线程操作中的队列:(⚠️不论是串行还是并行,队列都是依照 FIFO 的准则顺次触发工作)

两个通用队列:
  • 串行队列:所有工作会在一条线程中执行(有可能是以后线程也有可能是新开拓的线程),并且一个工作执行结束后,才开始执行下一个工作。(期待实现)
  • 并行队列:能够开启多条线程并行执行工作(但不肯定会开启新的线程),并且当一个工作放到指定线程开始执行时,下一个工作就能够开始执行了。(期待产生)
两个非凡队列:
  • 主队列:零碎为咱们创立好的一个串行队列,牛逼之处在于它治理必须在主线程中执行的工作,属于有劳保的。
  • 全局队列:零碎为咱们创立好的一个并行队列,应用起来与咱们本人创立的并行队列无实质差异。

工作执行形式

说完队列,相应的,工作除了治理,还得执行。并且在 GCD 中并不能间接开拓线程执行工作,所以在工作退出队列之后,GCD 给出了两种执行形式——同步执行(sync)和异步执行(async)。

  • 同步执行:在以后线程执行工作,不会开拓新的线程。必须等到 Block 函数执行结束后,dispatch 函数才会返回。
  • 异步执行:能够在新的线程中执行工作,但不肯定会开拓新的线程。dispatch 函数会立刻返回, 而后 Block 在后盾异步执行。

工作队列组合形式

1. 线程死锁

- (void)viewDidLoad {[super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[5615:874679] 1========<NSThread: 0x608000072440>{number = 1, name = main}

咱们还是得剖析一下为什么会死锁:
咱们先做一个定义:- (void)viewDidLoad{} —> 工作 A,GCD 同步工作 —> 工作 B。
总而言之呢,大略是这样的,首先,工作 A 在主队列,并且曾经开始执行,在主线程打印出1===... ...,而后这时工作 B 被退出到主队列中,并且同步执行,这尼玛事都大了,零碎说,同步执行啊,那我不开新的线程了,工作 B 说我要等我外面的 Block 函数执行实现,要不我就不返回,然而主队列说了,玩蛋去,我是串行的,你得等 A 执行完能力轮到你,不能坏了规矩,同时,工作 B 作为工作 A 的外部函数,必须等工作 B 执行完函数返回能力执行下一个工作。那就造成了,工作 A 期待工作 B 实现能力继续执行,但作为串行队列的主队列又不能让工作 B 在工作 A 未实现之前开始执行,所以工作 A 等着工作 B 实现,工作 B 等着工作 A 实现,期待,永恒的期待。所以就死锁了。

2. 这样不死锁

不如就写个最简略的:

- (void)viewDidLoad {[super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    NSLog(@"2========%@",[NSThread currentThread]);
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[5803:939324] 1========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 2========<NSThread: 0x600000078340>{number = 1, name = main}
ThreadDemo[5803:939324] 3========<NSThread: 0x600000078340>{number = 1, name = main}

其实这里有一个误区,那就是工作在主线程程序执行就是主队列。其实一点关系都没有,如果以后在主线程,同步执行工作,不论在什么队列工作都是程序执行。把所有工作都以异步执行的形式退出到主队列中,你会发现它们也是程序执行的。

3. 咱们改一下

- (void)viewDidLoad {[super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[5830:947858] 1========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 2========<NSThread: 0x60000007bb80>{number = 1, name = main}
ThreadDemo[5830:947858] 3========<NSThread: 0x60000007bb80>{number = 1, name = main}

你发现失常执行了,并且是程序执行的,和上诉状况一样,工作 A 在主队列中,然而工作 B 退出到了全局队列,这时候,工作 A 和工作 B 没有队列的束缚,所以工作 B 就先执行,执行结束之后函数返回,工作 A 接着执行。

4. 咱们再改一下

- (void)viewDidLoad {[super viewDidLoad];
    NSLog(@"1========%@",[NSThread currentThread]);
    dispatch_async(dispatch_get_main_queue(), ^{NSLog(@"2========%@",[NSThread currentThread]);
    });
    NSLog(@"3========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[5911:962470] 1========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 3========<NSThread: 0x600000072700>{number = 1, name = main}
ThreadDemo[5911:962470] 2========<NSThread: 0x600000072700>{number = 1, name = main}

发现不是程序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当工作 A 在执行时,工作 B 退出到了主队列,留神,是异步执行,所以 dispatch 函数不会等到 Block 执行实现才返回,dispatch 函数返回后,那工作 A 能够继续执行,Block 工作咱们能够认为在下一帧程序退出队列,并且默认有限下一帧执行。这就是为什么你看到 2===... ... 是最初输入的了。(⚠️一个函数的有多个外部函数异步执行时,不会造成死锁的同时,工作 A 执行结束后,这些异步执行的外部函数会程序执行)。

队列与执行形式的搭配

// 串行队列
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(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.serialQueue, ^{NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.serialQueue, ^{NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[6735:1064390] 1========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 2========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 3========<NSThread: 0x600000073cc0>{number = 1, name = main}
ThreadDemo[6735:1064390] 4========<NSThread: 0x600000073cc0>{number = 1, name = main}

全副都在以后线程程序执行,也就是说,同步执行不具备开拓新线程的能力。

2. 串行队列 + 异步执行
-(void)queue_taskTest{
    dispatch_async(self.serialQueue, ^{NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.serialQueue, ^{NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.serialQueue, ^{NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[6774:1073235] 4========<NSThread: 0x60800006e9c0>{number = 1, name = main}
ThreadDemo[6774:1073290] 1========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 2========<NSThread: 0x608000077000>{number = 3, name = (null)}
ThreadDemo[6774:1073290] 3========<NSThread: 0x608000077000>{number = 3, name = (null)}

先打印了 4,而后程序在子线程中打印 1,2,3。阐明异步执行具备开拓新线程的能力,并且串行队列必须等到前一个工作执行完能力开始执行下一个工作,同时,异步执行会使外部函数率先返回,不会与正在执行的内部函数产生死锁。

3. 并行队列 + 同步执行
-(void)queue_taskTest{
    dispatch_sync(self.concurrentQueue, ^{NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_sync(self.concurrentQueue, ^{NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_sync(self.concurrentQueue, ^{NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

运行后果:

ThreadDemo[7012:1113594] 1========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 2========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 3========<NSThread: 0x60800007e340>{number = 1, name = main}
ThreadDemo[7012:1113594] 4========<NSThread: 0x60800007e340>{number = 1, name = main}

未开启新的线程执行工作,并且 Block 函数执行实现后 dispatch 函数才会返回,能力持续向下执行,所以咱们看到的后果是程序打印的。

4. 并行队列 + 异步执行
-(void)queue_taskTest{
    dispatch_async(self.concurrentQueue, ^{NSLog(@"1========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:1];
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"2========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:2];
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"3========%@",[NSThread currentThread]);
        //[self nslogCount:10000 number:3];
    });
    NSLog(@"4========%@",[NSThread currentThread]);
}

打印后果:

ThreadDemo[7042:1117492] 1========<NSThread: 0x600000071900>{number = 3, name = (null)}
ThreadDemo[7042:1117491] 3========<NSThread: 0x608000070240>{number = 5, name = (null)}
ThreadDemo[7042:1117451] 4========<NSThread: 0x600000067400>{number = 1, name = main}
ThreadDemo[7042:1117494] 2========<NSThread: 0x600000071880>{number = 4, name = (null)}

开拓了多个线程,触发工作的机会是程序的,然而咱们看到实现工作的工夫却是随机的,这取决于 CPU 对于不同线程的调度调配,然而,线程不是无条件有限开拓的,当任务量足够大时,线程是会反复利用的。

总结一下

1. 对于单核 CPU 来说,不存在真正意义上的并行,所以,多线程执行工作,其实也只是一个人在干活,CPU 的调度决定了非期待工作的执行速率,同时对于非期待工作,多线程并没有真正意义提高效率。
2. 线程能够简略的认为就是一段代码 + 运行时数据。
3. 同步执行会在以后线程执行工作,不具备开拓线程的能力或者说没有必要开拓新的线程。并且,同步执行必须等到 Block 函数执行结束,dispatch 函数才会返回,从而阻塞同一串行队列中内部办法的执行。
4. 异步执行 dispatch 函数会间接返回,Block 函数咱们能够认为它会在下一帧退出队列,并依据所在队列目前的工作状况有限下一帧执行,从而不会阻塞以后内部工作的执行。同时,只有异步执行才有开拓新线程的必要,然而异步执行不肯定会开拓新线程。
5. 只有是队列,必定是 FIFO(先进先出),然而谁先执行完要看第 1 条。
6. 只有是串行队列,必定要等上一个工作执行实现,能力开始下一个工作。然而并行队列当上一个工作开始执行后,下一个工作就能够开始执行。
7. 想要开拓新线程必须让工作在异步执行,想要开拓多个线程,只有让工作在并行队列中异步执行才能够。执行形式和队列类型多层组合在肯定水平上可能实现对于代码执行程序的调度。
8. 同步 + 串行:未开拓新线程,串行执行工作;同步 + 并行:未开拓新线程,串行执行工作;异步 + 串行:新开拓一条线程,串行执行工作;异步 + 并行:开拓多条新线程,并行执行工作;在主线程中同步应用主队列执行工作,会造成死锁。
9. 对于多核 CPU 来说,线程数量也不能有限开拓,线程的开拓同样会耗费资源,过多线程同时解决工作并不是设想中的人多力量大。

比喻:
工作的治理形式——队列,串行队列和并行队列就像是人以什么规定打电话,排队一个等一个去,还是抢着去;
工作的执行形式——同步或异步执行,就像提供以后一个电话机,还是能够申请新的电话机。
而多线程的运作就等于是这些人去打电话。
同步执行的时候不能开拓新的线程,异步执行的时候能够开拓新的线程,但不肯定开拓。

GCD 其余函数用法

1. dispatch_after

该函数用于工作延时执行,其中参数 dispatch_time_t 代表延时时长,dispatch_queue_t代表应用哪个队列。如果队列未主队列,那么工作在主线程执行,如果队列为全局队列或者本人创立的队列,那么工作在子线程执行,代码如下:

-(void)GCDDelay{
    // 主队列延时
    dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(when_main, dispatch_get_main_queue(), ^{NSLog(@"main_%@",[NSThread currentThread]);
    });
    // 全局队列延时
    dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
    dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"global_%@",[NSThread currentThread]);
    });
    // 自定义队列延时
    dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
    dispatch_after(when_custom, self.serialQueue, ^{NSLog(@"custom_%@",[NSThread currentThread]);
    });
}

打印后果:

ThreadDemo[1508:499647] main_<NSThread: 0x60000007cf40>{number = 1, name = main}
ThreadDemo[1508:499697] global_<NSThread: 0x608000262d80>{number = 3, name = (null)}
ThreadDemo[1508:499697] custom_<NSThread: 0x608000262d80>{number = 3, name = (null)}
2. dispatch_once

保障函数在整个生命周期内只会执行一次,看代码。

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{NSLog(@"%@",[NSThread currentThread]);
    });
}

打印后果:

ThreadDemo[1524:509261] <NSThread: 0x600000262940>{number = 1, name = main}
无论你怎么疯狂的点击,在第一次打印之后,输入台便岿然不动。
3. dispatch_group_async & dispatch_group_notify
-(void)GCDGroup{
    //
    [self jointImageView];
    //
    dispatch_group_t group = dispatch_group_create();
    __block UIImage *image_1 = nil;
    __block UIImage *image_2 = nil;
    // 在 group 中增加一个工作
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{image_1 = [self imageWithPath:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg"];
        
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"];
    });
    //group 中所有工作执行结束,告诉该办法执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        self.imageView_1.image = image_1;
        self.imageView_2.image = image_2;
        //
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f);
        [image_2 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image_1 drawInRect:CGRectMake(100, 0, 100, 100)];
        UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext();
        self.imageView_3.image = image_3;
        UIGraphicsEndImageContext();});
}

-(void)jointImageView{self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)];
    [self.view addSubview:_imageView_1];
    
    self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)];
    [self.view addSubview:_imageView_2];
    
    self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)];
    [self.view addSubview:_imageView_3];
    
    self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor;
    self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;
}
4. dispatch_barrier_async

栅栏函数,应用此办法创立的工作,会查找以后队列中有没有其余工作要执行,如果有,则期待已有工作执行结束后再执行,同时,在此工作之后进入队列的工作,须要期待此工作执行实现后,能力执行。看代码(⚠️ 这里并发队列必须是本人创立的。如果抉择全局队列,这个函数和 dispatch_async 将会没有差异。)

-(void)GCDbarrier{
    
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 1");
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 2");
    });
    
//    dispatch_barrier_async(self.concurrentQueue, ^{//        NSLog(@"工作 barrier");
//    });
    
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 4");
    });
}

运行后果:

ThreadDemo[1816:673351] 工作 3
ThreadDemo[1816:673353] 工作 1
ThreadDemo[1816:673350] 工作 2
ThreadDemo[1816:673370] 工作 4 

上面咱们关上第一句正文:

-(void)GCDbarrier{
    
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 1");
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 2");
    });
    
    dispatch_barrier_async(self.concurrentQueue, ^{NSLog(@"工作 barrier");
    });
    
//    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 3");
    });
//    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 4");
    });
}

打印后果:

ThreadDemo[1833:678739] 工作 2
ThreadDemo[1833:678740] 工作 1
ThreadDemo[1833:678740] 工作 barrier
ThreadDemo[1833:678740] 工作 3
ThreadDemo[1833:678739] 工作 4 

再关上第二个和第三个正文,如下:

-(void)GCDbarrier{
    
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 1");
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 2");
    });
    
    dispatch_barrier_async(self.concurrentQueue, ^{NSLog(@"工作 barrier");
    });
    
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 4");
    });
}

运行后果:

ThreadDemo[1853:692434] 工作 1
ThreadDemo[1853:692421] 工作 2
ThreadDemo[1853:692387] big
ThreadDemo[1853:692421] 工作 barrier
ThreadDemo[1853:692387] apple
ThreadDemo[1853:692421] 工作 3
ThreadDemo[1853:692434] 工作 4 

咱们换一下函数:

-(void)GCDbarrier{
    
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 1");
    });
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 2");
    });
    
    dispatch_barrier_sync(self.concurrentQueue, ^{NSLog(@"工作 barrier");
    });
    
    NSLog(@"big");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 3");
    });
    NSLog(@"apple");
    dispatch_async(self.concurrentQueue, ^{NSLog(@"工作 4");
    });
}

打印后果:

ThreadDemo[1874:711841] 工作 1
ThreadDemo[1874:711828] 工作 2
ThreadDemo[1874:711793] 工作 barrier
ThreadDemo[1874:711793] big
ThreadDemo[1874:711793] apple
ThreadDemo[1874:711828] 工作 3
ThreadDemo[1874:711841] 工作 4 

发现了吗?这两个函数对于队列的栅栏作用是一样的,然而对于该函数绝对于其余外部函数遵循了最开始说到的同步和异步的规定。

5. dispatch_apply

该函数用于反复执行某个工作,如果工作队列是并行队列,反复执行的工作会并发执行,如果工作队列为串行队列,则工作会程序执行,须要留神的是,该函数为同步函数,要避免线程阻塞和死锁哦。

串行队列:

-(void)GCDApply{
    // 反复执行
    dispatch_apply(5, self.serialQueue, ^(size_t i) {NSLog(@"第 %@次_%@",@(i),[NSThread currentThread]);
    });
}

运行后果:

ThreadDemo[1446:158101] 第 0 次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第 1 次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第 2 次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第 3 次_<NSThread: 0x600000079ac0>{number = 1, name = main}
ThreadDemo[1446:158101] 第 4 次_<NSThread: 0x600000079ac0>{number = 1, name = main}

并行队列:

-(void)GCDApply{
    // 反复执行
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {NSLog(@"第 %@次_%@",@(i),[NSThread currentThread]);
    });
}

运行后果:

ThreadDemo[1461:160567] 第 2 次_<NSThread: 0x608000076000>{number = 4, name = (null)}
ThreadDemo[1461:160534] 第 0 次_<NSThread: 0x60800006d8c0>{number = 1, name = main}
ThreadDemo[1461:160566] 第 3 次_<NSThread: 0x60000007d480>{number = 5, name = (null)}
ThreadDemo[1461:160569] 第 1 次_<NSThread: 0x60000007d440>{number = 3, name = (null)}
ThreadDemo[1461:160567] 第 4 次_<NSThread: 0x608000076000>{number = 4, name = (null)}

死锁:

-(void)GCDApply{
    // 反复执行
    dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) {NSLog(@"第 %@次_%@",@(i),[NSThread currentThread]);
    });
}

运行后果:

6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait

看这几个函数的时候你须要抛开队列,丢掉同步异步,不要把它们想到一起,一概而论,信号量只是管制工作执行的一个条件而已,绝对于下面通过队列以及执行形式来控制线程的开拓和工作的执行,它更贴近对于工作间接的管制。相似于单个队列的最大并发数的管制机制,进步并行效率的同时,也避免太多线程的开拓对 CPU 早层负面的效率累赘。
dispatch_semaphore_create创立信号量,初始值不能小于 0;
dispatch_semaphore_wait期待升高信号量,也就是信号量 -1;
dispatch_semaphore_signal进步信号量,也就是信号量 +1;
dispatch_semaphore_waitdispatch_semaphore_signal 通常配对应用。

-(void)GCDSemaphore{
    //
    //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {//dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{NSLog(@"第 %@次_%@",@(i),[NSThread currentThread]);
            //dispatch_semaphore_signal(semaphore);
        });
    });
}
ThreadDemo[1970:506692] 第 0 次_<NSThread: 0x600000070f00>{number = 3, name = (null)}
ThreadDemo[1970:506711] 第 1 次_<NSThread: 0x6000000711c0>{number = 4, name = (null)}
ThreadDemo[1970:506713] 第 2 次_<NSThread: 0x6000000713c0>{number = 5, name = (null)}
ThreadDemo[1970:506691] 第 3 次_<NSThread: 0x600000070f40>{number = 6, name = (null)}
ThreadDemo[1970:506694] 第 4 次_<NSThread: 0x600000070440>{number = 7, name = (null)}
-(void)GCDSemaphore{
    //
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    dispatch_apply(5, self.concurrentQueue, ^(size_t i) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_async(self.concurrentQueue, ^{NSLog(@"第 %@次_%@",@(i),[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
    });
}
ThreadDemo[2020:513651] 第 0 次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第 1 次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第 2 次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第 3 次_<NSThread: 0x608000073900>{number = 3, name = (null)}
ThreadDemo[2020:513651] 第 4 次_<NSThread: 0x608000073900>{number = 3, name = (null)}

信号量是管制工作执行的重要条件,当信号量为 0 时,所有工作期待,信号量越大,容许可并行执行的工作数量越多。

1.4 NSOperation

NSOperation以及 NSOperationQueue 是苹果对于 GCD 的封装,其中 NSOperation 其实就是咱们下面所说的工作,然而这个类不能间接应用,咱们要用他的两个子类,NSBlockOperationNSInvocationOperation,而NSOperationQueue 呢,其实就是相似于 GCD 中的队列,用于治理你退出到其中的工作。

NSOperation 提供了对于工作的执行,勾销,以及随时获取工作的状态,增加工作依赖以及优先级等办法和属性,绝对于 GCD 提供的办法来说,更直观,更不便,并且提供了更多的管制接口。(很多时候,苹果设计的架构是很棒的,不要只是在乎他实现了什么,可能你学到的货色会更多),有几个办法和属性咱们理解一下

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}

- (void)start;// 启动工作 默认在以后线程执行
- (void)main;// 自定义 NSOperation,写一个子类,重写这个办法,在这个办法外面增加须要执行的操作。@property (readonly, getter=isCancelled) BOOL cancelled;// 是否曾经勾销,只读
- (void)cancel;// 勾销工作

@property (readonly, getter=isExecuting) BOOL executing;// 正在执行,只读
@property (readonly, getter=isFinished) BOOL finished;// 执行完结,只读
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);// 是否并发,只读
@property (readonly, getter=isReady) BOOL ready;// 筹备执行

- (void)addDependency:(NSOperation *)op;// 增加依赖
- (void)removeDependency:(NSOperation *)op;// 移除依赖

@property (readonly, copy) NSArray<NSOperation *> *dependencies;// 所有依赖关系,只读

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};// 零碎提供的优先级关系枚举

@property NSOperationQueuePriority queuePriority;// 执行优先级

@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);// 工作执行实现之后的回调

- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);// 阻塞以后线程,等到某个 operation 执行结束。@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);// 已废除,用 qualityOfService 代替。@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);// 服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的实现操作。@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);// 工作名称

@end

然而 NSOperation 自身是个抽象类,不能间接应用,咱们有三种形式赋予它新的生命,就是上面这三个货色。

NSOperation 自定义子类

这是我要说的第一个工作类型,咱们能够自定义继承于 NSOperation 的子类,并重写父类提供的办法,实现一波具备非凡意义的工作。比方咱们去下载一个图片:

.h
#import <UIKit/UIKit.h>

@protocol QPImageDownLoadOperationDelegate <NSObject>
-(void)QPImageDownLoadFinished:(UIImage*)image;

@end

@interface QPImageDownLoadOperation : NSOperation

-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<QPImageDownLoadOperationDelegate>)delegate;

@end

.m
#import "QPImageDownLoadOperation.h"

@implementation QPImageDownLoadOperation{
    NSURL *_imageUrl;
    id _delegate;
}

-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<QPImageDownLoadOperationDelegate>)delegate{if (self == [super init]) {
        _imageUrl = imageUrl;
        _delegate = delegate;
    }
    return self;
}

-(void)main{
    @autoreleasepool {UIImage *image = [self imageWithUrl:_imageUrl];
        if (_delegate && [_delegate respondsToSelector:@selector(QPImageDownLoadFinished:)]) {[_delegate QPImageDownLoadFinished:image];
        }
    }
}

-(UIImage*)imageWithUrl:(NSURL*)url{NSData *imageData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:imageData];
    return image;
}


@end

而后调用:-(void)QPDownLoadImageOperationRun{QPImageDownLoadOperation *ysOper = [[QPImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self];
    [ysOper start];
}

-(void)QPImageDownLoadFinished:(UIImage *)image{NSLog(@"%@",image);
}

运行打印后果:

ThreadDemo[4141:1100329] <UIImage: 0x60800009f630>, {700, 1050}

NSBlockOperation

第二个,就是零碎提供的 NSOperation 的子类NSBlockOperation,咱们看一下他提供的 API:

@interface NSBlockOperation : NSOperation {
@private
    id _private2;
    void *_reserved2;
}

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

@end

很简略,就这几个,咱们就用它实现一个工作:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]);
    }];
    [blockOper start];
}

运行后果:

ThreadDemo[4313:1121900] NSBlockOperationRun_<NSOperationQueue: 0x608000037420>{name = 'NSOperationQueue Main Queue'}_<NSThread: 0x60000006dd80>{number = 1, name = main}

咱们发现这个工作是在以后线程程序执行的,咱们发现还有一个办法 addExecutionBlock: 试一下:

-(void)NSBlockOperationRun{
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]);
    }];
    [blockOper addExecutionBlock:^{NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]);
    }];
    [blockOper start];
}

打印后果:

ThreadDemo[4516:1169835] NSBlockOperationRun_1_<NSThread: 0x60000006d880>{number = 1, name = main}
ThreadDemo[4516:1169875] NSBlockOperationRun_3_<NSThread: 0x600000070800>{number = 4, name = (null)}
ThreadDemo[4516:1169877] NSBlockOperationRun_4_<NSThread: 0x6080000762c0>{number = 5, name = (null)}
ThreadDemo[4516:1169893] NSBlockOperationRun_2_<NSThread: 0x608000076100>{number = 3, name = (null)}

从打印后果来看,这个 4 个工作是异步并发执行的,开拓了多条线程。

NSInvocationOperation

第三个,就是它了,同样也是零碎提供给咱们的一个工作类,基于一个 target 对象以及一个 selector 来创立工作,具体代码:

-(void)NSInvocationOperationRun{NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [invocationOper start];
}
-(void)invocationOperSel{NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);
}

运行后果:

ThreadDemo[4538:1173118] NSInvocationOperationRun_<NSThread: 0x60800006e900>{number = 1, name = main}

运行后果与 NSBlockOperation 单个 block 函数的执行形式雷同,同步程序执行。确实零碎的封装给予咱们对于工作更直观的货色,然而对于多个工作的管制机制并不欠缺。

NSOperationQueue

下面说道咱们创立的 NSOperation 工作对象能够通过 start 办法来执行,同样咱们能够把这个工作对象增加到一个 NSOperationQueue 对象中去执行,先看一下零碎的 API:

@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}

- (void)addOperation:(NSOperation *)op;// 增加工作
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);// 增加一组工作

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);// 增加一个 block 模式的工作

@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;// 队列中所有的工作数组
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);// 队列中的工作数

@property NSInteger maxConcurrentOperationCount;// 最大并发数

@property (getter=isSuspended) BOOL suspended;// 暂停

@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);// 名称

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);// 服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的实现操作。@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);

- (void)cancelAllOperations;// 勾销队列中的所有工作

- (void)waitUntilAllOperationsAreFinished;// 阻塞以后线程,等到队列中的工作全副执行结束。#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);// 获取以后队列
@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);// 获取主队列
#endif

@end
-(void)NSOperationQueueRun{NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil];
    [queue addOperation:invocationOper];
    NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]);
    }];
    [queue addOperation:blockOper];
    [queue addOperationWithBlock:^{NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]);
    }];
}

打印后果:

ThreadDemo[4761:1205689] NSBlockOperationRun_<NSThread: 0x600000264480>{number = 4, name = (null)}
ThreadDemo[4761:1205691] NSInvocationOperationRun_<NSThread: 0x600000264380>{number = 3, name = (null)}
ThreadDemo[4761:1205706] QUEUEBlockOperationRun_<NSThread: 0x6000002645c0>{number = 5, name = (null)}

咱们发现,退出队列之后不必调用工作的 start 办法,队列会帮你治理工作的执行状况。上述执行后果阐明这些工作在队列中为并发执行的。

增加依赖关系

-(void)NSOperationQueueRun{NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 1000; i++) {NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]);
        }
    }];
    
    NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{for (int i = 0; i < 1000; i++) {NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]);
        }
    }];
    
    [blockOper_1 addDependency:blockOper_2];
    [queue addOperation:blockOper_1];
    [queue addOperation:blockOper_2];
}

打印后果:

ThreadDemo[5066:1233824] blockOper_2_0_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_1_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_2_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233824] blockOper_2_3_<NSThread: 0x600000078340>{number = 3, name = (null)}
... ...
ThreadDemo[5066:1233824] blockOper_2_999_<NSThread: 0x600000078340>{number = 3, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_0_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
... ...
ThreadDemo[5066:1233822] blockOper_1_997_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_998_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
ThreadDemo[5066:1233822] blockOper_1_999_<NSThread: 0x60000006ae80>{number = 4, name = (null)}

通过打印后果咱们能够看到,增加依赖之后,依赖工作必须期待被依赖工作执行结束之后才会开始执行。⚠️,就算依赖工作的优先级再高,也是被依赖工作先执行,同时,和优先级不同,依赖关系不受队列的局限,只有是我依赖于你,那你必须先执行完,我才执行。

队列的最大并发数
就是说,这个队列最多能够有多少工作同时执行,或者说最多开拓多少条线程,如果设置为 1,那就一次只能执行一个工作,然而,不要认为这和 GCD 的串行队列一样,就算最大并发数为 1,队列工作的执行程序仍然取决于很多因素。

2. 线程锁

锁 是什么意思?

咱们在应用多线程的时候多个线程可能会拜访同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就须要咱们保障每次只有一个线程拜访这一块资源, 应运而生。

2.1 OSSpinLock 自旋锁

#import <libkern/OSAtomic.h>
__block OSSpinLock oslock = OS_SPINLOCK_INIT;
// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 1 筹备上锁");
    OSSpinLockLock(&oslock);
    sleep(4);
    NSLog(@"线程 1");
    OSSpinLockUnlock(&oslock);
    NSLog(@"线程 1 解锁胜利");
    NSLog(@"--------------------------------------------------------");
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 2 筹备上锁");
    OSSpinLockLock(&oslock);
    NSLog(@"线程 2");
    OSSpinLockUnlock(&oslock);
    NSLog(@"线程 2 解锁胜利");
});


咱们来批改一下代码:

__block OSSpinLock oslock = OS_SPINLOCK_INIT;
// 线程 1        
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
......
//OSSpinLockUnlock(&oslock);
......

OSSpinLock1 图中能够发现:当咱们锁住线程 1 时,在同时锁住线程 2 的状况下,线程 2 会始终期待 (自旋锁不会让期待的进入睡眠状态),直到线程 1 的工作执行完且解锁结束,线程 2 会立刻执行;而在 OSSpinLock2 图中,因为咱们正文掉了线程 1 中的解锁代码,会绕过线程 1,直到调用了线程 2 的解锁办法才会继续执行线程 1 中的工作, 失常状况下,lockunlock 最好成对呈现
OS_SPINLOCK_INIT: 默认值为 0, 在 locked 状态时就会大于 0unlocked 状态下为 0
OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock 地址
OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock 地址
OSSpinLockTry(&oslock):尝试加锁,能够加锁则 立刻加锁 并返回 YES, 反之返回 NO
这里顺便提一下 trylocklock应用场景:
以后线程锁失败,也能够持续其它工作,用 trylock 适合
以后线程只有锁胜利后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock

2.2 dispatch_semaphore 信号量

dispatch_semaphore_t signal = dispatch_semaphore_create(1); // 传入值必须 >=0, 若传入为 0 则阻塞线程并期待 timeout, 工夫到后会执行其后的语句
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);

// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 1 期待 ing");
    dispatch_semaphore_wait(signal, overTime); //signal 值 -1
    NSLog(@"线程 1");
    dispatch_semaphore_signal(signal); //signal 值 +1
    NSLog(@"线程 1 发送信号");
    NSLog(@"--------------------------------------------------------");
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 2 期待 ing");
    dispatch_semaphore_wait(signal, overTime);
    NSLog(@"线程 2");
    dispatch_semaphore_signal(signal);
    NSLog(@"线程 2 发送信号");
});

dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并期待 timeout, 工夫到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):能够了解为 lock, 会使得 signal-1
dispatch_semaphore_signal(signal):能够了解为 unlock, 会使得 signal+1

对于信号量,咱们能够用停车来比喻:

停车场残余 4 个车位,那么即便同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆须要期待。
信号量的值(signal)就相当于残余车位的数目,dispatch_semaphore_wait 函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的残余数目在初始化的时候就曾经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,残余的车位就减少一个;调用一次 dispatch_semaphore_wait 残余车位就缩小一个;当残余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能期待。有可能同时有几辆车期待一个停车位。有些车主没有急躁,给本人设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就始终等上来。

运行后果:

能够发现,因为咱们初始化信号量的时候是大于 0 的,所以并没有阻塞线程,而是间接执行了 线程 1 线程 2。

咱们把 信号量初始值改为 0:

能够看到这时候咱们设置的 overTime 失效了。

2.3 pthread_mutex 互斥锁

ibireme 在《不再平安的 OSSpinLock》这篇文章中提到性能最好的 OSSpinLock 曾经不再是线程平安的并把本人开源我的项目中的 OSSpinLock 都替换成了 pthread_mutex
特意去看了下源码,总结了下常见用法:

#import <pthread.h>
static pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
 //1. 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 1 筹备上锁");
    pthread_mutex_lock(&pLock);
    sleep(3);
    NSLog(@"线程 1");
    pthread_mutex_unlock(&pLock);
});

//1. 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 2 筹备上锁");
    pthread_mutex_lock(&pLock);
    NSLog(@"线程 2");
    pthread_mutex_unlock(&pLock);
});


pthread_mutex 中也有个 pthread_mutex_trylock(&pLock),和下面提到的 OSSpinLockTry(&oslock) 区别在于,前者能够加锁时返回的是 0,否则返回一个谬误提醒码;后者返回的 YESNO

pthread_mutex(recursive) 递归锁

通过下面几种例子,咱们能够发现:加锁后只能有一个线程拜访该对象,前面的线程须要排队,并且 lock 和 unlock 是对应呈现的,同一线程屡次 lock 是不容许的,而递归锁容许同一个线程在未开释其领有的锁时重复对该锁进行加锁操作。

例子:

static pthread_mutex_t pLock;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr); // 初始化 attr 并且给它赋予默认
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // 设置锁类型,这边是设置为递归锁
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr); // 销毁一个属性对象,在从新进行初始化之前该构造不能从新应用

//1. 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{static void (^RecursiveBlock)(int);
    RecursiveBlock = ^(int value) {pthread_mutex_lock(&pLock);
        if (value > 0) {NSLog(@"value: %d", value);
            RecursiveBlock(value - 1);
        }
        pthread_mutex_unlock(&pLock);
    };
    RecursiveBlock(5);
});


下面的代码如果咱们用 pthread_mutex_init(&pLock, NULL) 初始化会呈现死锁的状况,递归锁能很好的防止这种状况的死锁;

2.4 NSLock 互斥锁

NSLock 是对 mutex 一般锁的封装,是 Foundation 框架中以对象模式裸露给开发者的一种锁,(Foundation 框架同时提供了 NSConditionLockNSRecursiveLockNSCondition
NSLock API 很少也很简略:

lock、unlock:不多做解释,和下面一样
trylock:能加锁返回 YES 并执行 加锁 操作,相当于 lock,反之返回 NO
** lockBeforeDate:这个办法示意会在传入的工夫内尝试加锁,若能加锁则执行 加锁 ** 操作并返回 YES,反之返回 NO

NSLock *lock = [NSLock new];
// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 1 尝试减速 ing...");
    [lock lock];
    sleep(3);// 睡眠 5 秒
    NSLog(@"线程 1");
    [lock unlock];
    NSLog(@"线程 1 解锁胜利");
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"线程 2 尝试减速 ing...");
    BOOL x =  [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]];
    if (x) {NSLog(@"线程 2");
        [lock unlock];
    }else{NSLog(@"失败");
    }
});

#### 2.5 NSCondition 条件锁
NSCondition 是对 mutex 和 cond 的封装
咱们先来看看 API:

wait:进入期待状态
waitUntilDate::让一个线程期待肯定的工夫
signal:唤醒一个期待的线程
broadcast:唤醒所有期待的线程

NSCondition *cLock = [NSCondition new];
// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"start");
    [cLock lock];
    [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
    NSLog(@"线程 1");
    [cLock unlock];
});

  • 唤醒一个期待线程
NSCondition *cLock = [NSCondition new];
// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[cLock lock];
    NSLog(@"线程 1 加锁胜利");
    [cLock wait];
    NSLog(@"线程 1");
    [cLock unlock];
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[cLock lock];
    NSLog(@"线程 2 加锁胜利");
    [cLock wait];
    NSLog(@"线程 2");
    [cLock unlock];
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{sleep(2);
    NSLog(@"唤醒一个期待的线程");
    [cLock signal];
});

2.6 NSRecursiveLock 递归锁

NSRecursiveLock 也是对 mutex 递归锁的封装,API 跟 NSLock 基本一致。
递归锁能够被同一线程屡次申请,而不会引起死锁。这次要是用在循环或递归操作中。

NSLock *rLock = [NSLock new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{static void (^RecursiveBlock)(int);
    RecursiveBlock = ^(int value) {[rLock lock];
        if (value > 0) {NSLog(@"线程 %d", value);
            RecursiveBlock(value - 1);
        }
        [rLock unlock];
    };
    RecursiveBlock(4);
});


这段代码是一个典型的死锁状况。在咱们的线程中,RecursiveMethod 是递归调用的。所以每次进入这个 block 时,都会去 加一次锁 ,而从第二次开始,因为锁曾经被应用了且 没有解锁 ,所以它 须要期待锁被解除,这样就导致了死锁,线程被阻塞住了。

将 NSLock 替换为 NSRecursiveLock:

NSRecursiveLock 办法里还提供了两个办法,用法和下面介绍的根本没什么差异,这里不过多介绍了:

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;

2.7 @synchronized 互斥锁

@synchronized 是对 mutex 递归锁的封装
@synchronized(obj)外部会生成 obj 对应的递归锁,而后进行加锁、解锁操作

// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{@synchronized (self) {sleep(2);
        NSLog(@"线程 1");
    }
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{@synchronized (self) {NSLog(@"线程 2");
    }
});

2.8 NSConditionLock 条件锁

NSConditionLock 是对 NSCondition 的进一步封装,能够设置具体的条件值

相比于 NSLock 多了个 condition 参数,咱们能够了解为一个 条件标示

例子:

NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];

// 线程 1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{if([cLock tryLockWhenCondition:0]){NSLog(@"线程 1");
       [cLock unlockWithCondition:1];
    }else{NSLog(@"失败");
    }
});

// 线程 2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[cLock lockWhenCondition:3];
    NSLog(@"线程 2");
    [cLock unlockWithCondition:2];
});

// 线程 3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[cLock lockWhenCondition:1];
    NSLog(@"线程 3");
    [cLock unlockWithCondition:3];
});

运行后果:

  • 咱们在初始化 NSConditionLock 对象时,给了他的标示为 0
  • 执行 tryLockWhenCondition:时,咱们传入的条件标示也是 0, 所 以线程 1 加锁胜利
  • 执行 unlockWithCondition:时,这时候会把 condition0 批改为 1
  • 因为 condition 批改为了 1,会先走到 线程 3 ,而后 线程 3 又将 condition 批改为 3
  • 最初 走了 线程 2 的流程

从下面的后果咱们能够发现,NSConditionLock 还能够实现工作之间的依赖。

线程锁总结


锁的概念定义:(参考维基百科)

  1. 临界区:指的是一块对公共资源进行拜访的代码,并非一种机制或是算法。
  2. 自旋锁:是用于多线程同步的一种锁,线程重复查看锁变量是否可用。因为线程在这一过程中放弃执行,因而是一种 忙期待。一旦获取了自旋锁,线程会始终放弃该锁,直至显式开释自旋锁。自旋锁防止了过程上下文的调度开销,因而对于线程只会阻塞很短时间的场合是无效的。
  3. 互斥锁(Mutex):是一种用于多线程编程中,避免两条线程同时对同一公共资源(比方全局变量)进行读写的机制。该目标通过将代码切片成一个一个的临界区而达成。
  4. 读写锁:是计算机程序的并发管制的一种同步机制,也称“共享 - 互斥锁”、多读者 - 单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。读写锁通常用互斥锁、条件变量、信号量实现。
  5. 信号量(semaphore):是一种更高级的同步机制,互斥锁 能够说是 semaphore 在仅取值 0 / 1 时的特例。信号量 能够有更多的取值空间,用来实现更加简单的同步,而不单单是线程间互斥。
  6. 条件锁:就是条件变量,当过程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被调配到了,条件锁关上,过程持续运行。

3.runloop 机制

  • 首先说介绍下 NSRunLoopOSX/iOS 零碎中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程平安的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,然而这些 API 不是线程平安的。
  • NSRunLoop 与线程的关系
  1. 其实 runLoop 就是一个 do … while()函数,每个 runLoop 对应一个线程他们是一一对应的关系,关系保留在一个全局的 Dictionary 里边,线程刚创立时没有 RunLoop, 如果不被动获取,是不会有的,RunLoop 的创立产生在第一次获取时,RunLoop 的销毁产生在线程完结,只能在一个线程的外部获取它的 RunLoop(主线程除外)主线程默认有个 RunLoop.
  2. Thread 蕴含一个 CFRunLoop,一个 CFRunLoop 蕴含一种 CFRunLoopMode,mode 蕴含 CFRunLoopSource,CFRunLoopTimer 和 CFRunLoopObserver。
  3. RunLoop 只能运行在一种 mode 下,如果要换 mode 以后的 loop 也须要停下重启成新的。利用这个机制,ScrollView 过程中 NSDefaultRunLoopMode 的 mode 会切换 UITrackingRunLoopMode 来保障 ScrollView 的晦涩滑动不受只能在 NSDefaultRunLoopMode 时解决的事件影响滑动。同时 mode 还是可定制的。
    NSDefaultRunLoopMode:默认,闲暇状态
    UITrackingRunLoopMode:ScrollView 滑动时 UIInitializationRunLoopMode:启动时 NSRunLoopCommonModes:Mode 汇合 Timer 计时会被 scrollView 的滑动影响的问题能够通过将 timer 增加到 NSRunLoopCommonModes 来解决
// 将 timer 增加到 NSDefaultRunLoopMode 中
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
// 而后再增加到 NSRunLoopCommonModes 里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
/// 全局的 Dictionary,key 是 pthread_t,value 是 CFRunLoopRefstatic CFMutableDictionaryRef loopsDic;
/// 拜访 loopsDic 时的锁 static CFSpinLock_t loopsLock;
/// 获取一个 pthread 对应的 RunLoop。CFRunLoopRef _CFRunLoopGet(pthread_t thread) {OSSpinLockLock(&loopsLock); 
  if (!loopsDic) { // 第一次进入时,初始化全局 Dic,并先为主线程创立一个 RunLoop。loopsDic = CFDictionaryCreateMutable(); 
      CFRunLoopRef mainLoop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic,       pthread_main_thread_np(), mainLoop);
   }  
   /// 间接从 Dictionary 里获取。CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));  
   if (!loop) { 
    /// 取不到时,创立一个 
    loop = _CFRunLoopCreate();
   CFDictionarySetValue(loopsDic, thread, loop); 
   /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop); 
   }  
  OSSpinLockUnLock(&loopsLock); 
  return loop;
}
CFRunLoopRef CFRunLoopGetMain() {return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {return _CFRunLoopGet(pthread_self());
}

介绍完线程和 RunLoop 的关系后,次要看下 RunLoop 和 RunLoopModel, RunLoop runLoop 在 CoreFoundation 对外一共有五大类最为对外的接口:

RunLoop

CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

1. 其中 CFRunLoopModeRef 类并没有对外裸露,只是通过 CFRunLoopRef 的接口进行了封装
一个 RunLoop 蕴含若干个 Mode,每个 Mode 又蕴含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个 Mode 被称作 CurrentMode。如果须要切换 Mode,只能退出 Loop,再从新指定一个 Mode 进入。这样做次要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

2. CFRunLoopSourceRef 是事件产生的中央,Source 蕴含了两个局部:Source0 和 Source1。source0:解决如 UIEvent,CFSocket 这样的事件 source1:Mach port 驱动,CFMachport,CFMessagePortSource0 只蕴含了一个回调(函数指针),它并不能被动触发事件。应用时,你须要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,而后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其解决这个事件。Source1 蕴含了一个 mach_port 和一个回调(函数指针),被用于和通过内核和其余 mach_port 互相发送音讯。这种 Source 能被动唤醒 RunLoop 的线程,其原理在上面会讲到。

3. CFRunLoopTimerRefNSTimer 是对 RunLoopTimer 的封装是基于工夫的触发器,它和 NSTimer 是 toll-free bridge 的。其蕴含一个工夫长度和一个回调(函数指针)。当其退出到 RunLoop 时,RunLoop 会注册对应的工夫点,当工夫点到时,RunLoop 会被唤醒以执行那个回调

4. CFRunLoopObserverRefCocoa 框架中很多机制比方 CAAnimation 等都是由 RunLoopObserver 触发的。observer 到以后状态的变动进行告诉观察者,每个 Ovserver 都蕴含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调承受到这个变动。能够观测的工夫点有以下几个:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // 行将进入 Loop
 kCFRunLoopBeforeTimers = (1UL << 1), // 行将解决 Timer 
 kCFRunLoopBeforeSources = (1UL << 2), // 行将解决 Source 
 kCFRunLoopBeforeWaiting = (1UL << 5), // 行将进入休眠 
 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 
 kCFRunLoopExit = (1UL << 7), // 行将退出 Loop
};

RunLoopModelCFRunLoopMode 和 CFRunLoop 的构造大抵如下:

struct __CFRunLoopMode {
  CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
  CFMutableSetRef _sources0; // Set  
  CFMutableSetRef _sources1; // Set 
  CFMutableArrayRef _observers; // Array  
  CFMutableArrayRef _timers; // Array  ...
};
struct __CFRunLoop { 
  CFMutableSetRef _commonModes; // Set
  CFMutableSetRef _commonModeItems; // Set  
  CFRunLoopModeRef _currentMode; //
  Current Runloop Mode CFMutableSetRef _modes; // Set  ...
};

这里有个概念叫 “CommonModes”:一个 Mode 能够将本人标记为 ”Common” 属性(通过将其 ModeName 增加到 RunLoop 的 “commonModes” 中)。RunLoop 会主动将 _commonModeItems 退出到具备 “Common” 标记的所有 Mode 里。利用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都曾经被标记为 ”Common” 属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创立一个 Timer 并加到 DefaultMode 时,Timer 会失去反复回调,但此时滑动一个 TableView 时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。有时你须要一个 Timer,在两个 Mode 中都能失去回调,一种方法就是将这个 Timer 别离退出这两个 Mode。还有一种形式,就是将 Timer 退出到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具备 ”Common” 属性的 Mode 里去。

RunLoop 底层实现从下面代码能够看到,RunLoop 的外围是基于 mach port 的,其进入休眠时调用的函数是 mach_msg()。为了解释这几个概念,上面略微介绍一下 OSX/iOS 的零碎架构。

苹果官网将整个零碎大抵划分为上述 4 个档次:应用层包含用户能接触到的图形利用,例如 Spotlight、Aqua、SpringBoard 等。利用框架层即开发人员接触到的 Cocoa 等框架。外围框架层包含各种外围框架、OpenGL 等内容。Darwin 即操作系统的外围,包含零碎内核、驱动、Shell 等内容,这一层是开源的,其所有源码都能够在 opensource.apple.com 里找到。

CFRunLoop {
 current mode = kCFRunLoopDefaultMode common modes = {UITrackingRunLoopMode kCFRunLoopDefaultMode} common mode items = {// source0 (manual) CFRunLoopSource {order =-1, 
{callout = _UIApplicationHandleEventQueue}}
 CFRunLoopSource {order =-1, 
{callout = PurpleEventSignalCallback}} 
CFRunLoopSource {order = 0, {callout = FBSSerialQueueRunLoopSourceHandler}} 
// source1 (mach port) CFRunLoopSource {order = 0,
 {port = 17923}} 
CFRunLoopSource {order = 0, {port = 12039}} CFRunLoopSource {order = 0, {port = 16647}} CFRunLoopSource {order =-1, { callout = PurpleEventCallback}} CFRunLoopSource {order = 0, {port = 2407, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}} CFRunLoopSource {order = 0, {port = 1c03, callout = __IOHIDEventSystemClientAvailabilityCallback}} CFRunLoopSource {order = 0, {port = 1b03, callout = __IOHIDEventSystemClientQueueCallback}} CFRunLoopSource {order = 1, {port = 1903, callout = __IOMIGMachPortPortCallback}} 
// Ovserver CFRunLoopObserver {order = -2147483647, activities = 0x1,
// Entry callout = _wrapRunLoopWithAutoreleasePoolHandler} CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting callout = _UIGestureRecognizerUpdateObserver} CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit callout = _afterCACommitHandler} CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit callout = _wrapRunLoopWithAutoreleasePoolHandler} // Timer CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0, next fire date = 453098071 (-4421.76019 @ 96223387169499), callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)} }, modes = {CFRunLoopMode { sources0 = { /* same as 'common mode items' */}, sources1 = {/* same as 'common mode items' */}, observers = {/* same as 'common mode items' */}, timers = {/* same as 'common mode items' */}, }, CFRunLoopMode {sources0 = { /* same as 'common mode items' */}, sources1 = {/* same as 'common mode items' */}, observers = {/* same as 'common mode items' */}, timers = {/* same as 'common mode items' */}, }, CFRunLoopMode {sources0 = { CFRunLoopSource {order = 0, { callout = FBSSerialQueueRunLoopSourceHandler}} }, sources1 = (null), observers = {CFRunLoopObserver >{activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} )}, timers = (null), },
 CFRunLoopMode { // 这是一个占位的 Mode,没有理论作用。sources0 = {CFRunLoopSource {order = -1, { callout = PurpleEventSignalCallback}} },
 sources1 = {CFRunLoopSource {order = -1, { callout = PurpleEventCallback}} },
 observers = (null), timers = (null), },
  CFRunLoopMode {sources0 = (null), sources1 = (null), observers = (null), timers = (null), } }}

这里能看到苹果所有的 Model

事件响应

苹果注册了一个 Source1 (基于 mach port 的) 用来接管零碎事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件 (触摸 / 锁屏 / 摇摆等) 产生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接管。这个过程的详细情况能够参考 这里。SpringBoard 只接管按键 (锁屏 / 静音等),触摸,减速,靠近传感器等几种 Event,随后用 mach port 转发给须要的 App 过程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行利用外部的散发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 解决并包装成 UIEvent 进行解决或散发,其中包含辨认 UIGesture/ 解决屏幕旋转 / 发送给 UIWindow 等。通常事件比方 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中实现的。#### 手势辨认当下面的 _UIApplicationHandleEventQueue() 辨认了一个手势时,其首先会调用 Cancel 将以后的 touchesBegin/Move/End 系列回调打断。随后零碎将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop 行将进入休眠) 事件,这个 Observer 的回调函数是 _UIGestureRecognizerUpdateObserver(),其外部会获取所有刚被标记为待处理的 GestureRecognizer,并执行 GestureRecognizer 的回调。当有 UIGestureRecognizer 的变动(创立 / 销毁 / 状态扭转) 时,这个回调都会进行相应解决。

界面更新

当在操作 UI 时,比方扭转了 Frame、更新了 UIView/CALayer 的档次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay 办法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。苹果注册了一个 Observer 监听 BeforeWaiting(行将进入休眠) 和 Exit (行将退出 Loop) 事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行理论的绘制和调整,并更新 UI 界面。

NSRunLoop 的利用

AFNetworking 创立一个常驻服务线程去解决数据返回应用 NSOperation+NSURLConnection 并发模型都会面临 NSURLConnection 下载实现火线程退出导致 NSOperation 对象接管不到回调的问题。AFNetWorking 解决这个问题的办法是依照官网的 guid 上写的 NSURLConnection 的 delegate 办法须要在 connection 发动的线程 runloop 中调用,于是 AFNetWorking 间接借鉴了 Apple 本人的一个 Demo 的实现办法独自起一个 global thread,内置一个 runloop,所有的 connection 都由这个 runloop 发动,回调也是它接管,不占用主线程,也不耗 CPU 资源。##TableView 中实现平滑滚动提早加载图片利用 CFRunLoopMode 的个性,能够将图片的加载放到 NSDefaultRunLoopMode 的 mode 里,这样在滚动 UITrackingRunLoopMode 这个 mode 时不会被加载而影响到。

UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

应用 RunLoop 阻塞线程并且同时可能解决其余 source 事件
CFRunLoopRunInMode(kCFRunLoopDefaultMode,second, NO);
CFRunLoopStop(CFRunLoopGetMain());

正文完
 0