iOS边城之多线程并发编程

参考:官方 OS X和iOS中的多线程技术

基本每门语言都有多线程这个技术点,多线程是为了实现并发执行,可以理解为一个系统进程是由一个或多个线程组成的。iOS中创建线程的方式简单到可以直接调用对象的方法来实现,下面我们来看看。

 

调用NSObject方法实现多线程

NSObject提供了以 performSelector为前缀的一系列方法。它允许用户在指定的线程、什么时间执行某个方法的调用。实现了多线程编程最简洁的方式


在当前线程中执行方法

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelectorwithObject:(id)object;
- (id)performSelector:(SEL)aSelectorwithObject:(id)object1 withObject:(id)object2;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;//delay为延迟多少秒调用
在主线程中执行方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait;

在指定线程中执行方法

- (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)waitmodes:(NSArray *)array;
- (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
在后台线程中执行方法

- (void)performSelectorInBackground:(SEL)aSelectorwithObject:(id)arg;
取消延迟调用的方法

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelectorobject:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

上述方法中需要注意的是

指定执行的方法(但传入的参数数量有限制,最多两个);

iOS默认的主线程为UI线程,更新UI的代码需要在主线程中执行,这点与Android相同,一般在非主线程需要进行UI更新,可以调用performSelectorOnMainThread来实现

如果指定在后台线程执行,则会自动创建一个线程去调用方法

如果使用了waitUntilDone:相关的方法来延迟调用方法,在期间如果不想调用了可以取消掉

 

NSThread 

以对象的方式来创建线程,它是线程的一个轻量级实现,有点类似于Java的Thread类。在执行一些简单任务时,NSThread很有用,比如线程休眠、线程同步、线程间通信

 

三种创建方式

 立即执行

[NSThreaddetachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:selfwithObject:nil]; 

 通过 start执行

NSThread* myThread = [[NSThread alloc]initWithTarget:self
                                      selector:@selector(myThreadMainMethod:)
                                      object:nil];
[myThread start];

通过继承方式

@interface TalkService :NSThread
@end
@implementationTalkService
 
-(void)main{
   //要执行的代码
   NSLog(@"哄");
}
@end
 
TalkService *talk = [TalkService new];
[talk start];


相关方法

开启线程 - (void)start;

停止线程    

- (void)cancel;  

+ (void)exit;与前者的区别在于,它是强制性的,不会给你线程清理任何资源的机会

休眠线程

+ (void)sleepUntilDate:(NSDate*)date;

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

获取主线程 [NSThread mainThread];

判断当前线程是否为主线程  [NSThread isMainThread]

获取当前线程 [NSThread currentThread];

 

线程间同步

涉及到线程间同步仍然需要配合使用NSLock,NSCondition 或者 @synchronized。

一个简单例子,两个人同时一起抢火车票,但是票只有最后一张了,看谁先抢到,当然这里的抢票规则完全靠哪个线程被先执行谁就先拿到票,话说我现在都还没抢到高铁票鄙视,过年又得骑车回家啦尴尬

@interface TestNSObjectThread : NSObject
@property(nonatomic,strong) NSLock *lock;
@property(nonatomic) int count;
-(void)test;
@end

@implementation TestNSObjectThread
-(void)test{
    self.lock = [[NSLock alloc] init];
    self.count = 1;
    
    NSThread *mokai = [[NSThread alloc] initWithTarget:self selector:
                        @selector(run) object:nil];
    [mokai setName:@"mokai"];
    [mokai start];
    
    NSThread *gongkai = [[NSThread alloc] initWithTarget:self selector:
                       @selector(run)  object:nil];
    [gongkai setName:@"gongkai"];
    [gongkai start];
    
    [NSThread sleepForTimeInterval:1];//因为为控制台项目,所以要防止主线程先执行完结束
}

-(void)run{
    [self.lock lock];//给lock锁上,这里可以通俗地理解为一间房一次只能进一个人,每个进去时把门反锁,出来时把门打开
    if(self.count > 0){
        self.count--;
        NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
    }
    [self.lock unlock];//解锁
}
@end

上述run方法也可以用@synchronized来代替

@synchronized(self){
    if(self.count > 0){
       self.count--;
        NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
    }
}

传进去的self为对象锁,可以理解为该对象做了一个标识“这个对象我在用,你等着!我用完你再用。”

好了,多运行多几次,控制台会随机的输出:

2015-01-06 11:38:18.119 TestMultithreading[1266:77053] mokai抢到一张火车票

2015-01-06 11:38:26.028 TestMultithreading[1269:77140] mokai抢到一张火车票

2015-01-06 11:38:32.259 TestMultithreading[1273:77236] mokai抢到一张火车票 

2015-01-06 11:38:38.450 TestMultithreading[1276:77311]gongkai抢到一张火车票

 

GCD 

参考:iOS多线程GCD

GCD(GrandCentral Dispatch),基于系统多核编程、C函数式编程、结合Blocks配合使用


简单一例

在上面我们可以通过performSelector系列方法来创建线程

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];

GCD中,我们是这样写的

dispatch_queue_t t = dispatch_get_main_queue();
dispatch_async(t, ^{
    [self run];
});
虽然看起来没前者那么简洁,尤其对于面向对象开发者来说,函数式编程怎么看怎么不爽。但是官方说了, GCD 是基于多核的,性能杆杆的,强烈建议能用 GCD 解决尽量使用 GCD 解决。不管你用不用,反正我平常都是用的本文第一种“调用NSObject方法实现多线程”

分发队列

执行任务的容器,可以提供各种自定义操作,如并行,串行

GCD中有三种队列:

1、主线程队列

主线程队列中的任务会在应用的主线程中执行。一般用于UI更新操作

dispatch_queue_t queue =dispatch_get_main_queue();

 

2、并行队列

串行分发队列又被称为全局分发队列,由系统创建三个不同优先级的dispatchqueue,也按顺序执行队列中的任务,但是顺序开始的多个任务会并发同时执行。并发分发队列常用于管理并发任务。

dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

 

3串行队列

串行分发队列又被称为私有分发队列,按顺序执行队列中的任务,且同一时间只执行一个任务。但各个串行队列之间是并发的。

当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。另外,通过这种方式创建的队列需要我们手动调用dispatch_release释放

dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue",NULL);//注意,此处传的是C字符不是@""

 

任务执行

同步执行,主线程会被阻塞,可以理解为Block的执行是主线程上进行的

dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);

异步执行,与同步执行相反,主线程不会被阻塞

dispatch_async(dispatch_queue_t queue,dispatch_block_t block);

 

加载网络图片的例子

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSData * data = [NSData dataWithContentsOfURL:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
    UIImage * image = [UIImageimageWithData:data];
    //加载UI的操作,一般放在主线程进行
    dispatch_async(dispatch_get_main_queue(), ^{
       self.imageView.image = image;
    });
});

实际中我们会使用了嵌套GCD来完成功能,先用后台线程执行任务,然后调用主线程执行UI更新,注意,GCD的嵌套也不能乱嵌,否则会出现死锁情况,像如下代码,会立即死锁

dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue", NULL);
dispatch_sync(queue, ^{
    NSLog(@"1");
    dispatch_sync(queue, ^{
       NSLog(@"2");
    });
});

常见场景

 1、在主线程执行代码

dispatch_async(dispatch_get_main_queue(),^{
    // updateui
});
2 、延时执行
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));//表示当前时间后的5秒
    dispatch_after(time, dispatch_get_main_queue(), ^(void){
       //执行代码
    });
3、使用 dispatch_once 实现线程安全单一执行要求

GCD 的 dispatch_once能够在保证自应用运行到暂停 block只执行一次。以下是编程中经常用到的单例模式,这也是现在OC中推荐的一种方法

+ (id)sharedInstance {
    static TestNSObjectThread*sharedInstance = nil;
   static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
       sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}


操作队列NSOperation  

参考:iOS多线程编程之NSOperation和NSOperationQueue的使用

NSOperation,类似于javaRunable接口,设计用来扩展的,自带有二个扩展,NSInvocationOperation(基于Selector调用)、NSBlockOperation(基于Block调用),如果想自定义只需重写main方法即可(java的run方法),使用与前面介绍的NSThread差不多NSOperationQueue队列,用来管理执行Operations的容器

 

NSInvocationOperation 

NSInvocationOperation*operation = [[NSInvocationOperation alloc]initWithTarget:self
                                                                    selector:@selector(downloadImage:)
                                                                     object:kURL];
 
NSOperationQueue *queue= [[NSOperationQueue alloc]init];
[queue addOperation:operation];

NSBlockOperation 

NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{
   //代码...
}];
NSOperationQueue *queue= [[NSOperationQueue alloc]init];
[queue addOperation:operation];

细粒化控制

通过NSOperationNSOperationQueue的配合使用,可实现复杂的流程化任务。


1、NSOperation依赖对象,通过addDependency来添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系

唯一的限制是不能创建环形依赖,比如A依赖BB依赖A,这是错误的

依赖关系会影响到NSOperation对象在queue中的执行顺序,默认是按照添加顺序执行的

 

2、执行修改Operations的执行顺序

依赖关系相对优先级:优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operationqueue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。

 

3、设置队列的最大并发操作数量

setMaxConcurrentOperationCount:默认为

如果设置为1就表示为串行化operationqueue

 

4、等待Options完成

 [operation waitUntilFinished]; //单个operation
 [queue waitUntilAllOperationsAreFinished];  //queue中全部operation

5、暂停和继续queue

[queue setSuspended:YES]; // 暂停queue  
queue setSuspended:NO]; // 继续queue  
 

重复任务NSTimer、NSRunLoop

有时应用中需要用到定时刷新的功能,如抢票APP需要频繁地请求12306,用之前的GCD也能实现效果,但是过程太痛苦了,那么苹果提供了NSTimer类来帮助我们完成重复任务。

NSTimerNSOperation设计一样,可以通过自身来启动任务,也可以放到NSRunLoop里面执行

NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行
[timer fire];//立即执行

通过这种方式创建的NSTimer结果只会调用一次refresh方法,无论repeatsYES还是NO都会如此,如果想重复执行则需要添加到NSRunLoop中,如下

NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行
NSRunLoop *loop = [NSRunLoop mainRunLoop];
[loop addTimer:timer forMode:NSDefaultRunLoopMode];//增加到运行中
[loop run];//启动执行

这里的代码每隔1秒就会调用一次log方法,其实NSTimer提供了一个快捷方法,上述代码也可以写成

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];
[timer fire];

停止任务可以通过[timer invalidate]来执行,需要注意的是NSTimer没有提供暂停的接口,如果想实现暂停的功能可以先invalidate然后重新启动一个NSTimer


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在网络编程中,多线程编程是一种常用的技术,可以提高程序的并发性和性能。下面是一些关于多线程编程的常用方法和注意事项: 1. NSThread:NSThread是iOS中最底层的线程类,它可以通过类方法或实例方法来创建线程。使用NSThread可以设置线程的名称、优先级,以及控制线程的睡眠和退出等操作。 2. 线程调度:在多线程编程中,多个线程会并发运行,但线程的执行顺序是由CPU调度器决定的,程序员无法控制。多个线程会同时竞争CPU资源,谁先抢到资源谁就先执行,所以多线程的执行顺序是随机的。 3. 多线程的创建:在iOS开发中,常用的多线程编程方式有三种:NSThread、GCD和NSOperation。NSThread是最底层的线程类,可以直接操作线程的各种属性和方法。GCD(Grand Central Dispatch)提供了一种高效的并发编程模型,可以通过队列来管理任务的执行。NSOperation是基于GCD的更高层次的封装,提供了更多的控制和管理线程的功能。 4. 线程的创建顺序:在多线程编程中,并不能保证哪个线程会先运行,即无法确定新创建的线程或调用线程哪个会先执行。新创建的线程可以访问进程的地址空间,并继承调用线程的浮点环境和信号屏蔽字,但挂起信号集会被清除。 总结来说,多线程编程是一种提高程序并发性和性能的技术,在网络编程中尤为重要。通过使用NSThread、GCD或NSOperation等方法,可以实现多线程的创建和管理。然而,程序员无法控制线程的执行顺序,因为线程的调度是由CPU调度器决定的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [IOS多线程基础(OC)](https://blog.csdn.net/yong_19930826/article/details/105857055)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [UNIX环境高级编程笔记](https://blog.csdn.net/w_x_myself/article/details/128613534)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值