程序员

iOS线程基础知识总结

2016-05-07  本文已影响83人  雷雨庭花

一, 程序、进程和线程
程序:由源代码生成的可执行应用
进程: 一个正在运行的程序可以看做是一个进程
线程:程序中独立运行的代码段。
一个进程是由一个或多个线程组成。进程只负责资源的调度和分配, 线程才是程序真正的执行单元, 负责代码的执行。
线程和进程的区别:
1, 地址空间:线程是进程内的一个执行单元;进程至少有一个线程, 他们共享进程的地址空间; 而地址有自己的独立的地址空间;
2, 资源拥有:进程是资源分配和拥有的单位, 同一个进程内的线程共享进程的资源;
3, 线程是处理器调度的基本单位, 而进程不是;
4, 二者都可以并发执行
**************iOS中关于UI的添加和刷新必须在主线程中操作
二, 多线程的实现种类
iOS中多线程的实现种类有:NSThread,NSObject,NSOperationQueue,GCD
1, NSThread是一个轻量级的多线程, 有以下两种创建方式:
(1)初始化开辟子线程

- (void)allocThread {

    //初始化开辟子线程
    //object 就是子线程方法的参数
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@1000];
    //*********初始化开辟线程的话需要手动开启
    thread.name = @"zhengzhou";
//    _thread.isCancelled 是否被取消
//    _thread.isExecuting 是否正在执行
//    _thread.isMainThread 是否是主线程
    [thread start];
}

- (void)threadAction:(NSNumber *)number {
    
    //使用NSThread开辟的子线程, 方法内部默认没有添加自动释放池, 为了防止内存泄露, 我们需要手动添加自动释放池
    @autoreleasepool {
        for (int i = 0; i < [number integerValue]; i++) {
            NSLog(@"%@", [NSThread currentThread]);
            if (i == 100) {
                // cancle 并不能取消正在执行的线程,只能取消还没有开始执行的线程
                [[NSThread currentThread] cancel];
                // 立即停止线程
                [NSThread exit];
            }
            NSLog(@"%d", i);
        }
    }
}

(2)便利构造器开辟子线程

//不需要手动开启
[NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@1000];

我们一般不用NSThread来开辟子线程
2, NSObject

//NSObject
- (void)objectAction {
    //开辟子线程
    [self performSelectorInBackground:@selector(reuestImageData:) withObject:@100];
}

//子线程方法
- (void)reuestImageData:(NSString *)string {
    //手动添加自动释放池
    @autoreleasepool {
        
        //回到主线程刷新UI
        //最后一个参数为YES时, 是等主线程的方法执行完之后在执行线程后面的方法
        //最后一个参数为NO时, 是主线程和子线程中的方法同时执行
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:nil waitUntilDone:NO];
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d********%@", i, [NSThread currentThread]);
        }
    }
}
//刷新UI
- (void)updateUI:(NSData *)data {
    
    for (int i = 0; i < 10; i++) {
        NSLog(@"%d+++++++++%@", i, [NSThread currentThread]);
    }
}

3, NSOperationQueue
NSOperation在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类,不能够直接使用这个类,而是使用子类(NSInvocationOperation或NSBlockOperation)来执行实际任务。
只是一个操作,本身无主线程子线程之分,可在任意线程中使用。通常与NSOperationQueue结合使用,只是封装了一定的代码段和数据去实现一个功能。

- (void)invocationOperationAction {
    //NSOperation的子类, 创建的操作和线程没有关系, 在主线程中创建的操作, 方法在主线程中进行, 在子线程中创建的操作在子线程中进行
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethod:) object:@"hello"];
    //需要手动开启任务
    [operation start];
    //取消任务
    //    [operation cancel];
    //是否正在执行
    //    [operation isExecuting];
    //是否完成任务
    //    [operation isFinished];
}

- (void)invocationOperationMethod:(NSString *)string {
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"%@", string);
}
// NSBlockOperation
- (void)blockOperation {
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        //执行的方法
        NSLog(@"%@", [NSThread currentThread]);
    }];
    //需要手动开启
    [blockOperation start];
}

NSOperationQueue是操作队列, 用来管理一组Operation对象的执行, 会根据需要自动为Operation开辟合适数量的线程, 已完成任务的执行
其中NSOperation可以调节它在队列中的优先级(使用addDependency: 设置依赖关系)
当最大并发数设置为1的时候, 能实现线程同步(串行执行)

- (void)operationQueue {
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000000; i++) {
            NSLog(@"1 ------ %d", i);
        }
    }];
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 1000000; i++) {
            NSLog(@"2 ------ %d", i);
        }
    }];
    //创建一个新的队列
    //新创建的队列, 里面的任务是并发执行的, 而且是在子线程中执行的
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //设置任务最大并发数, 如果为1, 队列里边的任务相当于串行
    //    queue.maxConcurrentOperationCount = 1;
    
    //添加依赖关系, 必须在任务添加到队列之前设置
    [blockOperation1 addDependency:blockOperation2];
    
    [queue addOperation:blockOperation1];
    [queue addOperation:blockOperation2];
    
}

4,GCD(Grand Central Dispatch)
是Apple开发的一种多核编程技术. 主要用于优化应用程序一支持多核处理器以及其他对称多处理系统
GCD提供函数实现多线程开发, 性能更高, 功能也更加强大
首次发布在Mac OS X 10.6, iOS4以上可用

核心概念:
任务: 具有一定功能的代码段, 一般是一个block或者函数, 与NSOperation一样,并无主线程与子线程之分,主要是看它加载在什么queue队列中
分发队列: GCD队列的方式进行工作
GCD会根据分发队列的类型, 创建合适数量的线程执行队列中的任务

dispatch queue有串行队列和并发队列两种:
(1)串行队列(SerialQueue):一次只执行一个任务. 一般用于同步访问特定的资源和数据. 当创建多个SerialQueue时, 虽然他们是同步执行的, 但他们之间是并发执行的.SerialQueue能实现线程同步

//主队列
- (void)mainQueueAction {
    //获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //主队列中添加任务
    dispatch_async(queue, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"-------------------%@", [NSThread mainThread]);
        }
    });
    //主队列中添加任务
    dispatch_async(queue, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"++++++++++++++++++++%@", [NSThread mainThread]);
        }
    });
}

//自定义串行队列
- (void)customSerialQueue {
    //创建一个串行队列
    //参数:(1)队列的名字, (2)队列类型, 是串行还是并行
    //串行里面的任务是在同一个线程中执行的
    dispatch_queue_t serialQueue = dispatch_queue_create("com.lanou3g.serialQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"---------------------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(serialQueue, ^{
        for (int i = 0; i < 100; i++) {
            NSLog(@"++++++++++++++++++++%@", [NSThread currentThread]);
        }
    });
}

(2)并发队列(Concurrent):可以并发地执行多个任务

//全局队列
- (void)globalQueue {
    //参数: (1)参数优先级, (2)预留参数
    //队列里面的任务并发执行
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //添加任务
    dispatch_async(globalQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"----------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(globalQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"+++++++++++%@", [NSThread currentThread]);
        }
    });
}
//自定义并发队列
- (void)customConcurrentQueue {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lanou3g.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"------------------%@", [NSThread currentThread]);
        }
    });
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"+++++++++++++++++++%@", [NSThread currentThread]);
        }
    });
}

(3)GCD功能
GCD功能
dispatch_async() //往队列中添加任务, 任务会排队执行
dispatch_after() //往队列中添加任务, 任务不但会排队, 还会在延迟的时间点执行

//延迟执行任务
- (void)delayPerformQueue {
    dispatch_queue_t queue = dispatch_queue_create("com.lanou3g.delayPerformQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%@", [NSThread currentThread]);
        }
    });
    //延迟执行任务
    //queue队列里面的任务执行完成之后, 延迟5秒执行该函数里面的任务
    //不能在主队列中执行该任务
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
        NSLog(@"延迟执行的任务");
    });
}

dispatch_apply() //往队列中添加任务, 任务会重复执行n次

//重复执行任务
- (void)repeatPerformTask {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //注意: queue 参数不能是主队列(死锁)
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%ld, %@", index, [NSThread currentThread]);
    });
}

dispatch_group_async()//将任务添加到队列中, 并添加分组标记
dispatch_group_notify() //将任务添加到队列中, 当某个分组的所有任务执行完之后, 此任务才会执行

//分组标记功能
- (void)groupTask {
    dispatch_queue_t queue = dispatch_queue_create("waige", DISPATCH_QUEUE_CONCURRENT);
    //创建分组
    dispatch_group_t group = dispatch_group_create();
    //往队列里面添加任务1
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"------------%@", [NSThread currentThread]);
        }
    });
    
    //往队列里面添加任务2
    dispatch_group_async(group, queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"+++++++++++++++%@", [NSThread currentThread]);
        }
    });
    
    //给队列添加任务, 并添加分组标记
    dispatch_group_notify(group, queue, ^{
        NSLog(@"queue里面的所有任务都已经执行完毕");
    });
    
}

dispatch_barrier_async() //将任务添加到队列中, 此任务执行的时候, 其他任务停止执行

//添加障碍
- (void)barrierFunction {
    //创建队列
    dispatch_queue_t queue = dispatch_queue_create("waige", DISPATCH_QUEUE_CONCURRENT);
    //添加任务
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"写入数据");
        }
    });
    //设置屏障, 队列里面前面的任务执行完之后才执行屏障函数里面的任务
    dispatch_barrier_async(queue, ^{
        for (int i = 0; i < 20; i++) {
            NSLog(@"不要打扰我, 哥在写数据呢");
        }
    });
    //屏障函数的任务完成之后才会执行下面的任务
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"写完了, 大家可以读数据了");
        }
    });
}

dispatch_once() //任务添加到队列中, 但任务在程序运行过程中, 只执行一次
可用于单例的创建
该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型, 实际上作为BOOL使用), 它在接收一个希望在应用的生命周期内仅被调度一次的代码块. 不仅意味着代码仅会被运行一次, 而且还是线程安全的, 这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题

+ (instancetype)sharedSingleTon {
    static SingleTon *singleTon = nil;
    //只能同时允许一个线程访问
    @synchronized(self) {
        if (singleTon == nil) {
            singleTon = [[SingleTon alloc] init];
        }
    }
    return singleTon;
}

//GCD创建单例
+ (instancetype)defaultSingleTon {
    static SingleTon *single = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //这里的代码只执行一次
        single = [[SingleTon alloc] init];
    });
    return single;
}

dispatch_sync() //将任务添加到队列中, block不执行完, 下面代码不会执行(同步),会阻塞当前线程 ----------在当前线程中执行
dispatch_async_f() //将任务添加到队列中, 任务是函数非block(异步)---------重新创建线程执行任务

//同步
- (void)useOfSync {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //dispatch_sync会阻塞当前线程
    dispatch_sync(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%@", [NSThread currentThread]);
            
        }
    });
    NSLog(@"这是一条分割线");
    dispatch_sync(queue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"=====%@", [NSThread currentThread]);
        }
    });
    NSLog(@"这又是一条分割线");
}

三,线程间的通信
子线程回到主线程的方法

//bool参数 如果是YES, 子线程会等待主线程的任务完成以后才会继续执行, 如果为NO, 主线程的任务没有结束的时候就会执行子线程的任务
        [self performSelectorOnMainThread:@selector(updataUI:) withObject:data waitUntilDone:NO];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = [UIImage imageWithData:data];
        });
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = [UIImage imageWithData:data];
        }];

四,线程互斥
对线程并行编程中, 线程间的同步和互斥是一个很有技巧的也很容易出错的地方。
多线程操作同一个资源(即某个对象), 需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后, 状态仍然正确。
情景: 三个售票窗口同时售票, 可只剩最后一张票, 如何解决
解决方案
1,@synchronized 自动对参数对象加锁, 保证临街区内的代码线程安全
2,NSLock

//创建一个并行队列
    dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
    //创建锁对象
        NSLock *lock = [[NSLock alloc] init];
    //开辟10个子线程, 进行售票
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            //加锁
            [lock lock];
            //售票任务
            while (self.ticketsCount > 0) {
                //线程休息0.1秒
                [NSThread sleepForTimeInterval:0.1];
                //售出一张
                self.ticketsCount--;
                NSLog(@"剩余票数:%ld, 当前线程:%@", self.ticketsCount, [NSThread currentThread]);
            }
            //解锁
            [lock unlock];
        });
    }

3,NSConditionLock条件锁 可以设置条件

//创建一个并行队列
    dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
    //条件锁
    NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:2];;
    //开辟10个子线程, 进行售票
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            //条件锁
            [lock lockWhenCondition:2];
            //售票任务
            while (self.ticketsCount > 0) {
                //线程休息0.1秒
                [NSThread sleepForTimeInterval:0.1];
                //售出一张
                self.ticketsCount--;
                NSLog(@"剩余票数:%ld, 当前线程:%@", self.ticketsCount, [NSThread currentThread]);
            }
            //解锁
            [lock unlockWithCondition:2];
        });
    }

4,NSRecursiveLock 递归锁 多次调用不会阻塞以获取该锁的线程

//创建一个并行队列
    dispatch_queue_t queue = dispatch_queue_create("zz", DISPATCH_QUEUE_CONCURRENT);
    //递归锁
    recursiveLock = [[NSRecursiveLock alloc] init];
    //开辟10个子线程, 进行售票
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            //递归锁
            [recursiveLock lock];
            //售票任务
            while (self.ticketsCount > 0) {
                //线程休息0.1秒
                [NSThread sleepForTimeInterval:0.1];
                //售出一张
                self.ticketsCount--;
                NSLog(@"剩余票数:%ld, 当前线程:%@", self.ticketsCount, [NSThread currentThread]);
            }
            //解锁
            [recursiveLock unlock];
        });
    }

五,总结
NSThread, NSOperationQueue, NSObject, GCD 都能实现多线程
1,GCD是苹果提供的性能更高的方式
使用多线程开发的优点: 资源利用率更好, 程序设计在某些情况下更简单, 程序响应更快
缺点: 多线程金环提升了性能, 但是存在一些访问限制, 比如线程同步, 线程互斥等; 多线程在使用的时候, 最终是要回到主线程刷新UI的, 如果开辟过多的多线程, 会造成CPU的消耗

2,NSOperationQueue和GCD 的区别
(1)GCD是基于C语言构成的API, 而NSoperationQueue是NSObject对象,在GCD 中在队列中执行的是由block构成的任务, 而NSOperationQueue有两种方式可供选择:NSInvocationOperation和NSBlockOperation
(2)在NSOperationQueue中可以随时取消正在等待执行队列, 而GCD无法停止已经加入queue队列的block块(其实是有的, 只不过代码比较复杂)
(3)在NSOperation中我们可以为任务设定依赖关系,
(4)我们可以讲KVO应用在NSOperation对象中, 可以监听一个operation是否完成或取消, 这样可以更有效的掌控我们的后台任务
(5)在NSOperation中我们可以设置NSOperation的priority的优先级, 能够使同一个并行的序列中的任务区分先后的执行, 而在GCD中, 我们只能区分不同任务队列的优先级, 如果要区分block任务的优先级, 也需要大量的代码
(6)我们能够对NSOperation进行继承, 在这智商添加成员变量与成员方法, 提高整个代码的复用度, 这笔简单地将block任务排入执行队列更有自由度, 能够在其之上添加更多自定制的功能

3,OperationQueue提供了更多在编写多线程程序时需要的功能, 并隐藏了许多线程调度, 线程取消与线程优先级的复杂代码, 为我们提供简单API入口. 从编程原则来说, 一般我们需要尽可能的使用最高等级, 封装完美的API, 在必须时才使用底层的API.
GCD更简洁, 性能更好,而NSOperationQueue为我们提供了更多的选择

上一篇 下一篇

猜你喜欢

热点阅读