多线程

多线程的应用

1.使用单利模式时,可以使用GCD

2.耗时操作放入子线程处理,完成后回主线程显示。

3.从数据库读取大量数据,可开辟子线程操作。

4.处理音频、视频数据时,在子线程处理。

5.数据同步操作。

多线程实现方案

NSThread(OC语言,可以手动开辟的子线程,需要自己管理内存): 使用更加面向对象,相对简单,可以直接操作线程对象,偶尔使用。

GCD(C语言,自动管理生命周期): 是NSOperation的底层实现,是c语音级别的多线程,用block(代码块)来表示一个任务 GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);

NSOperation(GCD的封装,管理生命周期): 基于GCD,比GCD多了一些更简单实用的功能,更加面向对象,经常使用。

###NThread的线程创建的三种方式

1.NSObject的

performSelectorInBackground / performSelectorOnMainThread

2.NSThread 的

detachNewThreadSelector,创建一个新线程并自动执行

3.NSthread 的

alloc initWithtarget,创建一个新线程但不会自动执行的

###NSThread常用方法

当前线程休眠几秒 [NSThread sleepForTimeInterval:1.0f];

休眠到指定时间 [NSThread sleepUntilDate:date];

YES表示暂停,NO表示恢复 [queue1 setSuspended:YES];

NSThread线程通讯

[self performSelector:@selector(function) onThread:[NSThread mainThread]
     withObject:nil waitUntilDone:YES];

waitUntilDone 是否马上执行
YES:表示马上执行
NO:表示加入onThread所在队列中。等待runloop调度执行

注意:如果onThread还是当前线程,就相当于直接调用一个方法

Grand Central Dispatch(GCD)

是NSOperation的底层实现,是c语音级别的多线程,用block(代码块)来表示一个任务 GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);

###GCD的队列queue GCD中的调度队列FIFO(先进先出),

GCD的队列queue 分为三种:全局并行queue;主线程串行queue; 自定义queue

1.获取全局队列 dispatch_get_global_queue(queue优先级,0)

2.获取主线程队列dispatch_get_main_queue()

3.创建自定义队列dispatch_queue_create(“唯一标识”,队列类型)

队列类型说明:
    DISPATCH_QUEUE_SERIAL表示串行队列 ;  DISPATCH_QUEUE_CONCURRENT表示并行队列    

全局队列又分为: Hight;default ;low; background四种全局队列。

其中 主队列是在主线程中顺序执行,全局队列是在子线程种并发执行的,所以全局队列也叫子线程队列。

各种队列背后的线程模型是什么样的?

GCD各种队列背后的线程模型是一个苹果自己维护的线程池。

image

最底层是主线程和gcd线程池,其中主线程存放主线程队列,gcd线程池存放各种级别的全局队列 在主线程创建的串行队列会自动存入主线程队列

在子线程中创建的全局队列就会存到相应优先级的全局队列里,并且依次优先级成树形排列

GCD如何停止线程

GCD的线程开启后无法停止,但是可以设置条件停止线程中的任务,让线程空闲,减少cpu的消耗

GCD常用方法:

异步提交代码块到某个队列

 dispatch_async(queue, ^{ 
 });

同步提交代码块到某个队列

dispatch_sync(queue1, ^{
});

异步提交代码块到串行队列,线程池将在指定延迟时间5s后执行任务

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5*NSEC_PER_SEC)), queue2, ^{

});

GCD_Group使用场景

多个任务完成后,需要一个统一的回调通知去处理接下来的业务,这个时候就需要使用dispatch_group_notify及dispatch_gruop_enter() dispatch_group_lever来处理

// 1.创建Dispatch_group
dispatch_group_t group = dispatch_group_create();
    for (int i = 0; i < 3; i ++){
//2.1 声明dispatch_group_enter(group)下面的任务由group组管理,group组的任务数+1
 dispatch_group_enter(group);

//2.将block任务添加到queue队列,并被group组管理 dispatch_group_async

        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"异步任务开启!,当前线程:%@",[NSThread currentThread]);
        });
  //2.2    dispatch_group_leave相应的任务执行完成,group组的任务数-1
  dispatch_group_leave(group);
    }
  //3.所有Dispatch_group后的汇总操作
  //dispatch_group_notify只会等待任务的完成,不会阻塞线程
   dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
        // 主线程处理
        NSLog(@"主线程执行,当前线程:%@",[NSThread currentThread]);
    });

});

dispatch_group_enter和dispatch_group_leave

分别表示某个任务进入到group中,受到group管理,在group内部会有一个计数器,调用dispatch_group_enter后,该计数器会+1,调用dispatch_group_leave后该计数器会-1,当计数器为0时,则主动调用dispatch_group_notify的block,否则一直处于等待中,直到所有enter的任务都调用完leave,因此,一个任务真正的执行完的标志是调用了dispatch_group_leave,否则dispatch_group_notify会一直处于等待中,因此,dispatch_group_enter和dispatch_group_leave一定要配套使用。

dispatch_group_wait
 dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

wait会阻塞主线程,等待 dispatch_group中的任务执行,当执行完毕后,或者超过了dispatch_time_t设置的时间,就会结束这个方法,执行剩下的任务。

(这里注意dispatch_group_notify只会等待任务的完成,不会阻塞线程)

NSOperation

NSOperation与NSOperationQueue来实现多线程,是基于GCD更高一层的封装,是完全面向对象。比GCD更简单易用、代码可读性也更高。

是对GCD的封装,可以理解为gcd的oc实现,于GCD相比好处是提供了任务的暂停, 取消. 恢复依赖等方法

其中NSOPeration代表一个多线程任务,是个抽象类不可直接使用,使用的是它的子类NSInvacationOperation和NSBlockOperation

NSOperationQueue:负责管理添加到队列中的任务,加入的任务会自动在子线程中异步执行,执行顺序会根据线程间的依赖关系和优先级来决定

gcd创建的线程,系统自动管理其生命周期,程序无法控制,但是可以通过添加条件来停止线程中的任务,让线程无任务可做,从而减少cpu的消耗

NSOperation实现多线程的步骤:

1.创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。 2.创建队列:创建 NSOperationQueue 对象。 3。将操作加入到队列中:即:将 NSOperation 对象添加到 NSOperationQueue 对象中。之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。

NSOperation 对象创建操作(实现多线程)的三种方式

1.NSInvocationOperation 通过selector快速的构建一个operation

2.NSBlockOperation通过addExecutionBlock来添加多个执行block来实现并发执行一个或则多个block,只有所有block执行完毕,operation才算执行完毕,所以一个NSBlockOperation可以有多个线程。

如果一个 NSBlockOperation 添加多个执行block。NSBlockOperation 是否开启新线程,取决于操作的个数。如果添加的操作的个数多,就会自动开启新线程。当然开启的线程数是由系统来决定的。

3.自定义NSOperation子类的做法是继承NSOperation;重写main方法;给用户提供接口,在main中自己实现线程的事件处理

NSOperation创建的操作的常用方法

1.添加操作间依赖关系

[op2 addDependency:op1]; // 让op2 依赖于 op1,则先执行op1,在执行op2

2.设置操作的优先级

[o3 setQueuePriority:NSOperationQueuePriorityHigh];

3.NSBlockOperation可通过completionBlock来添加所有任务完成后的下一步操作,可用于任务间的依赖执行。

队列中任务的执行顺序

1,先看operation是否准备好,准备好的看它们之间的依赖关系

2.在看operation的优先级,默认都是普通级别,从优先级高的开始执行

NSoperationQueue 定义

NSOperationQueue:队列,负责管理添加到队列中的多线程任务,底层维护了一个线程池。加入的任务会自动在子线程中异步执行,执行顺序会根据线程间的依赖关系和优先级来决定, NSOperationQueue一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发两种队列

NSOperationQueue常用方法

主队列获取方法

NSOperationQueue *queue = [NSOperationQueue mainQueue];

自定义队列创建方法

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

操作添加到队列中 【queue addOperation】

加入队列中任务会自动异步执行.加入主队列的任务在主线程串行执行,加入其他线程的任务是并发执行的

NSOperationQueue设置最大并发操作数

queue.maxConcurrentOperationCount = 1; // 串行队列

maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。

NSOperation和NSOperationQueue实现的多线程功能注意事项

1.单个任务NSOperation启动都是在当前线程同步执行

2.使用NSOperationQueue管理任务operation,加入队列的任务会自动异步并发执行。

3.添加到quete中的operation对象随时可能执行,因此加入quete后不可修改operation的状态。

4.NSBlockOperation的多个任务是并发执行的,有的在当前线程,有的在其他线程,但NSBlockOperation的completionBlock,会在blockoperation的所有任务完成后执行。并且是在当前线程执行

5.设置队列最大并发数(就是队列一次并发执行operation的个数),不是线程个数,所以1也不能确保是串行,因为blockoperation可以有多个block,每个block都是一个线程,但是只有一个operation。

多线程队列中operation的执行顺序决定因素有2个:

1.operation是否准备好,由operation间的依赖关系决定

2.operation的优先级,默认都是普通级别。

NSOperation与GCD的区别

CDG:Grand Central Dispatch 是NSoperation的底层实现,是c语言级别的多线程,用block代码块表示一个任务,会自动利用cpu的多核特性并行运算,会自动管理线程的生命周期(创建线程、调度任务、销毁线程)也就是说程序员只需要告诉GCD想要执行什么任务,不需要写任何线程管理代码.

NSOperation,基于GCD,是面对对象的,但是GCD更简单易用、代码可读性也更高。 可以添加任务依赖,来保证执行顺序的正确性,而且已开启的任务可以取消,暂停,恢复,也可以设置优先级

GCD高效,NSOperation开销相对高

线程安全

线程安全就是线程同步,就是不允许在同一时刻在不同的线程中操作同一变量,说白了就是一个对象只有一个线程使用完成才能让其他线程访问,所有多线程对数据的访问需要加锁

线程加锁实现方式

1.@synchronized (主要应用创建单利)

2.NSLock线程锁,简单的互斥锁,需要lock与unlock对应,不能嵌套使用

3.NSConditionLock 条件锁,手动控制线程wait和signal

4.NSRecursiveLock 递归锁

5.GCD的并发控制-信号量semaphore

线程死锁

死锁通常指两个线程a和b都卡住了,a在等待b完成才能继续,b在等待a完成才能继续执行,这样大家都完成不了,所以导致死锁

死锁出现的条件是串行队列的同步执行,因为同步会阻塞当前线程,但是串行要等待当前线程完成才能执行,所以就产生了死锁

先看依赖关系,然后从准备好的优先级最高的那个执行

不产生子线程,不阻塞主线程,如何实现?

主线程的异步执行 在主线程队列中的异步执行,相当于子线程的作用,但不会产生子线程,会在主线程无任务时执行。

用于数据准备好后的一种依赖执行,而且可以保证数据准备完成后执行,好处是不会产生子线程,不会阻塞主线程

线程死锁的原因及解决办法:

原因:系统资源不足,进程的推进顺序不合适。资源分配不足,比如互斥条件,请求与条件保持,不可剥夺条件,循环等待条件,线程死锁,

互斥条件:及一个资源一次只能被一个进程使用 请求与保持条件:一个进程因请求资源而阻塞时对已获得的资源保持不放(解决办法资源一次性分配) 不可剥夺条件:进程已获得资源。在未使用完之前。不可强行剥夺(解决办法可剥夺资源) 循环等待条件;若干进程之间形成一种头尾相接的循环等待资源的关系(解决办法资源有序分配)

线程死锁:解决办法:破坏死锁条件,除了第一条

处理死锁策略:

1)鸵鸟政策,及视而不见 2)银行家算法,分配之前看清楚,如果会导致死锁则不分配,不会导致死锁才分配

dispatch_barrier_async说明(栅栏(用于需要依次执行完多个线程组)
/** 栅栏(用于需要依次执行完多个线程组) */
//并发队列异步执行代码块1,2
dispatch_async(queue, ^{
    //代码块1
});
dispatch_async(queue, ^{
    //代码块2
});
//1,2执行完后才会执行3,4
dispatch_barrier_async(queue, ^{

});
GCD的信号量dispatch_semaphore

定义:信号量就是一种可用来控制访问资源数量的标识,设置一个信号量,在线程访问之前,加上信号量的处理,则可控制系统按照指定的信号量数量来执行多个线程 信号量相关函数: 1.创建信号量dispatch_semaphore_create(信号量数) 2.降低信号量 dispatch_semaphore_wait//信号量减一 3.提高信号量dispatch_semaphore_signal//信号量加一 正常的使用逻辑是:先降低再提高,而且这两个函数一般成对使用 如果dispatch_semaphore_wait减1前如果小于1,则一直等待。同时这两者之间是线程无顺序的抢占资源,但只能允许一个线程执行。所以猜测如果某一时刻进行了dispatch_semaphore_signal操作,则会继续执行

//1.创建GCD信号量dispatch_semaphore
 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
   //2.创建队列dispatch_queue
   dispatch_queue_t quene = dispatch_get_global_queue(0, 0);
    //3.创建gcd任务1并添加到队列中
    dispatch_async(quene, ^{
    //3.1信号量减一dispatch_semaphore_wait
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        //3.2gcd信号量加1spatch_semaphore_signal
        dispatch_semaphore_signal(semaphore);
    });
    ///3.创建gcd任务2并添加到队列中
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });

生产者消费者实例(多线程安全GCD)NSCondition锁

 NSCondition *condition = [[NSCondition alloc] init];

    NSMutableArray *products = [NSMutableArray arrayWithCapacity:0];
 //消费者
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            //加锁
            [condition lock];
            if([products count]==0)
            {
                NSLog(@"wait for Product");
                //wait 让当前线程处于等待状态
                [condition wait];
            }
            //消费一个产品
            [products removeObjectAtIndex:0];
            NSLog(@"custome a product");
            //解锁
            [condition unlock];


        }
    });

    //生产者
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (1) {
            //加锁
            [condition lock];
                //制作一个产品
            NSLog(@"make a product");

            [products addObject:[[NSObject alloc] init]];
            //所有处于wait的线程,均有机会开始执行
            [condition signal];
            //解锁
            [condition unlock];
            sleep(1);
        }
    });
gcd定时器dispatch_source_t
// GCD定时器
static dispatch_source_t _timer;
- (void)startGCDTimer {
    if (_timer) return;
    //1设置时间间隔
    NSTimeInterval period = 0.05; 
    //2.创建存放定时器的queue
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //3.创建定时器timer
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //4.设置timer
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);
    // 5.添加timer的事件回调
    dispatch_source_set_event_handler(_timer, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            [self upDateProgress];
        });
    });
    // 6.开启定时器
    dispatch_resume(_timer);
    //6.1关闭定时器
    dispatch_source_cancel(_timer);
}

dispatch_source_t 是间隔定时器,也就是说每隔一段时间间隔定时器就会触发

dispatch_source_set_timer 中第二个参数,当我们使用dispatch_time 或者 DISPATCH_TIME_NOW 时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用 dispatch_walltime 可以让计时器按照真实时间间隔进行计时。

dispatch_source_set_event_handler 这个函数在执行完之后,block 会立马执行一遍

###NSthread 小知识

休眠5s
// [NSThread sleepForTimeInterval:5];
 中止当前线程
// [NSThread exit];
qualityOfService 属性来设置线程的优先级
NSLog(@"子线程运行:%@ %@ 优先级:%d", [NSThread currentThread], object, [NSThread currentThread].qualityOfService);


####GCD-dispatch_apply函数说明
 该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束

10 指定重复次数 指定10次 //queue 添加block的queur // index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block

dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
 });
NSLog(@"done");

多线程注意事项

1.注意线程间的通信,数据共享,数据安全等问题 2.多线程会将耗时操作放在后台执行,避免了阻塞主线程,而且在iOS中UI绘制和用户响应都必须在主线程。

GCD queue优先级说明:
        DISPATCH_QUEUE_PRIORITY_HIGH:  //高
        DISPATCH_QUEUE_PRIORITY_DEFAULT://默认
        DISPATCH_QUEUE_PRIORITY_LOW: //低
        DISPATCH_QUEUE_PRIORITY_BACKGROUND: //后台

1.使用子类 NSInvocationOperation来实现操作的实例代码段

// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

// 2.调用 start 方法开始执行操作
[op start];

2.使用子类NSBlockOperation来实现操作的实例代码段

// 1.创建 NSBlockOperation 对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    for (int i = 0; i < 2; i++) {
    NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }
}];
// 2.添加额外的操作
[op addExecutionBlock:^{
    for (int i = 0; i < 2; i++) {
        NSLog(@"2---"); 
    }
}];

// 3.调用 start 方法开始执行操作
[op start];

}

3.自定义NSOperation来实现操作的实例代码段

3.1 继承NSopeation定义一个新类

3.2新类中改写main方法来实现自己的线程事件处理

 - (void)main {
if (!self.isCancelled) {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }
}
 }
 @end
JSRUN前端笔记, 是针对前端工程师开放的一个笔记分享平台,是前端工程师记录重点、分享经验的一个笔记本。JSRUN前端采用的 MarkDown 语法 (极客专用语法), 这里属于IT工程师。