谈一谈iOS中的多线程

来源:互联网 发布:安卓网络聊天室破解版 编辑:程序博客网 时间:2024/06/05 06:15


首先,讲到多线程的话,首先要明确几个基本概念。


进程是并发执行的程序在执行过程中分配和管理资源的基本单位。

线程:是指程序在执行时,能执行代码的一个执行单位(CPU调度的一个基本单位)。线程是进程的一部分,又被称为轻权进程或轻量级进程。

多线程:多个线程并发执行的技术。


多线程的作用主要有:1、可以减少程序的相应时间;2、线程的创建和切换开销比进程小,提高CPU利用率;3、简化程序结构,是程序便于维护。


举个栗子说明一下:

我们可以把在桌子上吃饭看做一个进程,那么可以把吃饭看做一个线程(这里都没有主语,把“人”给省略了)。在这样的前提下,我们可以得到以下结果:

单进程:在一张桌子上吃饭单线程:一个人吃饭

多进程:多张桌子上都在吃饭多线程:多个人都在吃饭

单进程单线程:一个人在一张桌子上吃饭;

单进程多线程:多个人在一张桌子上吃饭;

多进程单线程(有多个进程每个进程中都是单线程):有多个人,每个人都拥有一张桌子,每个人都在自己的桌子上吃饭。


现在有个需求:实现一家人吃饭。

如果使用单进程单线程来实现:大家在一张桌子上吃饭,但是必须一个一个的吃,前一个人吃完,才能下一个人吃。如果你是单身狗,可以用这样的方式吃饭

如果使用多进程单线程来实现:大家每个人都配一张桌子,各自在自己的桌子上吃饭,那么问题来了,就是别人吃不到你桌子上的菜(不是完全没办法,可以起身去夹菜,但也会有进程之间通信不便的情况)。如果家里人不是很多,至于几个人吃饭还要搞好几张桌子,然后每张桌子搞几个相同的菜吗?

如果使用单进程多线程来实现:一张桌子,大家在同一张桌子上吃饭,每个菜大家都可以吃,但是这种情况下有个问题需要注意,如某盘菜特别好吃,大家都在吃这个菜(多个线程频繁获取共享的资源),那么可能会发生争抢冲突,实现过程中要协调好。

上述实例(借鉴别人的说法),一般情况下当然是用单进程多线程比较合适,当然并不是非要使用单进程,比如你家是个大家族,每次吃饭20多个人,一张桌子也坐不下的情况,或者你家有个小宝宝,那吃饭的时候给宝宝一个餐桌椅的情况,再或者就是有钱,就是任性,就是要每个人一张桌子的情况等;这些情况下用多进程也是没有问题的。


iOS中的4套多线程方案

1.Pthreads(不常用)

基于C语言的框架,是一套在很多操作系统上都通用的API,移植性强(。。。),在iOS中调用Pthreads时要用C语言函数,并且需要手动管理生命周期(这是重点,看到这个,就不想用了)。知道有这么个东西,不要被人拿这个秀一脸就行了。搬运代码:

//记得导入pthread头文件#import <pthread.h>- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    pthread_t thread;    //创建一个线程并自动执行    pthread_create(&thread, NULL, start, NULL);}void *start(void *data) {    NSLog(@"%@", [NSThread currentThread]);    return NULL;}

2.NSThread (不常用)

NSThread经过了苹果封装,完全面向对象,但是它的生命周期还是需要我们手动管理(。。。),不过NSThread中有我们可以用到的函数,当调试程序需要获取当前线程及线程的各个属性时,可以通过[NSThread currentThread]来获取当前线程。

 // 创建  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];  // 启动  [thread start];  //创建并自动启动  [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

列举NSThread常用方法:

//取消线程- (void)cancel;//启动线程- (void)start;//判断某个线程的状态的属性@property (readonly, getter=isExecuting) BOOL executing;@property (readonly, getter=isFinished) BOOL finished;@property (readonly, getter=isCancelled) BOOL cancelled;//设置和获取线程名字-(void)setName:(NSString *)n;-(NSString *)name;//获取当前线程信息+ (NSThread *)currentThread;//获取主线程信息+ (NSThread *)mainThread;//使当前线程暂停一段时间,或者暂停到某个时刻+ (void)sleepForTimeInterval:(NSTimeInterval)time;+ (void)sleepUntilDate:(NSDate *)date;

3.GCD

GCD是苹果开发的一个多核编程的较新解决方案,是一个替代如NSThread等技术的高效和强大技术。会自动管理线程的生命周期(美滋滋),使用C语言来实现调度,并且会使用到Block(闭包),让GCD的使用更加方便和灵活。

要说GCD就不得不说两个非常重要的概念:任务 和 队列

· 任务:即操作,在GCD中就是Block的内容,所以添加任务非常方便,任务有两种执行方式,同步执行 和 异步执行。

同步和异步的区别在与同步操作会阻塞当前线程并等待任务(Block)执行完毕,当前线程才会继续运行。而异步缺不会。

·队列:用于存放任务,遵循先进先出(FIFO)原则。分为 串行队列 和 并行队列。

串行:取出一个执行一个,完事再取下一个。

并行:不同于串行的FIFO,并行队列的FIFO会取出一个任务加入到其他线程,每个任务依次取出,因为取出任务的耗时很少,所以会感觉任务在同时进行。


获取队列:

主队列:dispatch_get_main_queue()

获取全局并行队列:只要是并发任务一般都加入到这个队列,这是系统提供的。

dispatch_queue_t queue = dispatch_get_global-queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)

自己创建的队列:其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空。

  //串行队列  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);  //并行队列  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

创建任务

同步任务:会阻塞当前线程

dispatch_sync(<#queue#>, ^{ NSLog(@"%@", [NSThread currentThread]);});

异步任务:不会阻塞当前线程

dispatch_async(<#queue#>, ^{ NSLog(@"%@", [NSThread currentThread]);});

· 队列组:队列组可以将很多队列添加到一个组里,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们。上代码:

//1.创建队列组dispatch_group_t group = dispatch_group_create();//2.创建队列dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//3.多次使用队列组的方法执行任务, 只有异步方法//3.1.执行3次循环dispatch_group_async(group, queue, ^{    for (NSInteger i = 0; i < 3; i++) {        NSLog(@"group-01 - %@", [NSThread currentThread]);    }});//3.2.主队列执行8次循环dispatch_group_async(group, dispatch_get_main_queue(), ^{    for (NSInteger i = 0; i < 8; i++) {        NSLog(@"group-02 - %@", [NSThread currentThread]);    }});//3.3.执行5次循环dispatch_group_async(group, queue, ^{    for (NSInteger i = 0; i < 5; i++) {        NSLog(@"group-03 - %@", [NSThread currentThread]);    }});//4.都完成后会自动通知dispatch_group_notify(group, dispatch_get_main_queue(), ^{    NSLog(@"完成 - %@", [NSThread currentThread]);});

GCD中还有一个很常用的函数:dispatch_barrier_async

这个方法重点是传入的queue,当传入的queue是通过 DISPATCH_QUEUE_CONCURRENT 参数自己创建的queue时,这个方法会阻塞这个queue(注意是阻塞queue,而不是阻塞当前线程),一直等在这个queue中排在它前面的任务都执行完成才会开始执行自己,自己执行完毕,再回取消阻塞,使这个queue中排在它后面的任务继续执行。如果传入的是其他queue,那么它就和dispatch_async一样了。

假设我们原先有6个任务要执行,我们现在要插入一个任务0,这个任务0要在1、2、3都并发执行完了之后才能执行,而4、5、6号任务要在这个任务0结束后才允许并发。对于这样一种需求,很多朋友的第一反应就是用个group就解决了。确实如此,但是系统提供了一种更加简单地方法,那就是dispatch_barrier_async,我们只要按照前面所述的顺序将任务分配到队列就OK,剩下的都不用管了。dispatch_barrier_async的参数跟dispatch_async一模一样的。上代码:

dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);    dispatch_async(queue, ^{        NSLog(@"任务1");    });    dispatch_async(queue, ^{        NSLog(@"任务2");    });    dispatch_async(queue, ^{        NSLog(@"任务3");    });    dispatch_barrier_async(queue, ^{        for (int i = 0; i < 10; i++) {            NSLog(@"任务0-%d",i);        }        NSLog(@"任务0完成");    });    dispatch_async(queue, ^{        NSLog(@"任务4");    });    dispatch_async(queue, ^{        NSLog(@"任务5");    });    dispatch_async(queue, ^{        NSLog(@"任务6");    });
看看运行结果:

可以看出前三个任务和后三个任务都分别并发完成了,而且后3个任务必须要等到任务0完成才能进行。

关于GCD知识点和常用方法就到这里。GCD是iOS中非常重要的多线程技术,也是开发中最常见的多线程方案了。


4.NSOperation和NSOperationQueue

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分别对应 GCD 的 任务 和 队列 。操作步骤也很好理解:
1. 将要执行的任务封装到一个 NSOperation 对象中。
2. 将此任务添加到一个 NSOperationQueue 对象中。
然后系统就会自动在执行任务。

添加任务

值得说明的是,NSOperation 只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation 和 NSBlockOperation 。创建一个 Operation 后,需要调用 start 方法来启动任务,它会 默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel 方法即可。

NSInvocationOperation : 需要传入一个方法名。

//1.创建NSInvocationOperation对象  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];  //2.开始执行  [operation start];

NSBlockOperation

//1.创建NSBlockOperation对象  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{      NSLog(@"%@", [NSThread currentThread]);  }];  //2.开始任务  [operation start];

  NSBlockOperation 有一个方法:addExecutionBlock: ,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务 会并发执行,它会 在主线程和其它的多个线程 执行这些任务。这个方法必须在operation start 之前添加。


获取队列

主队列:[NSOperationQueue mainQueue]

其他队列:其他队列的任务会在其他线程并行执行。[[NSOperationQueue alloc] init]

将 NSOperationQueue 与 GCD的队列 相比较就会发现,这里没有串行队列NSOperationQueue有个最大并发数的属性:maxConcurrentOperationCount,用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,NSOperationQueue就是串行了!和GCD类似,NSOperationQueue 还有一个添加任务的方法,- (void)addOperationWithBlock:(void (^)(void))block; ,这样就可以添加一个任务到队列中了,十分方便。


NSOperation 有一个非常实用的功能,那就是 添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

//1.任务一:下载图片NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"下载图片 - %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:1.0];}];//2.任务二:打水印NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"打水印   - %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:1.0];}];//3.任务三:上传图片NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{    NSLog(@"上传图片 - %@", [NSThread currentThread]);    [NSThread sleepForTimeInterval:1.0];}];//4.设置依赖[operation2 addDependency:operation1];      //任务二依赖任务一[operation3 addDependency:operation2];      //任务三依赖任务二//5.创建队列并加入任务NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

注意:不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。可以使用 removeDependency 来解除依赖关系。可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。


iOS中的多线程差不多就这样了。这只是对多线程做了简单的解释和概括,不过暂时也就先这样了。

原创粉丝点击