iOS笔记系列目录

一 常见多线程实现

(一)pthread

(1)特点

  • 1)一套通用的多线程API
  • 2)适用于Unix/Linux/Windows等系统
  • 3)跨平台可移植
  • 4)使用难度大

(2)使用语言

C语言

(3)使用频率

几乎不用

(4)线程生命周期

由程序员进行管理

(5)概念、属性与方法

(二)NSThread

(1)特点

  • 1)使用更加面向对象
  • 2)简单易用,可直接操作线程对象

(2)使用语言

OC

(3)使用频率

偶尔使用

(4)线程生命周期

由程序员进行管理

(5)概念、属性与方法

1)创建线程:
// 实例化线程需要手动启动才能运行,手动调用[newThread start];
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"Thread"];
//或
NSThread *newThread= [[NSThread alloc] init];
//或
NSThread *newThread= [[NSThread alloc] initWithBlock:^{
    NSLog(@"initWithBlock");
}];
2)常用属性

/// a.线程字典
/*
每个线程都维护了一个<键-值>的字典,它可以在线程里面的任何地方被访问。你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。
*/
@property (readonly, retain) NSMutableDictionary *threadDictionary;

/// b.优先级
@property double threadPriority;

/// c.线程优先级
/*
  NSQualityOfServiceUserInteractive:最高优先级,主要用于提供交互UI的操作,比如处理点击事件,绘制图像到屏幕上
  NSQualityOfServiceUserInitiated:次高优先级,主要用于执行需要立即返回的任务
  NSQualityOfServiceDefault:默认优先级,当没有设置优先级的时候,线程默认优先级
  NSQualityOfServiceUtility:普通优先级,主要用于不需要立即返回的任务
  NSQualityOfServiceBackground:后台优先级,用于完全不紧急的任务
*/
@property NSQualityOfService qualityOfService; 

/// d.线程名称
@property (nullable, copy) NSString *name;

/// e.线程分配的栈的大小(子线程默认512k,主线程1M)
@property NSUInteger stackSize ;

/// f.是否在执行
@property (readonly, getter=isExecuting) BOOL executing;

/// g.是否已完成
@property (readonly, getter=isFinished) BOOL finished;

/// h.是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
3)常用方法
/// a.启动线程 
-(void)start;  //实例化线程需要手动启动才能运行

/// b.是否是主线程
-(BOOL)isMainThread; 

/// c.获取当前线程
+(void)currentThread;

/// d.当前代码运行所在线程是否是子线程
+(BOOL)isMultiThreaded; 

/// e.退出当前线程
+(void)exit; 

/// f.创建子线程并开始
// 以下两种方式,创建完成后就可执行,不需手动开启
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

(三)GCD

(1)特点

  • 1)旨在替代NSThread等线程技术
  • 2)充分利用设备的多核(自动)

(2)使用语言

C语言

(3)使用频率

经常使用

(4)线程生命周期

自动管理

(5)概念、属性与方法

1)栅栏方法
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

多读单写

  • 1.读写互斥
  • 2.写写互斥
  • 3.读读并发
@interface TestFileReadWrite ()

//模拟场景,允许多个线程同时访问字典,但是只有一个线程可以写字典,多线程需要访问的数据量
@property (nonatomic, strong) NSMutableDictionary *dataDic;

@end

@implementation TestFileReadWrite {
    //定义一个并发队列
    dispatch_queue_t _concurrent_queue;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _concurrent_queue = dispatch_queue_create("com.by.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
        _dataDic = @{}.mutableCopy;
    }
    return self;
}

// 读取数据,并发操作
- (id)by_objectForKey:(NSString *)key {
    __block id obj;
    //同步读取数据
    dispatch_sync(_concurrent_queue, ^{
        obj = [self.dataDic objectForKey:key];
    });
    
    return obj;
    
}

// 写入数据,异步栅栏
- (void)by_setObject:(id)obj forKey:(NSString *)key {
    key = [key copy];
    dispatch_barrier_async(_concurrent_queue, ^{
        [self.dataDic setObject:obj forKey:key];
    });
}

@end

解析

  • 首先我们要维系一个GCD 队列,最好不用全局队列,毕竟大家都知道全局队列遇到栅栏函数是有坑点的,这里就不分析了!
  • 因为考虑性能 死锁 堵塞的因素不考虑串行队列,用的是自定义的并发队列!_concurrent_queue = dispatch_queue_create("com.by.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
  • 首先我们来看看读操作:by_setObject:forKey: 我们考虑到多线程影响是不能用异步函数的!说明:

    • 线程2 获取:name 线程3 获取 age
    • 如果因为异步并发,导致混乱 本来读的是name 结果读到了age
    • 我们允许多个任务同时进去! 但是读操作需要同步返回,所以我们选择:同步函数 (读读并发)
  • 我们再来看看写操作,在写操作的时候对key进行了copy, 关于此处的解释,插入一段来自参考文献的引用:
函数调用者可以自由传递一个NSMutableStringkey,并且能够在函数返回后修改它。因此我们必须对传入的字符串使用copy操作以确保函数能够正确地工作。如果传入的字符串不是可变的(也就是正常的NSString类型),调用copy基本上是个空操作。
  • 这里我们选择 dispatch_barrier_async, 为什么是栅栏函数而不是异步函数或者同步函数,下面分析:

    • 栅栏函数任务:之前所有的任务执行完毕,并且在它后面的任务开始之前,期间不会有其他的任务执行,这样比较好的促使 写操作一个接一个写 (写写互斥),不会乱!
    • 为什么不是异步函数?应该很容易分析,毕竟会产生混乱!
    • 为什么不用同步函数?如果读写都操作了,那么用同步函数,就有可能存在:我写需要等待读操作回来才能执行,显然这里是不合理!
2)信号量
dispatch_semaphore
/// 降低信号量,返回值小于0将会阻塞
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); 
/// 升高信号量,如果前面信号量为-1,使用这个方法将解除阻塞
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);   
3)延时执行方法
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
4)一次性代码(只执行一次)
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

单例

static MyClass * __instance;

+ (instancetype)shareInstance {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        __instance = [[super allocWithZone:NULL] init];
    });
    
    return __instance;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [self shareInstance];
}

- (id)copyWithZone:(NSZone *)zone {
    return self;
}

- (instancetype)init {
    if (self = [super init]) {
        
    }
    return self;
}
5)快速迭代方法
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
6)队列组

dispatch_group

// 全局变量group
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// 进入组(进入组和离开组必须成对出现, 否则会造成死锁)
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    NSLog(@"1");
    //离开组
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
    NSLog(@"2");
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{  // 监听组里所有线程完成的情况
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务1,2已完成");
    });    
});

(四)NSOperation

(1)特点:

  • 1)基于GCD(底层是GCD)
  • 2)比GCD多了一些更简单实用的功能
  • 3)使用更加面向对象

(2)使用语言

OC

(3)使用频率

经常使用

(4)线程生命周期

自动管理

(5)概念、属性与方法

1).创建

a.使用子类 NSInvocationOperation

// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

// 2.调用 start 方法开始执行操作
[op start];

b.使用子类 NSBlockOperation

// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}];

// 2.调用 start 方法开始执行操作
[op start];

c.自定义继承自 NSOperation 的子类,通过实现内部相应的方法来封装操作

@implementation CustomNSOperation

- (void)main {
    if (!self.isCancelled) {
        for (int i = 0; i<2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"current thread: %@", [NSThread currentThread]);
        }
    }
}

@end


- (void)viewDidLoad {
    [super viewDidLoad];

    // 1.创建 CustomNSOperation 对象
    CustomNSOperation *op = [[CustomNSOperation alloc] init];
    // 2.调用 start 方法开始执行操作,在主线程调用,将不会开辟新线程
    [op start];
}

二 多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)

三 多线程的优点

能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)

四 多线程的缺点

开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享

五 你更倾向于哪一种?

倾向于GCD:
GCD 技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术 NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。
这种类似不是一个巧合,在早期,MacOX 与 iOS 的程序都普遍采用Operation Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X 10.6以后,Operation Queue的底层实现都是用GCD来实现的。

六 GCD与NSOprationQueue的区别

(1)区别

  • 1.GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
  • 2.在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
  • 3.NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
  • 4.我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样能比GCD更加有效地掌控我们执行的后台任务;
  • 5.在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
  • 6.我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

总的来说,Operation queue 提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

(二)更倾向于哪个

更倾向于NSOperation

NSOperation相对于GCD:

  • 1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
  • 2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
  • 3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
  • 4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

(三)使用NSOperation的情况

各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。

(四)使用GCD的情况

一般的需求很简单的多线程操作,用GCD都可以了,简单高效。

从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。

当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

七 常见提问

  • 1.你理解的多线程?
  • 2.可能会追问,每种多线程基于什么语言?
  • 3.生命周期是如何管理?
  • 4.你更倾向于哪种?追问至现在常用的两种你的看法是?

参考链接:

NSThread
GCD


Adrenine
9 声望3 粉丝

最怕一生碌碌无为却道平凡难能可贵!