登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

关于iOS多线程,说的很详细你会懂会懂! [复制链接]

2017-8-31 10:41
BlueManlove 阅读:1099 评论:2 赞:0
Tag:  

事出必有因,今天我想和你聊聊线程的原因就是——当然是本着一个共产党人的思想觉悟,为人民透析生命,讲解你正在蒙圈的知识点,或者想破脑袋才发现如此简单的技术方案。

1.jpg

很多人学线程,迷迷糊糊;很多人问线程,有所期待;也有很多人写线程,分享认知给正在努力的年轻人,呦,呦,呦呦。但是,你真的了解线程么?你真的会用多线程么?你真的学明白,问明白,写明白了么?不管你明不明白,反正我不明白,但是,没准,你看完,你就明白了。

2.jpg

前言

  • 提到线程,那就不得不提CPU,现代的CPU有一个很重要的特性,就是时间片,每一个获得CPU的任务只能运行一个时间片规定的时间。

  • 其实线程对操作系统来说就是一段代码以及运行时数据。操作系统会为每个线程保存相关的数据,当接收到来自CPU的时间片中断事件时,就会按一定规则从这些线程中选择一个,恢复它的运行时数据,这样CPU就可以继续执行这个线程了。

  • 也就是其实就单核CUP而言,并没有办法实现真正意义上的并发执行,只是CPU快速地在多条线程之间调度,CPU调度线程的时间足够快,就造成了多线程并发执行的假象。并且就单核CPU而言多线程可以解决线程阻塞的问题,但是其本身运行效率并没有提高,多CPU的并行运算才真正解决了运行效率问题。

  • 系统中正在运行的每一个应用程序都是一个进程,每个进程系统都会分配给它独立的内存运行。也就是说,在iOS系统中中,每一个应用都是一个进程。

  • 一个进程的所有任务都在线程中进行,因此每个进程至少要有一个线程,也就是主线程。那多线程其实就是一个进程开启多条线程,让所有任务并发执行。

  • 多线程在一定意义上实现了进程内的资源共享,以及效率的提升。同时,在一定程度上相对独立,它是程序执行流的最小单元,是进程中的一个实体,是执行程序最基本的单元,有自己栈和寄存器。

  • 上面这些你是不是都知道,但是我偏要说,哦呵呵。既然我们聊线程,那我们就先从线程开刀。

Pthreads && NSThread

先来看与线程有最直接关系的一套C的API:

Pthreads

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

高大上有木有,跨平台有木有,你没用过有木有!下面我们来看一下这个看似牛逼但真的基本用不到的Pthreads是怎么用的:

不如我们来用Pthreads创建一个线程去执行一个任务:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-(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;
}

输出结果:    

1
2
ThreadDemo[1197:143578] creat thread success
ThreadDemo[1197:143649] {number = 3, name = (null)} - Hello World

从打印结果来看,该任务是在新开辟的线程中执行的,但是感觉用起来超不友好,很多东西需要自己管理,单单是任务队列以及线程生命周期的管理就够你头疼的,那你写出的代码还能是艺术么!其实之所以抛弃这套API很少用,是因为我们有更好的选择:NSThread。

4408163-56137341b57d4745.jpg

NSThread

哎呀,它面向对象,再去看看苹果提供的API,对比一下Pthreads,简单明了,人生仿佛又充满了阳光和希望,我们先来一看一下系统提供给我们的API自然就知道怎么用了,来来来,我给你注释一下啊:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@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 *)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 *)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

上面的介绍您还满意吗?小的帮您下载一张图片,您瞧好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(void)creatBigImageView{
    self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_bigImageView];
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startButton.frame = CGRectMake(0, 0, self.view.frame.size.width / 2, 50);
    startButton.backgroundColor = [UIColor grayColor];
    [startButton setTitle:@"开始加载" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(loadImage) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];
     
    UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
    jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 0, self.view.frame.size.width / 2, 50);
    jamButton.backgroundColor = [UIColor grayColor];
    [jamButton setTitle:@"阻塞测试" forState:UIControlStateNormal];
    [jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:jamButton];
}
-(void)jamTest{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"线程阻塞" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
    [alertView show];
}
-(void)loadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    [self updateImageData:imageData];
}
-(void)updateImageData:(NSData*)imageData{
    UIImage *image = [UIImage imageWithData:imageData];
    self.bigImageView.image = image;
}

运行结果:

我们可以清楚的看到,主线程阻塞了,用户不可以进行其他操作,你见过这样的应用吗?

所以我们这样改一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-(void)creatBigImageView{
    self.bigImageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:_bigImageView];
    UIButton *startButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startButton.frame = CGRectMake(0, 20, self.view.frame.size.width / 2, 50);
    startButton.backgroundColor = [UIColor grayColor];
    [startButton setTitle:@"开始加载" forState:UIControlStateNormal];
    [startButton addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startButton];
     
    UIButton *jamButton = [UIButton buttonWithType:UIButtonTypeSystem];
    jamButton.frame = CGRectMake(self.view.frame.size.width / 2, 20, self.view.frame.size.width / 2, 50);
    jamButton.backgroundColor = [UIColor grayColor];
    [jamButton setTitle:@"阻塞测试" forState:UIControlStateNormal];
    [jamButton addTarget:self action:@selector(jamTest) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:jamButton];
}
-(void)jamTest{
    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"阻塞测试" message:@"" delegate:nil cancelButtonTitle:@"好" otherButtonTitles:nil, nil];
    [alertView show];
}
-(void)loadImageWithMultiThread{
    //方法1:使用对象方法
    //NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
    //??启动一个线程并非就一定立即执行,而是处于就绪状态,当CUP调度时才真正执行
    //[thread start];
     
    //方法2:使用类方法
    [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
-(void)loadImage{
    NSURL *imageUrl = [NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"];
    NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
    //必须在主线程更新UI,Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装),waitUntilDone:是否线程任务完成执行
    [self performSelectorOnMainThread:@selector(updateImageData:) withObject:imageData waitUntilDone:YES];
     
    //[self updateImageData:imageData];
}
-(void)updateImageData:(NSData*)imageData{
    UIImage *image = [UIImage imageWithData:imageData];
    self.bigImageView.image = image;
}

运行结果:

哎呀,用多线程果然能解决线程阻塞的问题,并且NSThread也比Pthreads好用,仿佛你对精通熟练使用多线程又有了一丝丝曙光。假如我有很多不同类型的任务,每个任务之间还有联系和依赖,你是不是又懵逼了,上面的你是不是觉得又白看了,其实开发中我觉得NSThread用到最多的就是[NSThread currentThread];了。(不要慌,往下看... ...)

4408163-7b841d1c77877c4c.jpg

GCD

GCD,全名Grand Central Dispatch,中文名郭草地,是基于C语言的一套多线程开发API,一听名字就是个狠角色,也是目前苹果官方推荐的多线程开发方式。可以说是使用方便,又不失逼格。总体来说,他解决我提到的上面直接操作线程带来的难题,它自动帮你管理了线程的生命周期以及任务的执行规则。下面我们会频繁的说道一个词,那就是任务,说白了,任务其实就是你要执行的那段代码。

任务管理方式——队列

上面说当我们要管理多个任务时,线程开发给我们带来了一定的技术难度,或者说不方便性,GCD给出了我们统一管理任务的方式,那就是队列。我们来看一下iOS多线程操作中的队列:(??不管是串行还是并行,队列都是按照FIFO的原则依次触发任务)

两个通用队列:

  • 串行队列:所有任务会在一条线程中执行(有可能是当前线程也有可能是新开辟的线程),并且一个任务执行完毕后,才开始执行下一个任务。(等待完成)

  • 并行队列:可以开启多条线程并行执行任务(但不一定会开启新的线程),并且当一个任务放到指定线程开始执行时,下一个任务就可以开始执行了。(等待发生)

两个特殊队列:

  • 主队列:系统为我们创建好的一个串行队列,牛逼之处在于它管理必须在主线程中执行的任务,属于有劳保的。

  • 全局队列:系统为我们创建好的一个并行队列,使用起来与我们自己创建的并行队列无本质差别。

任务执行方式

说完队列,相应的,任务除了管理,还得执行,要不然有钱不花,掉了白搭,并且在GCD中并不能直接开辟线程执行任务,所以在任务加入队列之后,GCD给出了两种执行方式——同步执行(sync)和异步执行(async)。

  • 同步执行:在当前线程执行任务,不会开辟新的线程。必须等到Block函数执行完毕后,dispatch函数才会返回。

  • 异步执行:可以在新的线程中执行任务,但不一定会开辟新的线程。dispatch函数会立即返回, 然后Block在后台异步执行。

上面的这些理论都是本人在无数被套路背后总结出来的血淋淋的经验,与君共享,但是这么写我猜你一定还是不明白,往下看,说不定有惊喜呢

4408163-c2a5cb92cc9a187c.jpg

任务队列组合方式

相信这个标题你看过无数次?是不是看完也不知道到底怎么用?这么巧,我也是,请相信下面这些肯定有你不知道并且想要的,我们从两个最直接的点切入:

1. 线程死锁

这个你是不是也看过无数次?哈哈哈!你是不是觉得我又要开始复制黏贴了?请往下看:

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

运行结果:

打印结果:

1
ThreadDemo[5615:874679] 1========{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. 这样不死锁

不如就写个最简单的:

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

打印结果:

1
2
3
ThreadDemo[5803:939324] 1========{number = 1, name = main}
ThreadDemo[5803:939324] 2========{number = 1, name = main}
ThreadDemo[5803:939324] 3========{number = 1, name = main}

之前有人问:顺序打印,没毛病,全在主线程执行,而且顺序执行,那它们一定是在主队列同步执行的啊!那为什么没有死锁?苹果的操作系统果然高深啊!

4408163-6298500c99aca848.jpg

其实这里有一个误区,那就是任务在主线程顺序执行就是主队列。其实一点关系都没有,如果当前在主线程,同步执行任务,不管在什么队列任务都是顺序执行。把所有任务都以异步执行的方式加入到主队列中,你会发现它们也是顺序执行的。而且任务的执行不一定非得撕扯白咧的加入到队列中才可以啊!

相信你知道上面的死锁情况后,你一定会手贱改成这样试试:

1
2
3
4
5
6
7
8
- (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]);
}

打印结果:

1
2
3
ThreadDemo[5830:947858] 1========{number = 1, name = main}
ThreadDemo[5830:947858] 2========{number = 1, name = main}
ThreadDemo[5830:947858] 3========{number = 1, name = main}

你发现正常执行了,并且是顺序执行的,你是不是若有所思,没错,你想的和我想的是一样的,和上诉情况一样,任务A在主队列中,但是任务B加入到了全局队列,这时候,任务A和任务B没有队列的约束,所以任务B就先执行喽,执行完毕之后函数返回,任务A接着执行。

我猜你一定手贱这么改过:

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

打印结果:

1
2
3
ThreadDemo[5911:962470] 1========{number = 1, name = main}
ThreadDemo[5911:962470] 3========{number = 1, name = main}
ThreadDemo[5911:962470] 2========{number = 1, name = main}

细心而帅气的你一定发现不是顺序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当任务A在执行时,任务B加入到了主队列,注意哦,是异步执行,所以dispatch函数不会等到Block执行完成才返回,dispatch函数返回后,那任务A可以继续执行,Block任务我们可以认为在下一帧顺序加入队列,并且默认无限下一帧执行。这就是为什么你看到2===... ...是最后输出的了。(??一个函数的有多个内部函数异步执行时,不会造成死锁的同时,任务A执行完毕后,这些异步执行的内部函数会顺序执行)。

我们说说队列与执行方式的搭配

上面说了系统自带的两个队列,下面我们来用自己创建的队列研究一下各种搭配情况。

我们先创建两个队列,并且测试方法都是在主线程中调用:

1
2
3
4
//串行队列
self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);
//并行队列
self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);

1. 串行队列 + 同步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(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]);
}

打印结果:

1
2
3
4
ThreadDemo[6735:1064390] 1========{number = 1, name = main}
ThreadDemo[6735:1064390] 2========{number = 1, name = main}
ThreadDemo[6735:1064390] 3========{number = 1, name = main}
ThreadDemo[6735:1064390] 4========{number = 1, name = main}

全部都在当前线程顺序执行,也就是说,同步执行不具备开辟新线程的能力。

2. 串行队列 + 异步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(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]);
}

打印结果:

1