今天看啥  ›  专栏  ›  聪莞

iOS 多线程初探

聪莞  · 掘金  ·  · 2019-07-09 02:25
阅读 11

iOS 多线程初探

线程的定义

线程是进程的基本执行单元,一个进程的所有任务都是在线程中执行,进程想要执行任务,必须有一条线程。
程序启动会默认开启一条线程,这条线程被称为主线程或UI线程。
复制代码

为什么一定要在主线程刷新UI?

因为UIKit框架不是线程安全的,当在多个线程进行UI操作,有可能出现资源抢夺,导致问题。虽然apple有对大部分的绘图方法和诸如UIColor等类改写成线程安全可用,可还是建议将UI操作保证在主线程中(可以理解为这是苹果的一种设计)。

线程&进程

    进程是指在系统中正在运行的一个应用程序,每个进程之间是相互独立的,每个进程均运行在其专用且受保护的内存里。
复制代码

进程与线程的关系?

    地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是相互独立的地址空间。
    资源拥有:同一进程内的线程共享本进程的资源,如:内存、IO、Cpu等,但是进程之间的资源是独立的。

    一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃,整个进程都会死掉。所以多进程要比多线程健壮。
    进程切换时,消耗的资源大,效率高。所以涉及到频繁切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
    执行过程:每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    线程是处理器调度的基本单位,但是进程不是。
    两者均可并发执行。
复制代码

#多线程的意义

优点

  1. 能适当提高程序的执行效率,
  2. 能适当提高资源的利用率 (cpu、内存等)
  3. 线程上的任务执行完成后,线程会自动销毁

缺点

  1. 开启线程需要占用一定的内存空间(默认情况下,每一个线程占用512kb)
  2. 线程越多,Cpu在调度线程上的开销就越大

iOS多线程技术方案

image.png

##多线程生命周期

image.png

##多线程创建

image.png

  • pthread的使用
- (void)pThreadTest {
    /**
     参数说明
     1.线程标识符
     2.线程属性
     3.线程执行函数起始指针
     4.函数运行参数
     */
    pthread_t threadId = NULL;
    NSString * str = @"pthread name";
    int result = pthread_create(&threadId, NULL, pThreadAction, (__bridge void*)str);
    if (result == 0) {
        NSLog(@"线程创建成功");
    }
    else {
        NSLog(@"线程创建失败 %d",result);
    }
    
    //pthread一定要手动把当前线程结束掉
    pthread_detach(threadId);
}

void * pThreadAction(void * params) {
    NSLog(@"执行任务");
    return NULL;
}
复制代码
  • NSThread的使用
- (void)threadTest {
    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(downloadImage) object:nil];
    thread.name = @"myThread";    //设置线程名称
    thread.threadPriority = 0.7;     //设置优先级
    [thread start];
}

- (void)downloadImage {
    NSThread *currentThread=[NSThread currentThread];
    //    如果当前线程处于取消状态,则退出当前线程
    if (currentThread.isCancelled) {
        NSLog(@"thread(%@) will be cancelled!",currentThread);
        [NSThread exit];    //取消当前线程
    }
    
    //下载图片...

}
复制代码
  • NSOperation的使用
/*
 使用NSOperation和NSOperationQueue进行多线程开发类似于C#中的线程池,只要将一个NSOperation(实际开中需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就会依次启动。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系。
 
 NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。
 */
- (void)operationTest {
//    NSInvocationOperation * operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
//    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
//    [queue addOperation:operation];
    
    int count=ROW_COUNT*COLUMN_COUNT;
    NSOperationQueue * operationQueue = [[NSOperationQueue alloc] init];
    operationQueue.maxConcurrentOperationCount = 5;      //最大并发数为5
    
    /* 每个NSOperation可以设置依赖线程。假设操作A依赖于操作B,线程操作队列在启动线程时就会首先执行B操作,然后执行A。对于前面优先加载最后一张图的需求,只要设置前面的线程操作的依赖线程为最后一个操作即可。 */
    
    NSBlockOperation * lastOperation = [NSBlockOperation blockOperationWithBlock:^{
        
    }];
    
    for (int i = 0; i < count - 1; i ++) {
        //方法1:创建操作块添加到队列
                //创建多线程操作
                NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
                    [self download];
                }];
        
                [blockOperation addDependency:lastOperation];       //设置依赖操作为最后一张图片加载操作
        
                [operationQueue addOperation:blockOperation];
        
        // 方法2:直接使用操作队列添加操作
//        [operationQueue addOperationWithBlock:^{
//            [self download];
//        }];
    }
    [operationQueue addOperation:lastOperation];
}

- (void)download {
    NSData * data = [self getImageData:5];
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        //更新UI界面,此处调用了主线程队列的方法(mainQueue是UI主线程)
    }];
}
复制代码
  • GCD使用
- (void)gcdTest {
    //串行队列
    dispatch_queue_t queue1 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL_INACTIVE);
    dispatch_async(queue1, ^{
        //do sth
    });

    //并行队列 开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列
    //    dispatch_queue_t queue2 = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //并发队列
    });
}

/*
 在GDC中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行。
 串行队列可以按顺序执行,并行队列的异步方法无法确定执行顺序。
 UI界面的更新最好采用同步方法,其他操作采用异步方法。
 GCD中多线程操作方法不需要使用@autoreleasepool,GCD会管理内存。
 
 GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:
 
 dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
 dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
 dispatch_time():延迟一定的时间后执行。
 dispatch_barrier_async():使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)
 dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。
*/
复制代码



原文地址:访问原文地址
快照地址: 访问文章快照