iOS开发中多线程的应用

iOS开发中多线程的应用,第1张

一、前言

iOS开发中可以使用的多线程方案有:

pthread:纯C语言实现的跨平台多线程解决方案,使用难度大。在iOS平台不推荐使用。NSThread:iOS平台下面向对象的线程对象,使用相对容易,但需要开发者来管理其生命周期,而且多个线程同步需要配合NSLock等锁一起使用。Grand Central Dispatch (GCD):iOS平台下纯C的API的多线程解决方案,隐藏了很多技术细节,比如不用手动创建线程,不用管理线程生命周期,不用使用各种锁,让开发人员可以专注于业务逻辑本身。NSOperationQueue:基于GCD的面向对象封装,对于具有依赖关系的 *** 作,以及取消队列中任务只需要比较简单的设置。 二、实际使用 1.NSThread 1.1 显示创建线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  //创建线程。
[thread start];  // 启动线程
// 线程一启动,就会在线程thread中执行self的run方法
1.2 匿名创建并调用
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
//或者:
[self performSelectorInBackground:@selector(run) withObject:nil];
1.3 参数的意义 selector:线程执行的方法,这个selector只能有一个参数,而且不能有返回值。 target:selector消息发送的对象。argument:传输给target的唯一参数,也可以是nil。

第一种方式是先创建线程对象,然后再运行线程 *** 作,在运行线程 *** 作前可以设置线程的优先级(qualityOfService)等线程信息 ,第二种方式会直接创建线程并且开始运行线程。

1.4 线程的生命周期

启动线程
- (void)start; 
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 进入阻塞状态

强制停止线程
+ (void)exit;
// 进入死亡状态

注意:一旦线程停止(死亡)了,就不能再次开启任务,只能重新创建一个线程对象。

1.5 线程安全

NSThread对象在访问共享资源的时候,确切说是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,很容易引发数据错乱和数据安全问题。

解决线程安全问题可以通过互斥锁(@synchronized),原子属性(atomic)等方式,不过这两种方式都比较消耗CPU资源。

1.6 线程间通信

线程间通信的体现:1个线程传递数据给另1个线程,在1个线程中执行完特定任务后,转到另1个线程继续执行任务。线程间通信常用方法:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;//在主线程中运行方法,wait表示是否阻塞这个方法的调用,如果为YES则等待主线程中运行方法结束。一般可用于在子线程中调用UI方法。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;//在指定线程中执行,但该线程必须具备run loop。

tips:如果需要在指定的子线程执行方法,需要该指定的子线程开启runloop保活。在子线程的运行方法中加入这两行代码:

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
1.7 广泛用途

一般来说是用于判断是否处于主线程[NSThread isMainThread],还有获取主线程[NSThread mainThread],再有的话就是用于有个耗时任务例如加载网络图片需要放在后台执行,加载完成后转到主线程显示图片。多线程环境下使用比较麻烦,一般不用此类。

2. GCD

在GCD中不用处理和线程相关的内容,只需要选择好合适的队列(串行或并行)以及确定好任务块同步还是异步执行就行,系统内部会自动维护一个线程池来完成队列中的任务。

2.1 创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);

/**
同步串行任务
*/
dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"正在%@执行同步串行任务1",[NSThread isMainThread] ? @"主线程" : @"子线程");
    });
NSLog(@"任务2");

/********************************************************************************
//控制台打印结果:
正在子线程执行同步串行任务1
任务2
********************************************************************************/


/**
异步串行任务
*/
dispatch_queue_t serialQueue = dispatch_queue_create("com.serialQueue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"正在%@执行异步串行任务1",[NSThread isMainThread] ? @"主线程" : @"子线程");
    });
NSLog(@"任务2");

/********************************************************************************
//控制台打印结果:
任务2
正在子线程执行异步串行任务1
********************************************************************************/

由此可知,往串行队列添加任务,无论是同步还是异步,都会创建子线程执行任务。但是异步执行不会阻塞当前线程,所以“任务2”会先输出。 

2.2 创建并行队列 
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
   dispatch_sync(concurrentQueue, ^{
       NSLog(@"任务1在%@", [NSThread currentThread]);
    });
}
    
NSLog(@"主线程任务");

/********************************************************************************
//控制台打印结果:
任务1在{number = 7, name = (null)}
任务1在{number = 7, name = (null)}
任务1在{number = 7, name = (null)}
任务1在{number = 7, name = (null)}
任务1在{number = 7, name = (null)}
主线程任务
********************************************************************************/

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
    dispatch_async(concurrentQueue, ^{
        NSLog(@"任务1在%@", [NSThread currentThread]);
    });
}
    
NSLog(@"主线程任务");

/********************************************************************************
//控制台打印结果:
主线程任务
任务1在{number = 6, name = (null)}
任务1在{number = 7, name = (null)}
任务1在{number = 4, name = (null)}
任务1在{number = 6, name = (null)}
任务1在{number = 7, name = (null)}
********************************************************************************/

 由此可知,往并行队列添加任务,如果是全都以同步方式添加任务,那么系统只会使用一个子线程来处理,并且会阻塞当前线程,等待队列中任务都执行完毕才会往下执行。如果是全都以异步方式添加任务,那么系统就会创建多个子线程同时处理这些任务,并且不会阻塞当前线程,子线程在处理队列任务的同时当前线程也在继续执行下面的代码。

2.3 创建任务组

任务组的作用呢,就是可以在队列中任务执行完毕以后,可以监听到任务完毕事件并在开发人员指定的线程中做接下来的 *** 作,示例代码:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 5; i++) {
    dispatch_group_enter(group);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"任务%d在%@",i, [NSThread currentThread]);
        dispatch_group_leave(group);
    });
}

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"更新UI");
});


NSLog(@"主线程任务");


/********************************************************************************
//控制台打印结果:
主线程任务
任务0在{number = 5, name = (null)}
任务1在{number = 7, name = (null)}
任务2在{number = 6, name = (null)}
任务4在{number = 5, name = (null)}
任务3在{number = 4, name = (null)}
更新UI
********************************************************************************/
 2.4 信号量
//创建信号量
dispatch_semaphore_t lock = dispatch_semaphore_create(0);
//信号量加一
dispatch_semaphore_signal(lock);
//阻塞函数,会将信号量减一,减完一之后若信号量大于等于0,则不阻塞,否则阻塞当前线程
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        2.4.1 信号量的应用

        1.控制并发队列的最大并发数,以防止创建太多线程消耗过多系统资源。

//定义最大并发数
#define MAX_ConCurrent_Count 5
dispatch_semaphore_t signal = dispatch_semaphore_create(MAX_ConCurrent_count);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 200; i++) {
    dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
    dispatch_async(concurrentQueue, ^{
        [NSThread sleepForTimeInterval:2];
        dispatch_semaphore_signal(signal);
    });
}

        2.作为锁使用,使得多线程环境下对共享资源访问为线程安全的。

NSMutableSet *threadSets = [[NSMutableSet alloc] init];
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);

for (int i = 0; i < 200; i++) {
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        //保证了同时最多只有一个线程进入这里
        [threadSets addObject:[NSThread currentThread]];
        dispatch_semaphore_signal(lock);
    });
}
 2.5 线程安全单例

OC中创建唯一单例的保险做法https://blog.csdn.net/fang20180409/article/details/117691109

2.6 GCD定时器 
/**
   一次性定时器,在指定时间后只执行一次
*/
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
dispatch_after(timer, dispatch_get_main_queue(), ^(void){
    NSLog(@"GCD-----%@",[NSThread currentThread]);
});


/**
   可重复定时器,每隔一段时间会执行一次
*/
@property (nonatomic ,strong)dispatch_source_t timer;

//0.创建队列
dispatch_queue_t queue = dispatch_get_main_queue();
//1.创建GCD中的定时器
/*
第一个参数:创建source的类型 DISPATCH_SOURCE_TYPE_TIMER:定时器
第二个参数:0
第三个参数:0
第四个参数:队列
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//2.设置时间等
/*
第一个参数:定时器对象
第二个参数:DISPATCH_TIME_NOW 表示从现在开始计时
第三个参数:间隔时间 GCD里面的时间最小单位为 纳秒
第四个参数:精准度(表示允许的误差,0表示绝对精准)
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

//3.要调用的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"GCD-----%@",[NSThread currentThread]);
});

//4.开始执行
dispatch_resume(timer);

//5.必须强引用,否则定时器不起作用
self.timer = timer;

//如果想要取消定时器
//dispatch_source_cancel(self.timer);
2.7 栅栏函数
/**
栅栏函数有俩个,dispatch_barrier_sync和dispatch_barrier_async.该函数使用时要特别注意必须只能适用于自定义并发队列中,全局队列以及串行队列都是无效的。它的作用就是相当于设置依赖关系,栅栏函数之前添加的任务执行完毕以后,才可以执行栅栏函数之后添加的任务。
*/
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentQueue.test", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"非本队列任务1");
dispatch_async(concurrentQueue, ^{
    NSLog(@"任务1");
});
dispatch_barrier_sync(concurrentQueue, ^{
    NSLog(@"同步栅栏方法");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"任务2");
});
dispatch_barrier_async(concurrentQueue, ^{
    NSLog(@"异步栅栏方法");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"任务3");
});
NSLog(@"非本队列任务2");



/********************************************************************************
//控制台打印结果:
非本队列任务1
任务1
同步栅栏方法
任务2
非本队列任务2
异步栅栏方法
任务3
********************************************************************************/
2.8 GCD任务块

GCD中的任务块就是dispatch_block,可以监听该任务块是否执行完成,以及可以在该任务块执行完毕之后做点别的事情。

dispatch_block_t task = dispatch_block_create(0, ^{
    NSLog(@"正在执行block耗时任务");
    [NSThread sleepForTimeInterval:2];
});
dispatch_queue_t concurrentQueue = dispatch_queue_create("111", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, task);

//可以通过该函数取消未开始执行的任务,但无法取消正在执行的任务
//dispatch_block_cancel(task);

//阻塞当前线程,直到任务块执行完毕。返回值等于0表示执行完成,大于0表示任务超时未完成。
long time = dispatch_block_wait(task, DISPATCH_TIME_FOREVER);
if (time == 0) {
    NSLog(@"任务执行完毕");
}
//不会阻塞当前线程,在任务完成以后监听到block变量的通知,并可以选择在主队列中更新UI或进行其他 *** 作
dispatch_block_notify(task, dispatch_get_main_queue(), ^{
    NSLog(@"去更新UI");
});


/********************************************************************************
//控制台打印结果:
正在执行block耗时任务
任务执行完毕
去更新UI
********************************************************************************/
3. NSOperationQueue NSOperationQueue类是基于GCD的面向对象封装,功能强大。相对于GCD来说更加简单易用,代码可读性更高。NSOperationQueue执行任务一般都是异步执行的,即不会阻塞当前线程。如果想要等待任务执行完毕再往下执行,NSOperation可以使用waitUntilFinished方法或者NSOperationQueue可以使用waitUntilAllOperationsAreFinished方法。NSOperation类是抽象类,本身并不能创建任务。要想创建任务可以使用它的子类NSBlockOperation和NSInvocationOperation,或者是自定义NSOperation的子类,重写main方法就行了。NSOperation *** 作类有个start方法,在创建任务后,可以不将 *** 作添加进 *** 作队列而直接start也可以。这样可以让 *** 作同步执行在当前线程上。NSBlockOperation有个addExecutionBlock:方法是让该代码块单独执行在一条子线程里面的,即便该 *** 作对象添加进了主队列也是这样。不过当NSBlockOperation在创建时如果没有添加任务的话(使用init或者new方法创建时),使用addExecutionBlock:方法添加进入的第一个任务一定是在当前线程执行的。 3.1 设置最大并发数
/**
 *** 作队列并没有并行和串行的概念,最大并发数maxConcurrentOperationCount默认为-1。
设置为1则相当于串行队列,设置为大于1则相当于并行队列。
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置最大并发数为10,实际创建几条线程由系统决定,但肯定不会超过10条
queue.maxConcurrentOperationCount = 10;
3.2 设置任务依赖关系
/**
 *** 作依赖关系必须在 *** 作开始执行之前添加,否则不会有效果
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任务1--%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任务2--%@", [NSThread currentThread]);
}];
[task1 addDependency:task2];

[queue addOperation:task1];
[queue addOperation:task2];


/********************************************************************************
//控制台打印结果:
任务2--{number = 2, name = (null)}
任务1--{number = 2, name = (null)}
********************************************************************************/
3.3 添加栅栏任务
/**
效果和GCD是一样的,可以把前面的任务和后面的任务分隔开
*/
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;

NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任务1--%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"任务2--%@", [NSThread currentThread]);
}];

[queue addOperation:task1];
[queue addBarrierBlock:^{
    NSLog(@"栅栏任务--%@", [NSThread currentThread]);
}];
[queue addOperation:task2];


/********************************************************************************
//控制台打印结果:
任务1--{number = 6, name = (null)}
栅栏任务--{number = 6, name = (null)}
任务2--{number = 6, name = (null)}
********************************************************************************/
3.4 KVO监听队列中任务数量
/**
监听NSOperationQueue的operationCount属性,即可观察到队列中剩余任务的数量。由运行结果可知,operationCount属性更新是在一个任务执行完毕以后。
*/
- (void)main {
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;

    for (int i =0; i<5; i++) {
    NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"任务%d--%@",i, [NSThread currentThread]);
    }];
    [queue addOperation:task1];
}
    
    [queue addObserver:self forKeyPath:@"operationCount" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"operationCount"]) {
        NSLog(@"%@", change[NSKeyValueChangeNewKey]);
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}


/********************************************************************************
//控制台打印结果:
任务0--{number = 7, name = (null)}
4
任务1--{number = 7, name = (null)}
3
任务2--{number = 7, name = (null)}
2
任务3--{number = 7, name = (null)}
1
任务4--{number = 7, name = (null)}
0
********************************************************************************/
3.5 自定义NSOperation

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/993562.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存