多线程之GCD、NSOperation、NSThread

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Maple_ROSI/article/details/52855025

GCD

1、GCD介绍

  • 全称Grand Central Dispatch,可翻译为”牛逼的中枢调度器”
    纯C语言开发,是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),可以自动管理线程的生命周期(创建线程、调度任务、销毁线程)。

2、GCD的两个核心
2.1 任务

  • 执行的操作,在GCD中,任务是通过 block来封装的。并且任务的block没有参数也没有返回值。

2.2 队列

  • 存放任务

包括

  • 串行队列
  • 并发队列
  • 主队列
  • 全局队列
    3、函数
    3.1 GCD函数
    3.1.1 同步 dispatch_sync
    同步:任务会在当前线程执行,因为同步函数不具备开新线程的能力。

    void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

3.1.2 异步 dispatch_async
异步:任务会在子线程执行,因为异步函数具备开新线程的能力。

```
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
```

3.2 GCD使用步骤:

  • 创建队列,或则获取队列
  • 创建任务
  • 将任务添加到队列中
  • GCD会自动将队列中的任务取出,放到对应的线程中执行
  • 任务取出遵循队列的FIFO原则:先进先出,后进后出

示例代码

// 1. 获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 创建任务
dispatch_block_t task = ^ {
    NSLog(@"hello %@", [NSThread currentThread]);
};
// 3. 将任务添加到队列,并且指定执行任务的函数
dispatch_async(queue, task);

通常写成一句代码

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"hello %@", [NSThread currentThread]);
    });

4、串行队列和并发队列
4.1 串行队列 (Serial Dispatch Queue)
4.1.1 特点

  • 先进先出,按照顺序执行,并且一次只能调用一个任务
  • 无论队列中所指定的执行任务的函数是同步还是异步,都必须等待前一个任务执行完毕才可以调用后面的人

4.1.2 创建一个串行队列
方法一

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

方法二

dispatch_queue_t queue = dispatch_queue_create("test", NULL);

4.1.3 串行队列,同步执行
代码:

// 1、创建串行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 2、将任务添加到队列,并且指定同步执行
for (int i = 0; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"%@--%d",[NSThread currentThread],i);
    });
}

结论:串行队列,同步执行,不开新线程,按顺序执行

4.1.4 串行队列,异步执行
代码:

// 1、创建串行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 2、将任务添加到队列,并且指定同步执行
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"%@--%d",[NSThread currentThread],i);
    });
}

结论:串行队列,异步执行,开启一条新的线程,按顺序执行

4.2 并发队列 (Concurrent Dispatch Queue)
4.2.1 特点

  • 并发同时调度队列中的任务去执行
  • 如果当前调度的任务是同步执行的,会等待当前任务执行完毕后,再调度后续的任务
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,就不会等待当前任务,直接调度任务到新线程去执行。

4.2.2 创建并发队列

dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

4.2.3 并发队列,同步执行
代码:


// 1. 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 将任务添加到队列, 并且指定同步执行
for (int i = 0; i < 10; i++) {
    dispatch_sync(queue, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

结论:并发队列,同步执行,不开线程,顺序执行

4.2.4 并发队列,异步执行
代码:

// 1. 创建并发队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
// 2. 将任务添加到队列, 并且指定同步执行
for (int i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}

结论:开启足够多的线程,不按照顺序执行
CPU在调度的时候以最高效的方式调度和执行任务,所以会开启多条线程,因为并发,执行顺序不一定

5、主队列
5.1 主队列

  • 主队列是系统提供的,无需自己创建,可以通过dispatch_get_main_queue()函数来获取。

5.2 特点

  • 添加到主队列的任务只能由主线程来执行。
  • 先进先出的,只有当主线程的代码执行完毕后,主队列才会调度任务到主线程执行

5.3 主队列 异步执行
代码

// 1. 获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 将任务添加到主队列, 并且指定异步执行
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
    NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
// 先执行完这句代码, 才会执行主队列中的任务
NSLog(@"hello %@", [NSThread currentThread]);

结论:
在主线程顺序执行,不开启新的线程
主队列的特点:只有当主线程空闲时,主队列才会调度任务到主线程执行
主队列就算是异步执行也不会开启新的线程
主队列相当于一个全局的串行队列

主队列和串行队列的区别
串行队列:必须等待一个任务执行完毕,才会调度下一个任务。
主队列:如果主线程上有代码执行,主队列就不调度任务。

5.4 主队列 同步执行(死锁)
代码

NSLog(@"begin");
// 1. 获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 将任务添加到主队列, 并且指定同步执行
// 死锁
for (int i = 0; i < 10; i++) {
    dispatch_sync(q, ^{
        NSLog(@"%@ %d", [NSThread currentThread], i);
    });
}
NSLog(@"end");

结果可以看出,不是想要的结果,这时候发生了死锁
在主线程执行,主队列同步执行任务,会发生死锁,主线程和主队列同步任务相互等待,造成死锁

解决办法
代码

NSLog(@"begin");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"--- %@", [NSThread currentThread]);
    // 1. 获取主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    // 2. 将任务添加到主队列, 并且指定同步执行
    // 死锁
    for (int i = 0; i < 10; i++) {
        dispatch_sync(q, ^{
            NSLog(@"%@ %d", [NSThread currentThread], i);
        });
    }
});
NSLog(@"end");

结果可以看出,当我们将主队列同步执行任务放到子线程去执行,就不会出现死锁。由于将主队列同步放到了子线程中执行,主队列同步任务无法阻塞主线程执行代码,因此主线程可以将主线程上的代码执行完毕。当主线程执行完毕之后,就会执行主队列里面的任务。

6、全局队列
全局队列是系统提供的,无需自己创建,可以直接通过dispatch_get_global_queue(long identifier, unsigned long flags);函数来获取。

6.1 全局队列 异步执行
代码

// 1. 获取全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 将任务添加到全局队列, 异步执行
for (int i = 0; i < 10; i++) {
    dispatch_async(q, ^{
        NSLog(@"%d %@", i, [NSThread currentThread]);
    });
}

特点:
1、全局队列的工作特性跟并发队列一致。 实际上,全局队列就是系统为了方便程序员,专门提供的一种特殊的并发队列。
2、全局队列和并发队列的区别:

全局队列没有名称,无论ARC还是MRC都不需要考虑内存释放,日常开发,建议使用全局队列
并发队列有名称,如果在MRC开发中,需要使用dispatch_release来释放相应的对象,dispatch_barrier 必须使用自定义的并发队列,开发第三方框架,建议使用并发队列

3、函数

dispatch_get_global_queue(long identifier, unsigned long flags);

7、GCD总结
1、开不开线程,由执行任务的函数决定

  • 同步执行不开线程
  • 异步执行开线程

2、异步执行任务,开几条线程由队列决定

  • 串行队列,只会开一条线程,因为一条就足够了
  • 并发队列,可以开多条线程,具体开几条由线程池决定
  • 对主队列而言,不管是同步执行还是异步执行,都不会开线程。

NSOperation

1、NSOperation简介
1.1 NSOperation与GCD的区别:

  • OC语言中基于 GCD 的面向对象的封装;
  • 使用起来比 GCD 更加简单;
  • 提供了一些用 GCD 不好实现的功能;
  • 苹果推荐使用,使用 NSOperation 程序员不用关心线程的生命周期

1.2NSOperation的特点

  • NSOperation 是一个抽象类,抽象类不能直接使用,必须使用它的子类
  • 抽象类的用处是定义子类共有的属性和方法

2、核心概念

  • 将操作添加到队列,异步执行。相对于GCD创建任务,将任务添加到队列。
  • 将NSOperation添加到NSOperationQueue就可以实现多线程编程

3、操作步骤

  • 先将需要执行的操作封装到一个NSOperation对象中
  • 然后将NSOperation对象添加到NSOperationQueue中
  • 系统会自动将NSOperationQueue中的NSOperation取出来
  • 将取出的NSOperation封装的操作放到一条新线程中执行

4、NSInvocationOperation
No1.
代码

- (void)viewDidLoad {
[super viewDidLoad];
//创建操作,然后调用操作的start方法
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
NSLog(@"%d",op.isFinished);
[op start];
NSLog(@"%d",op.isFinished);
}

- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}

结论:[op start]在主线程中调用的,所以执行的线程也会是在主线程执行! 重复调用start也只会执行一次,因为NSOperation会有一个属性去记住,是否已经完成了该操作!

No2.
代码

- (void)viewDidLoad {
[super viewDidLoad];
//    创建操作,将操作添加到NSOperationQueue中,然后就会异步的自动执行
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
//队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//把操作添加到队列
[queue addOperation:op];
}

- (void)demo {
NSLog(@"hello %@",[NSThread currentThread]);
}

将操作添加到NSOperationQueue中,然后就会异步的自动执行

5、NSBlockOperation
NSBlockOperation 中使用block的方式让所有代码逻辑在一起,使用起来更加简便。

NO1.
代码

//创建操作
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"hello %@",[NSThread currentThread]);
}];
//更新op的状态,执行main方法,不会开新线程
[op start];

NO2.
代码

//    创建队列,创建操作,将操作添加到队列中执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"hello %@",[NSThread currentThread]);
}];
[queue addOperation:op];

NO3.
代码

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

[queue addOperationWithBlock:^{
    NSLog(@"hello %@",[NSThread currentThread]);

}];

创建队列,添加block形式的操作


NSThread

1、创建一个新的线程
方式一

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];

方式二

[NSThread detachNewThreadSelector:@selector(demo) toTarget:self withObject:nil];

方式三

[self performSelectorInBackground:@selector(demo) withObject:nil];

2、线程的状态
线程状态分为五种

  • 创建 New
  • 就绪 Runnable
  • 运行 Running

    -(void)start;
  • 阻塞(暂停) Blocked

    +(void)sleepUntilDate:(NSDate *)date;
    +(void)sleepForTimeInterval:(NSTimeInterval)ti;
  • 死亡 Dead

    +(void)exit;

3、线程的属性
线程有两个重要的属性:名称和优先级

3.1 名称 name
设置线程名用于记录线程,在出现异常时可以DeBug

3.2 优先级,也叫做“服务质量”。threadPriority,取值0到1.
优先级或者服务质量高的,可以优先调用,只是说会优先调用,但是不是百分之百的优先调用,这里存在一个概率问题,内核里的算法调度线程的时候,只是把优先级作为一个考虑因素,还有很多个因数会决定哪个线程优先调用。这点得注意注意!!!
下面是测试代码

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //新建状态
    NSThread *test1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    test1.name = @"test1";
    //线程的优先级
    test1.threadPriority = 1.0;
    //就绪状态
    [test1 start];


    //新建状态
    NSThread *test2= [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
    test2.name = @"test2";
    test2.threadPriority = 0;
    //就绪状态
    [test2 start];
    }

    //线程执行完成之后会自动销毁
    - (void)demo {
        for (int i = 0; i < 20; i++) {
            NSLog(@"%d--%@",i,[NSThread currentThread]);
        }
    }

猜你喜欢

转载自blog.csdn.net/Maple_ROSI/article/details/52855025
今日推荐