iOS多线程技术

iOS多线程技术(多数用GCD)

(为了提高用户体验,把所有耗时的事情放到对等线程中,主线程中只做刷新UI界面的事情)

pthread 是 POSIX兼容的创建线程函数,是底层函数。

iOS有三种多线程编程技术:NSThread 、NSOperation 、GCD(实现负载均衡,多核cpu适合用它,以后的趋势是用GCD),它们的抽象程度由低到高,越高的使用起来越简单。

NSThread

显示调用NSThread类

  1. 类方法(创建完后立即启动线程)

    //创建一个线程,用NSThread这个类方法向 Target后面这个参数 发消息,这个对象在另外的线程中执行 @selector里面的方法  
    [NSThread detachNewThreadSelector:@selector(doSomething:)
    toTarget:self withObject:@"参数"];
  2. 实例方法(可以控制启动时间)

    NSThread *thread = [[NSThread alloc]initWithTarget:self
    selector:@selector(doSomething:) object:@"参数"];
    [thread start];

隐式调用

  1. 开启后台线程

     [“对象” performSelectorInBackground:@selector(doSomething:)
    withObject:@"参数"];
  2. 在主线程中运行(主线程是UI线程)(所有耗时的事情不能在主线程中进行)

    [“对象” performSelectorOnMainThread:@selector(doSomething:)
    withObject:@"参数" waitUntilDone:YES];
  3. 在指定线程中执行,但该线程必须具备run loop(需要“同步机制”的时候会用到)

    //对后一个参数表示 等待结束
      [“对象” performSelector::@selector(doSomething:) onThread:thread
    withObject:@"参数" waitUntilDone:YES];
    

常见NSThread的方法

```
+ (NSThread *)currentThread; //获得当前线程
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //线程休眠
+ (NSThread *)mainThread; //主线程(UI线程)
- (BOOL)isMainThread; //当前线程是否为主线程
+ (BOOL)isMainThread; //用NSThread类在某个线程中调用这个方法,判断是否为主线程
- (BOOL)isExecuting; //线程是否正在运行
- (BOOL)isFinished; //线程是否已经结束
```

NSOperation

(是一个抽象类,不能直接使用,要用他的子类,UIControl也是抽象类)

NSInvocationOperation

```
//创建一个队列
NSOperationQueue queue = [[NSOperationQueue alloc]init];
//创建子任务,定义子任务必须是NSOperation的子类
NSInvocationOperation operation = [[NSInvocationOperation alloc]initWithTarget:“消息接收者” selector:@selector(doSomething:) object:@"参数"];
//把当前任务添加到队列后,自动开启线程
[queue addOperation:operation];
```

NSBlockOperation

```
//创建一个队列
NSOperationQueue queue = [[NSOperationQueue alloc]init];
//创建NSBlockOperation对象
NSBlockOperation operation = [NSBlockOperation blockOperationWithBlock:^{[self doSomething];}]
//加入队列  
[queque addOperation:operation];
```

GCD(苹果开发的一个多核编程的解决方法)

一些重要的概念:

串行:一个事件(线程)被执行,另外的事件要等到它完成之后再执(队列的术语)
并发:一个事件(线程)被开启后,第二个事件就可以开启,不用等待前面事件的完成(队列的术语)(cpu还是单核情况下,对线程进行快速切换)
队列只有串行和并发两种形式

并行:两个事件(线程)同时进行,依赖于多个cpu(多核系统)
如果是并行,那么一定是并发,但是若果是并发,不一定是并行

同步:程序中我们调用一个了函数(线程),要等待它执行完了之后再执行下面的语句(派发的术语)
异步:程序中我们调用一个了函数(线程),不等待它执行完了之后就执行下面的语句(派发的术语)

临界区:是一段代码,它可能进行并发访问(多个进程同时访问这段代码)(临界区需要加锁)

竞态条件:多个进程对共享的数据进行读或写的操作时,最终的结果取决于这些进程的执行顺序

死锁:多个进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。(建议在多线程中访问相同的多个数据时,按照相同的顺序进行)

线程安全:线程安全就是多线程访问同一代码,不会产生不确定的结果(与类相关,这个类在创建的时候要不要加锁)。不可变的对象一般是线程安全的,主线程用来处理事件。对于苹果官方的类,可变的类是线程不安全的(例如:NSMutableString,NSMutableArray)
iOS开发环境:Cocoa touch

GCD的工作原理

通过队列的形式来实现线程(系统提供一个主队列(串行),四个并发队列),每一个队列就是一个block块,建立在任务并行执行的线程池模式的基础上。

GCD的底层依然是用线程实现。

GCD中的队列称为dispatch queue(派遣队列),它可以保证先进来的任务先得到执行。
dispatch queue(分发队列)分为下面三种:

  1. Serial(串行) ,
又称为private dispatch queues,同时只执行一个任务串行。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
  2. Concurrent(并发) ,
又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
  3. Main dispatch queue ,
它是全局可用的串行队列,它是在应用程序主线程上执行任务的。

创建队列

第一个参数代表队列的标识符(是一个c风格的字符串),第二个参数代表队列的类型(DISPATCH_QUEUE_CONCURRENT创建并发队列,DISPATCH_QUEUE_SERIAL是创建串行队列)(返回值是dispatch_queue_t类型)   
dispatch_queue_t dispatch_queue_create( const char *label dispatch_queue_attr_t attr);

获取系统定义的全局并发队列

第一个参数表示优先级(优先级最好不要随意更改,否则会造成优先级反转),第二个参数目前没有使用,设置为0   
dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags
//四种优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH   #define DISPATCH_QUEUE_PRIORITY_DEFAULT   #define DISPATCH_QUEUE_PRIORITY_LOW     #define DISPATCH_QUEUE_PRIORITY_BACKGROUND

获取主队列
dispatch_queue_t dispatch_get_main_queue(void);

向一个队列里面派发线程

  1. 异步派发(第一个参数代表要派发到哪个队列,第二个参数代表用block表示的线程)
    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  2. 同步派发
    void dispatch_sync( dispatch_queue_t queue, dispatch_block_t block);

大多数情况下多线程的代码模版

//把耗时的操作异步派发到默认优先级的并发队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 耗时的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 更新UI界面
});
});

//如果最后一个线程和前几个线程有依赖关系,就把前几个线程放到组中
//创建组
dispatch_group_t dispatch_group_create( void );
//异步派发到组里面
void dispatch_group_async( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
//组里面的程序执行完后,在外面获取通知,继续执行
void dispatch_group_notify( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

补充

//第一个参数表示被放到队列中的代码块要被重复执行几次,后面的参数是队列和代码块(这个方法是同步返回,也就是说等到所有block执行完毕才返回)
void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)( size_t));
//延迟执行block块,第一个参数表示时间
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
//初始化单例的时候会使用到,(避免临界区访问)这个方法是对加锁的封装,第一个参数表示dispatch_once_t类型变量(必须使用全局变量或静态变量,最好使用静态变量)的指针
void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);

查看官方文档的时候,如果有 Discussion 就一定要看下,里面是我们经常会犯的错误

初始化单例的固定模式:

+ (instancetype)sharedInstance{
    static QYSomeClass *manager;
    static dispatch_once_t once;//创建dispatch_once_t类型的静态变量
    dispatch_once(&once, ^{
        manager = [[QYSomeClass alloc]init];//这里面的代码只会执行一次
    });
    return manager;
}

障碍

障碍都是放到自定义的并发队列中,因为串行队列中本身就是一步步执行

障碍可以用做读写锁,放置一个文件被同时读写

//设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行(异步返回函数)
void dispatch_barrier_sync( dispatch_queue_t queue, dispatch_block_t block);
//同上(同步返回函数)
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); 

死锁

两个被派发的任务互相等待,程序无法继续向下执行,形成死锁

在一个串行队列里面,一个异步派发的任务(线程)中,再去同步地派发一个任务(线程),必然死锁,举例如下:

//创建一个串行队列
    dispatch_queue_t queue = dispatch_queue_create("com.qingyun.serial", DISPATCH_QUEUE_SERIAL);
//1和2互相等待,形成死锁,只有1打印出来了,后面的都没出来
    dispatch_async(queue, ^{
        NSLog(@"1 job");
        dispatch_sync(queue, ^{
            NSLog(@"2 job");
        });
    });

你可能感兴趣的:(iOS-OC)