iOS多线程编程

一、进程

1.什么是进程?
  • 进程是指在系统中正在运行的一个应用程序
  • 每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

二、线程

1.什么是线程?
  • 应用程序中一条任务的执行路径
2.进程和线程是什么关系?
  • 一个进程可以包含多个线程(一个进程至少包含一个线程Main线程)
  • 一个进程中的所有任务都是在线程中执行
3.线程中的任务怎么执行?
  • 如果要在一个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,在同一时间内,一个线程只能执行一个任务
  • 比如在一个线程中下载三个文件A、B、C,那么会先下载文件A,然后下载文件B,再下载文件C
4.什么是多线程
  • 一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
  • 比如在一个线程中下载三个文件A、B、C,开启三条线程,每个线程下载一个文件,那么三个文件会同时去下载
5.多线程实现原理
  • .同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
  • .多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
  • 如果CPU调度线程的时间足够快,就造成了多线程并发执行
6.多线程的优缺点
  • 优点一:多线程技术可以提高程序的执行效率
  • 优点二:能适当提高资源利用率(CPU、内存利用率)
  • 缺点一:大量使用多线程CPU会在N多线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)
  • 缺点二:创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间

三、iOS中多线程实现方案

iOS多线程编程_第1张图片
iOS中多线程实现方案

四、pthread

1.C语言API,使用比较麻烦
//开启新的线程执行run方法
- (void)pthread {
    pthread_t thread;
    pthread_create(&thread, NULL, run, NULL);
}

void * run(void *param){
    for (NSInteger i = 0; i<50000; i++) {
        NSLog(@"---------%zd", i);
    }
    return NULL;
}

五、NSThread

1.一个NSThread对象就代表一个线程,以下是几种创建方式
  • 第一种多线程方式,需要调用start方法才能启动
- (void)createThread1{
    // 创建线程,线程对象(局部变量)系统会自己加持,在任务执行完之前不会被销毁
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"jack"];
    thread.name = @"my-thread";
    // 启动线程
    [thread start];
}

- (void)run:(NSString *)param{
    for (NSInteger i = 0; i<100; i++) {
        NSLog(@"------%@", [NSThread currentThread]);
    }
}

  • 第二种多线程方式,创建线程后自动启动线程
- (void)createThread2{
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"rose"];
}
- (void)run:(NSString *)param{
    for (NSInteger i = 0; i<100; i++) {
        NSLog(@"------%@", [NSThread currentThread]);
    }
}
  • 第三种多线程方式,隐式创建并启动线程
- (void)createThread3{
    [self performSelectorInBackground:@selector(run:) withObject:@"jack"];
}
- (void)run:(NSString *)param{
    for (NSInteger i = 0; i<100; i++) {
        NSLog(@"------%@", [NSThread currentThread]);
    }
}
2.NSThread的线程通信
  • 在一个线程中执行完特定任务后,转到另一个线程继续执行任务,一个线程传递数据给另一个线程,API如下:
- (void)performSelectorOnMainThread:(SEL)aSelector 
                         withObject:(id)arg 
                      waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector
               onThread:(NSThread *)thread
             withObject:(id)arg   
          waitUntilDone:(BOOL)wait;

  • 线程通信示例:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self performSelectorInBackground:@selector(download3) withObject:nil];
}

//子线程下载图片数据
- (void)download3{
    NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    
    // 回到主线程,显示图片
    [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
    //[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
    //[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];

    NSLog(@" --- waitUntilDone ---");
    //waitUntilDone参数:
    //YES:会等到setImage方法走完以后打印最后一句log。
    //NO:在执行setImage方法的时候不影响最后一句log的打印,两个是同时进行
}

//主线程显示图片
- (void)showImage:(UIImage *)image{
    self.imageView.image = image;
}

3.NSThread常用的方法
//获得当前线程
NSThread *current = [NSThread currentThread];

+ (NSThread *)mainThread; // 获得主线程
- (BOOL)isMainThread; // 是否为主线程
+ (BOOL)isMainThread; // 是否为主线程

//线程的名字,适用于第一种方式创建的线程(创建的时候返回NSThread的对象)
- (void)setName:(NSString *)n;
- (NSString *)name;

六、GCD

1.什么是GCD
  • 全称是Grand Central Dispatch,可译为“重要的中枢调度器”
  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
2.任务:执行什么操作
  • 用同步的方式执行任务:
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • 用异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  • 同步和异步的区别

同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力

3.队列
  • 并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效

  • 串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

  • 获取并发队列
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 队列的优先级
unsigned long flags); // 此参数暂时无用,用0即可
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列

全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MI

//自己创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
  • 获取串行队列
GCD中获得串行有2种途径
使用dispatch_queue_create函数创建串行队列
dispatch_queue_t
dispatch_queue_create(const char *label, // 队列名称 
dispatch_queue_attr_t attr); // 队列属性,一般用NULL即可,DISPATCH_QUEUE_SERIAL串行就传NULL
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); // 创建
dispatch_release(queue); // 非ARC需要释放手动创建的队列

使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
  • 各种队列和发送方法的组合
// 同步函数 + 主队列:(若是在主线程调用会卡死主线程)
- (void)syncMain{
    NSLog(@"syncMain ----- begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    NSLog(@"syncMain ----- end");
}

// 异步函数 + 主队列:只在主线程中执行任务
- (void)asyncMain{
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
}


 //同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务
- (void)syncSerial{
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
}

 //异步函数 + 串行队列:会开启新的线程,但是任务是串行的,执行完一个任务,再执行下一个任务
- (void)asyncSerial{
    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
}


 //同步函数 + 并发队列:不会开启新的线程
- (void)syncConcurrent{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    //会先执行任务再执行这句打印,sync的任务会立刻执行
    NSLog(@"syncConcurrent--------end");
}


// 异步函数 + 并发队列:可以同时开启多条线程
- (void)asyncConcurrent{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"1-----%@", [NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger i = 0; i<10; i++) {
            NSLog(@"2-----%@", [NSThread currentThread]);
        }
    });
    //会先执行这一句打印,再执行添加到队列里面的任务
    NSLog(@"asyncConcurrent--------end");
}
  • 各种组合图示


    iOS多线程编程_第2张图片
    各种组合图示
  • GCD中的实用方法
//1.延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});

//2.一次性代码:使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

//3.队列组
//分别异步执行2个耗时的操作,等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group =  dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

//4.快速迭代
dispatch_apply(10, queue, ^(size_t index) {
     //queue如果是串行队列index是有顺序的
     //queue如果是并发队列index是无序的(并发执行)
});

//5.barrier函数:保证barrier之前的任务是并发执行,然后执行barrier任务,然后执行barrier之后的任务
- (void)barrier{
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

七、NSOperation

1.NSOperation和NSOperationQueue实现多线程的具体步骤
  • NSOperation和NSOperationQueue是对GCD的一层封装
  • NSOperation和NSOperationQueue实现多线程的具体步骤
  • 先将需要执行的操作封装到一个NSOperation对象中
  • 然后将NSOperation对象添加到NSOperationQueue中
  • 系统会自动将NSOperationQueue中的NSOperation取出来
  • 将取出的NSOperation封装的操作放到一条新线程中执行
2.NSOperation:是个抽象类,并不具备封装操作的能力,必须使用它的子类
  • NSInvocationOperation
//默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
//只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
- (void)invocationOperation{
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
    [op start];
}

- (void)run{
    NSLog(@"------%@", [NSThread currentThread]);
}
  • NSBlockOperation
//只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
- (void)blockOperation{
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // 在主线程
        NSLog(@"下载1------%@", [NSThread currentThread]);
    }];
    
    // 添加额外的任务(在子线程执行)
    [op addExecutionBlock:^{
        NSLog(@"下载2------%@", [NSThread currentThread]);
    }];
    
    [op start];
}
3.NSOperationQueue
  • NSOperation可以调用start方法来执行任务,但默认是同步执行的
  • 如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
  • 添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op;//添加op的时候会默认调用[op start];
- (void)addOperationWithBlock:(void (^)(void))block;//会默认调用[op start];
4.NSOperation和NSOperationQueue的组合使用
  • NSInvocationOperation和NSOperationQueue
- (void)operationQueue1{
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 创建NSInvocationOperation
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download1) object:nil];
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download2) object:nil];
    
    // 添加任务到队列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
}

- (void)download1{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
}

- (void)download2{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
}
  • NSBlockOperation和NSOperationQueue的组合使用
- (void)operationQueue1{
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 创建NSBlockOperation
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download3 --- %@", [NSThread currentThread]);
    }];
    
    [op3 addExecutionBlock:^{
        NSLog(@"download4 --- %@", [NSThread currentThread]);
    }];
    [op3 addExecutionBlock:^{
        NSLog(@"download5 --- %@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"download6 --- %@", [NSThread currentThread]);
    }];
    
    // 添加任务到队列中
    [queue addOperation:op3]; // [op3 start]
    [queue addOperation:op4]; // [op4 start]
}
5.如果控制NSOperationQueue是串行队列还是并发队列
  • 默认最大并发数是-1,也就是不限制最大并发数,当有多个任务的时候会开启多条线程同时执行任务
  • maxConcurrentOperationCount:同时执行的任务个数
- (void)maxConcurrentOperationCountTest {
    
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
   // 变成了串行队列,会开启一条线程,任务依次执行
    queue.maxConcurrentOperationCount = 1; 
    
    // 添加操作
    [queue addOperationWithBlock:^{
        NSLog(@"download1 --- %@", [NSThread currentThread]);
    }];
    [queue addOperationWithBlock:^{
        NSLog(@"download2 --- %@", [NSThread currentThread]);
    }];
}
6.队列的取消、暂停、恢复
  • 取消队列的所有操作
- (void)cancelAllOperations;
//也可以调用NSOperation的- (void)cancel方法取消单个操作
  • 暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended;
7.操作的相互依赖:NSOperation之间可以设置依赖来保证执行顺序
  • 比如一定要让操作A执行完后,才能执行操作B,可以这么写
[operationB addDependency:operationA]; // 操作B依赖于操作A
  • 任务可以夸队列依赖,在不同队列里面的任务也可以相互依赖


    iOS多线程编程_第3张图片
    任务的跨队列依赖
  • 任务不可以相互依赖,会卡死:

[operationB addDependency:operationA]; // 操作B依赖于操作A
[operationA addDependency:operationB]; // 操作A依赖于操作B

八、线程状态

1.线程状态
  • 创建一个线程调用了start方法以后线程对象会放入可调度线程池,进入就绪状态,等待CPU的调度
  • 当CPU调度的时候处于runing状态,当CPU调用其他线程的时候当前线程会进入就绪状态
  • 调用了sleep方法\等待同步锁会进入阻塞状态
  • sleep到时\得到同步锁进入就绪状态
  • 线程任务执行完毕、异常\强制退出,进入死亡状态
2.线程状态示意图:
iOS多线程编程_第4张图片
线程状态示意图
3.线程状态相关方法
启动线程
- (void)start; 
// 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态

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

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

注意:一旦线程停止(死亡)了,就不能再次开启任务

九、多线程资源抢夺

1.资源抢夺简述:
  • 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件
  • 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
2.资源抢夺示意图:
iOS多线程编程_第5张图片
资源抢夺示意图
3.线程同步(互斥锁)
  • 线程同步:多条线程在同一条线上执行(按顺序地执行任务),互斥锁,就是使用了线程同步技术.
  • 互斥锁对某段代码进行加锁
  • 一个线程在进入之前会判断锁的状态是否是打开,若果是打开则加锁,然后进入代码进行操作,操作完成后解锁。在操作过程中如果有其他线程想对这段代码进行操作则需要等待锁放开才能进入操作
  • 互斥锁虽然能有效解决多线程的资源抢夺问题,同时需要消耗大量的CPU资源
//注意:锁定1份代码只用1把锁,用多把锁是无效的(一般self即可)
@synchronized(锁对象) {
 // 需要锁定的代码  
}
4.互斥锁的原理
iOS多线程编程_第6张图片
互斥锁的原理
5.原子和非原子属性
  • OC在定义属性时有nonatomic和atomic两种选择
  • atomic:原子属性,为setter方法加锁,是线程安全的(默认就是atomic)
  • nonatomic:非原子属性,不会为setter方法加锁

十、多线程下载多图片示例

多线程下载多图片demo 密码:94ts

你可能感兴趣的:(iOS多线程编程)