多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具备多线程能力的计算机因有硬件支持而可以在同一时间执行多于一个线程,进而提高总体处理性能。具备这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器.在一个程序中,这些独立运行的程序片断叫作线程(Thread).利用它编程的概念就叫作多线程.具备多线程能力的计算机因有硬件支持而可以在同一时间执行多个一个线程,提高总体处理性能.html
一.什么叫进程? git
进程是指在系统中正在运行的一个应用程序. 每一个进程之间是独立的.每一个进程均运行在其赚佣且受保护的内存空间内.程序员
二.什么是线程? github
1.用来执行进程的任务的叫线程.编程
2.每一个进程必须至少要有一个线程.网络
3.线程是进程的基本执行单元.多线程
4.一个进程的全部任务都在线程中执行.并发
5.一个程序只有一个进程,一个进程可能会有一个或者多个线程.进程包含线程.app
6.主线程是系统开辟的,其余任何线程都是手动开辟的.异步
三.线程的串行和并行分别是什么?
1.串行.
每个线程同一时间内只能执行一个任务.就像Boss指挥一我的作完A事情,再作B事情,最后作C事情.
2.并行.
可是由于时间缘由,Boss嫌一我的来作事件A,B,C时间太长.想要同时指挥多我的一块儿来作这三件事件.
四.多线程.
三我的一块儿完成这三件事用专业术语将就叫多线程.
1.多线程的原理.
同一时间,CPU只能处理一条线程,只有一个线程在工做.可是CPU若是快速的在多个线程之间切换的话,就能让多条线程同时执行.若是CPU切换的比较快的话,能够当作多个线程并发执行.可是CPU的工做能力毕竟有限,同时执行不少个线程,每条线程被切换到的频率就会下降,时间就会变长.因此要合理使用多线程.
2.多线程的优缺点
优势 |
缺点 |
1.能适当的提升传程序的执行效率 2. 能适当的提升资源的利用效率 |
1. 开启多线程须要占用必定的内存空间,主线程占用1M,子线程占用512KB. 2. 线程越多,CPU在调度线程上的开销就越大. 3. 程序设计更复杂.好比:线程之间的通信,多线程的数据共享等
|
3. 多线程在开发中的应用
主线程:一个iOS程序运行后,默认会开启1条线程,称为"主线程"或者"UI线程". 在iOS中除了主线程,其余子线程都是独立于Cocoa Touch的,因此只有主线程能够更新UI界面.
主线程的做用: 显示/刷新UI界面,处理UI事件(点击事件,滚动事件,拖拽事件等)
注意点: 不要将刷新比较耗时的放到主线程中.耗时操做会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验.
4. 多线程的使用.
若是我有一个个按钮A和一个UITextView. A按钮在当前线程(UI线程)下作一个for循环(循环10万次输出)点击完A按钮,当即拖拽UITextView,许久后才有反应.
5.任务的概念.
有两种执行方式:同步执行和异步执行.
(1) 同步执行(sync): 会阻塞当前线程并等待Block执行完毕,而后在当前线程才会继续往下执行. 会尽量的在当前线程派发任务,但若是在其余队列往主队列中同步派发,任务会在主线程中执行.
(2) 异步执行(async): 当前线程继续执行,不会阻塞当前线程. 不必定会新建一个线程,例如在主线程异步派发到主线程,派发依旧是异步线程的,任务也会在主线程中执行.
同步异步的区别,不在因而否会开辟一个线程,在于派发方法是否须要等待Block完成后才返回.
6.队列的概念.
用于存听任务.分为串行队列和并行队列.
(1)串行队列:放到串行队列中的任务,GCD会FIFO(先进先出)的取出一个,执行一个,而后取出下一个,这样一个一个的执行.
(2)并行队列:放到并行队列中任务,GCD也会FIFO的取出来,但不一样的是,取出来一个任务就会放到别的线程中去,燃火取出来另外一个又放到另外一个线程中.因为取的动做很快,能够忽略不计,看起来全部的任务都是一块儿执行的.不过须要注意,GCD会根据系统资源控制并行的数量,因此任务不少也不会把全部的任务都执行.
不管串行仍是并行队列,任务启动顺序都是按照FIFO的,只是并发队列容许同一时间有多个任务都在执行.
FIFO是 First Input First Output的缩写,先入先出队列,这是一种传统的按序执行方法,先进入的指令先完成并引退,跟着才执行第二条指令。
7.组合方式:
(1)串行队列同步执行:
(2)串行队列异步执行:
(3)并行队列同步执行:
(4)并行队列异步执行:
四.目前有四种多线程的实现方法.
1.Pthreads.
基于C的,适合作跨平台的SDK.
2.NSThread.
NSThread是轻量级的多线程开发,使用起来也并不复杂,可是使用NSThread须要本身管理线程生命周期.
目前我就用到
[NSThread currentThread]获取当前线程.主要用于调试.
[NSThread sleepForTimeInterval:<#(NSTimeInterval)#>]; 延时多少秒
3.NSOperation & NSOperationQueue
待整理
4.GCD.
queue--线程 thread--队列 diapatch--派遣 serial--连续的 concurrent--并发的
(1)这四总方式是随着iOS的发展逐渐引进的,因此后者比前者使用更加简单,而且GCD也是目前苹果官方比较推荐的(充分利用了多核运算性能).
(2)GCD的全拼 --> Grand Central Dispatch --> 集中调度,是iOS开发的一个多核编程解决方案.会本身管理线程的生命周期(建立线程,调度任务,销毁线程),不须要本身手动管理,只要要求它作就好了.使用灵活方便.
(3)在GCD中一个操做是多线程仍是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列而且使用异步方法执行才能在多个线程中执行.
(4)串行队列是能够按照顺序执行的,并行队列的异步方法是没法按照顺序执行的.
(5)UI界面的更新最好采用同步方法,其余采用异步方法.
(6)GCD中多线程操做方法不须要使用@autoreleasepool,GCD会管理内存.
(7)GCD是完成面向过程的.
(8)GCD的工做原理:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.
(9)一个任务能够是一个函数(function)或者是一个Block,GCD的底层依然是用线程实现的,不过这样可让程序员不用关注实现的细节.
(10) dispatch queue 分为三种
serial(连续的) | 又称为private dispatch queues,同时只执行一个任务.Serial queue 一般用于同步访问特定的资源或者数据.当你建立多个Serial queue时候,虽然他们各自是同步的,可是Serial queue之间是并发的. |
Concurrent(并发的) | 又称为Global dispatch queue,能够并发的执行多个任务,可是执行任务的顺序是随机的. |
Main dispatch queue (主队列) |
它是全局可用的serial queue,它是在应用程序主线程上执行任务的. |
So GCD is the leading role of today.
五.代码示例
1.获取主线程
/** 获取主线程 1. 全部的刷新UI界面的任务都要在主线程执行. 2. 将消耗时间的任务放在别的线程中出来,尽可能不要在主线程中处理. */ dispatch_queue_t main_queue = dispatch_get_main_queue(); NSLog(@"main_queue:\n %@",main_queue);
/*! * @function dispatch_get_main_queue * * @abstract * Returns the default queue that is bound to the main thread. * * @discussion * In order to invoke blocks submitted to the main queue, the application must * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main * thread. * * @result * Returns the main queue. This queue is created automatically on behalf of * the main thread before main() is called. */ DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW dispatch_queue_t dispatch_get_main_queue(void) { return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q); }
翻译一波
dispatch_get_main_queue的功能
1. 摘要
返回绑定到主线程的默认队列
2. 讨论
为了请求blocks提交到主线程,主队列的申请必须呼叫到dispatch_main,NSApplicationMain(),或者用一个CFRunLoop.
3.结果
返回主线程.为了主队列能再main()以前被呼叫,这个队列应该被自动的建立.
dispatch_get_main_queue 也是一种dispatch_queue_t
2.本身建立队列
/** 本身建立的队列 dispatch_queue_create 参数1: 第一个参数是标识符.用于DEBUG的时候标志惟一的队列,能够为空. 参数2: 第二个参数用来表示建立的队列是串行的仍是并行的.传入DISPATCH_QUEUE_SERIAL或者NULL表示建立的是串行队列.传入DISPATCH_QUEUE_CONCURRENT表示建立的并行队列. (SERIAL--> serial连续的/CONCURRENT--> concurrent,并发的,一致的) */
// 建立串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(nil, NULL); NSLog(@"serialQueue:\n %@",serialQueue); // 建立并行队列: 这应该是惟一一个并行队列,只要是并行任务通常都加入到这个队列
dispatch_queue_t concurrentQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT); NSLog(@"concurrentQueue:\n %@",concurrentQueue);
3.建立任务
dispatch_queue_t serialQueue = dispatch_queue_create(nil, NULL);
// 建立任务
/** 同步任务 (sync) 1. 会阻塞当前线程. */ dispatch_sync( serialQueue, ^{ for (int i = 0; i < 10000; i ++) { NSLog(@"同步任务: \n%@",[NSThread currentThread]); } }); /** 异步任务 (async) 1. 不会阻塞当前线程. */ dispatch_async(serialQueue, ^{ NSLog(@"异步任务: %@",[NSThread currentThread]); });
4.建立队列组
dispatch_group_async能够实现监听一组任务是否完成,完成后获得通知执行其余的操做。这个方法颇有用,好比你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码
//1. 建立队列组
dispatch_group_t group = dispatch_group_create(); /**2. 建立队列 dispatch_get_global_queue 会获取一个全局队列,咱们姑且理解为系统为咱们开启的一些全局线程。咱们用priority指定队列的优先级,而flag做为保留字段备用(通常为0)。并行队列的执行顺序与其加入队列的顺序相同. #define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) */ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //3. 屡次使用队列中的方法执行任务,只有异步任务 //3.1 执行三次循环
dispatch_group_async(group, queue, ^{ for (int i = 0; i < 3; i ++) { NSLog(@"group-01 - %@",[NSThread currentThread]); } }); //3.2 主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (int i = 0; i < 8; i ++) { NSLog(@"group-02 - %@",[NSThread currentThread]); } }); //3.3 执行5次循环
dispatch_group_async(group, queue, ^{ for (int i = 0; i < 5; i ++) { NSLog(@"group-03 - %@",[NSThread currentThread]); } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 - %@",[NSThread currentThread]); });
5. 死锁现象
(1)现象1
NSLog(@"以前==> %@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"sync==> %@",[NSThread currentThread]); }); NSLog(@"以后==> %@",[NSThread currentThread]); /** 解释 1. 只会打印第一句:以前==> <NSThread: 0x7fe66b700610>{number = 1, name = main} ,而后主线程就卡死了,你能够在界面上放一个按钮,你就会发现点不了了。 2. 打印完第一句,dispatch_sync(由于是一个同步任务,会阻塞当前的线程)会阻塞当前的主线程,而后把Block中的任务放到main_queue中,main_queue中的任务会被取出来放到主线程中执行,但主线程种鸽时候已经被阻塞了,因此Block种鸽的任务就不能完成,它不完成,dispatch_sync就会一直阻塞主线程.致使主线程一直卡死.这就是死锁现象. */
(2)现象2
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL); NSLog(@"输出1.以前==> %@",[NSThread currentThread]); dispatch_async(queue, ^{ NSLog(@"输出2.sync以前==> %@",[NSThread currentThread]); dispatch_sync(queue, ^{ NSLog(@"输出3.sync==> %@",[NSThread currentThread]); }); NSLog(@"输出4.sync以后==> %@",[NSThread currentThread]); }); NSLog(@"输出5.以后==> %@",[NSThread currentThread]); /** 解释 1. 当前线程为默认的主线程 2. 输出结果为,输出1,输出5和输出2 执行了输出.输出3和输出4没有被执行. 3. 按照执行顺序分析. (1)咱们建立的队列queue是一个串行队列(DISPATCH_QUEUE_SERIAL).串行队列的特色是,所持有的任务会取出一个执行一个.当前任务没有执行完,下一个任务不会被执行. (2)打印出输出1. (3)在queue队列中开启了一个异步任务(async).异步任务的特色是,当前的线程不会被阻塞.因此有了两条线程,一条是主线程中执行输出5.另外一条是在新开辟的queue线程中执行输出2. (4)在开辟的queue线程中,又执行了一个同步的任务(sync),同步任务的特色是执行一个任务会阻塞当前的线程.当前的线程是queue,已经被阻塞了.又要求它去执行下一个任务.就形成了死锁现象.因此 sync 所在的线程被卡死了,输出3和输出4天然就不会打印了. */
六.GCD经常使用方法
1.延迟
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ <#code to be executed after a specified delay#> });
2.从其余线程回到主线程的方法
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程以后,须要执行的代码
});
3. 一次性执行
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // code to be executed once
});
&onceToken的意思: &表示取地址符,这个是定义一个静态变量,而后再dispatch_once函数第一次运行时,写入数据,以后就不会再次写入,能够保证后面的block函数内部的代码只被执行一次.
4.自定义dispatch_queue_t
dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL); dispatch_async(urls_queue, ^{ // your code
});
5.程序在后台较长时间运行.
// AppDelegate.h文件
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask; // AppDelegate.m文件
- (void)applicationDidEnterBackground:(UIApplication *)application { [self beingBackgroundUpdateTask]; // 在这里加上你须要长久运行的代码
[self endBackgroundUpdateTask]; } - (void)beingBackgroundUpdateTask { self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void)endBackgroundUpdateTask { [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid; }
6.并行队列异步任务的具体使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行一些耗时间的操做
dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,刷新UI,或者点击触发UI事件
}); }); // 实际使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"https://gd2.alicdn.com/imgextra/i1/0/TB1p6QnOFXXXXbFXFXXXXXXXXXX_!!0-item_pic.jpg"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ self.imageView_one.image = image; }); } });
7. dispatch_barrier_sync
barrier 障碍物
dispatch_barrier_async是在前面的任务执行结束后它才执行,并且它后面的任务等它执行完成以后才会执行
dispatch_queue_t queue = dispatch_queue_create("customIdenrifier", NULL); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"dispatch_async1"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:4]; NSLog(@"dispatch_async2"); }); dispatch_barrier_sync(queue, ^{ NSLog(@"dispatch_barrier_async"); [NSThread sleepForTimeInterval:4]; }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"dispatch_async3"); });
8. dispatch_apply
屡次执行里面的代码块
参数1: 执行的次数
参数2: 在那个队列中执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(5, queue, ^(size_t index) { NSLog(@"第%zu次执行",index); });
七.代码实现串行队列
使用串行队列时首先要建立一个串行队列,而后调用异步调用方法,在此方法中传入串行队列和线程操做便可自动执行。下面使用线程队列演示图片的加载过程,你会发现多张图片会按顺序加载,由于当前队列中只有一个线程。
#import "Level_four_itemFiveViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
@interface Level_four_itemFiveViewController () @property (nonatomic, strong) UIButton * clickedButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; @end
@implementation Level_four_itemFiveViewController #pragma mark - 生命周期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 点击事件
- (void)clickedButtonClickd { // 多线程下载图片
NSInteger count = self.imageNamesArrayM.count; //建立一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多个线程用于填充图片
for (int i = 0; i < count; ++i) { //异步执行队列任务
dispatch_async(serialQueue, ^{ [self loadImage:i]; }); } } #pragma mark 加载图片
-(void)loadImage:(NSUInteger )index{ //请求数据
NSString * urlStr = [self.imageNamesArrayM objectAtIndex:index]; NSURL *url=[NSURL URLWithString:urlStr]; NSData *data=[NSData dataWithContentsOfURL:url]; //更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } #pragma mark 将图片显示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; } #pragma mark - 网络请求
- (void)sendNetWorking { //建立图片连接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", ]; self.imageNamesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 实现方法
#pragma mark 基本设置
- (void)basicSetting { self.title = @"串行队列"; self.view.backgroundColor = [UIColor whiteColor]; } #pragma mark - UI布局
- (void)initUI { NSInteger total = self.imageNamesArrayM.count; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列数
NSInteger row = i / 3; // 行数
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.clickedButton]; [self.clickedButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.right.mas_equalTo(self.view); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)clickedButton { if (_clickedButton == nil) { self.clickedButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.clickedButton.backgroundColor = [UIColor orangeColor]; [self.clickedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.clickedButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.clickedButton setTitle:@"加载图片" forState:UIControlStateNormal]; [self.clickedButton addTarget:self action:@selector(clickedButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _clickedButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } @end
八.代码实现并行队列
并发队列一样是使用dispatch_queue_create()方法建立,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行建立,可是在实际开发中咱们一般不会从新建立一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列(固然若是有多个并发队列可使用前者建立)。下面经过并行队列演示一下多个图片的加载. 其余代码和串行队列的实现相同,只是点击事件有区别
- (void)clickedButtonClickd { // 多线程下载图片
NSInteger count = self.imageNamesArrayM.count; //建立一个并行队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //建立多个线程用于填充图片
for (int i = 0; i < count; ++i) { //异步执行队列任务
dispatch_async(globalQueue, ^{ [self loadImage:i]; }); //同步执行队列任务
dispatch_sync(globalQueue, ^{ // 全部的图片都在主线程中加载.主线程被阻塞.致使全部照片一次性被显示.
}); /** 在GCD中一个操做是多线程执行仍是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列而且使用异步方法执行时才能在多个线程中执行。 串行队列能够按顺序执行,并行队列的异步方法没法肯定执行顺序。 UI界面的更新最好采用同步方法,其余操做采用异步方法。 */ } }
九.线程同步(NSLock,@synchronized代码块,GCD信号机制)
说到多线程就不得不提多线程中的锁机制,多线程操做过程当中每每多个线程是并发执行的,同一个资源可能被多个线程同时访问,形成资源抢夺,这个过程当中若是没有锁机制每每会形成重大问题。举例来讲,每一年春节都是一票难求,在12306买票的过程当中,成百上千的票瞬间就消失了。不妨假设某辆车有1千张票,同时有几万人在抢这列车的车票,顺利的话前面的人都能买到票。可是若是如今只剩下一张票了,而同时还有几千人在购买这张票,虽然在进入购票环节的时候会判断当前票数,可是当前已经有100个线程进入购票的环节,每一个线程处理完票数都会减1,100个线程执行完当前票数为-99,遇到这种状况很明显是不容许的。
这里不妨还拿图片加载来举例,假设如今有8张图片,可是有15个线程都准备加载这8张图片,约定不能重复加载同一张图片,这样就造成了一个资源抢夺的状况。在下面的程序中将建立9张图片,每次读取照片连接时首先判断当前连接数是否大于1,用完一个则当即移除,最多只有8个。
#import "Level_four_itemSevenViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
@interface Level_four_itemSevenViewController () { // 使用GCD解决资源抢占问题
dispatch_semaphore_t _semaphore;//定义一个信号量
} @property (nonatomic, strong) UIButton * clickedButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; @property (nonatomic, strong) NSLock * lock; @end
@implementation Level_four_itemSevenViewController /** 说明 拿图片加载来举例,假设如今有8张图片,可是有15个线程都准备加载这8张图片,约定不能重复加载同一张图片,这样就造成了一个资源抢夺的状况。在下面的程序中将建立8张图片,每次读取照片连接时首先判断当前连接数是否大于1,用完一个则当即移除,最多只有8个. */
#pragma mark - 生命周期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 系统代理
#pragma mark - 点击事件
- (void)clickedButtonClickd { // 多线程如今图片
NSInteger count = 15; //建立一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多个线程用于填充图片
for (int i = 0; i < count; ++i) { //异步执行队列任务
dispatch_async(serialQueue, ^{ [self loadImage:i]; }); } } #pragma mark 加载图片
-(void)loadImage:(NSUInteger )index{ /** 三种方式 一个线程A已经开始获取图片连接,获取完以后尚未来得及从self.imageNamesArrayM中删除,另外一个线程B已经进入相应代码中,因为每次读取的都是self.imageNamesArrayM的最后一个元素,所以后面的线程其实和前面线程取得的是同一个图片连接这样就形成图中看到的状况。要解决这个问题,只要保证线程A进入相应代码以后B没法进入,只有等待A完成相关操做以后B才能进入便可。这样才不会出错. */
// 1
[self ThreadSynchronization_wayOneWithIndex:index]; // 2 //[self ThreadSynchronization_wayTwoWithIndex:index]; // 3 //[self ThreadSynchronization_wayThreeWithIndex:index];
} - (void)ThreadSynchronization_wayOneWithIndex:(NSInteger)index { NSString * urlStr; NSData *data; /** 线程同步方法1: NSLock 1.线程使用前加锁,线程使用后解锁 2.iOS中对于资源抢占的问题可使用同步锁NSLock来解决,使用时把须要加锁的代码(之后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间,一个线程A进入加锁代码以后因为已经加锁,另外一个线程B就没法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。须要注意的是lock和unlock之间的”加锁代码“应该是抢占资源的读取和修改代码,不要将过多的其余操做代码放到里面,不然一个线程执行的时候另外一个线程就一直在等待,就没法发挥多线程的做用了 */ [self.lock lock]; if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } [self.lock unlock]; if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } - (void)ThreadSynchronization_wayTwoWithIndex:(NSInteger)index { /** 线程同步方法2: @synchronized代码块 使用@synchronized解决线程同步问题相比较NSLock要简单一些,平常开发中也更推荐使用此方法。首先选择一个对象做为同步对象(通常使用self),而后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另外一个线程占用,若是占用该线程就会处于等待状态,直到同步对象被释放 */ NSString * urlStr; NSData *data; @synchronized (self) { if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } } if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } - (void)ThreadSynchronization_wayThreeWithIndex:(NSInteger)index { /** 线程同步方法三: GCD信号机制 初始化信号量 参数是信号量初始值 在GCD中提供了一种信号机制,也能够解决资源抢占问题(和同步锁的机制并不同)。GCD中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;若是信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理咱们能够初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”以后就调用信号等待命令(此时信号量为0)开始等待,此时其余线程没法进入,执行完后发送信号通知(此时信号量为1),其余线程开始进入执行,如此一来就达到了线程同步目的。 */ NSString * urlStr; NSData *data; _semaphore = dispatch_semaphore_create(1); dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (self.imageNamesArrayM.count > 0) { urlStr = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:urlStr]; } // 信号通知
dispatch_semaphore_signal(_semaphore); if (urlStr) { NSURL * url = [NSURL URLWithString:urlStr]; data = [NSData dataWithContentsOfURL:url]; } //更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ [self updateImageWithData:data andIndex:index]; }); } #pragma mark 将图片显示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(NSInteger)index{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; } #pragma mark - 网络请求
- (void)sendNetWorking { //建立图片连接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", ]; self.imageNamesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 实现方法
#pragma mark 基本设置
- (void)basicSetting { self.title = @"线程同步(NSLock,@synchronized代码块,GCD信号机制"; self.view.backgroundColor = [UIColor whiteColor]; } #pragma mark - UI布局
- (void)initUI { NSInteger total = 15; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列数
NSInteger row = i / 3; // 行数
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.clickedButton]; [self.clickedButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.right.mas_equalTo(self.view); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)clickedButton { if (_clickedButton == nil) { self.clickedButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.clickedButton.backgroundColor = [UIColor orangeColor]; [self.clickedButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.clickedButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.clickedButton setTitle:@"加载图片" forState:UIControlStateNormal]; [self.clickedButton addTarget:self action:@selector(clickedButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _clickedButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } - (NSLock *)lock { if (_lock == nil) { self.lock = [[NSLock alloc] init]; } return _lock; } @end
十.GCD控制线程通信
因为线程的调度是透明的,程序有时候很难对它进行有效的控制,为了解决这个问题iOS提供了NSCondition来控制线程通讯(同前面GCD的信号机制相似)。NSCondition实现了NSLocking协议,因此它自己也有lock和unlock方法,所以也能够将它做为NSLock解决线程同步问题,此时使用方法跟NSLock没有区别,只要在线程开始时加锁,取得资源后释放锁便可,这部份内容比较简单在此再也不演示。固然,单纯解决线程同步问题不是NSCondition设计的主要目的,NSCondition更重要的是解决线程之间的调度关系(固然,这个过程当中也必须先加锁、解锁)。NSCondition能够调用wati方法控制某个线程处于等待状态,直到其余线程调用signal(此方法唤醒一个线程,若是有多个线程在等待则任意唤醒一个)或者broadcast(此方法会唤醒全部等待线程)方法唤醒该线程才能继续。
#import "Level_four_itemEightViewController.h"
#import "Masonry.h"
#import "Header.h"
#define ItemWH (ScreenW - MARGIN*4) / 3
#define MARGIN 20
#define MYQUEUE "myThreadQueue1"
#define ImageCount 8
@interface Level_four_itemEightViewController () { NSMutableArray *_imagesArrayM; } @property (nonatomic, strong) UIButton * createButton; @property (nonatomic, strong) UIButton * loadButton; @property (nonatomic, strong) NSMutableArray * imageViewsArrayM; @property (nonatomic, strong) NSMutableArray * imageNamesArrayM; #pragma mark 当前加载的图片索引(图片连接地址连续) @property (atomic,assign) int currentIndex; @property (nonatomic, strong) NSCondition * condition; @end
@implementation Level_four_itemEightViewController /** 线程的调度是透明的,程序有时候很难对它进行有效的控制.iOS提供了NSCondition来控制线程通讯.NSCondition也遵照NSLocking协议, 因此它自己就有lock和unlock方法.NSCondation能够解决线程同步的问题,可是更重要的是能解决线程之间的调度问题,固然这个过程也须要先加锁和解锁. wait方法控制某个线程处于等待状态. signal方法唤起一个线程,若是有多个线程,则任意唤起一个. broadcast方法唤起全部等待的线程. */
#pragma mark - 生命周期
#pragma mark viewDidLoad
- (void)viewDidLoad { [super viewDidLoad]; [self basicSetting]; [self sendNetWorking]; [self initUI]; } #pragma mark - 系统代理
#pragma mark - 点击事件
#pragma mark 产生图片
- (void)createButtonClickd { // 异步建立一张图片
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 建立图片连接
dispatch_async(globalQueue, ^{ [self createImageName]; }); } - (void)createImageName { [self.condition lock]; // 若是当前有图片则不建立,线程处于等待状态
if (self.imageNamesArrayM.count > 0) { NSLog(@"createImageName wait, current:%i",_currentIndex); [self.condition wait]; } else { NSLog(@"createImageName work, current:%i",_currentIndex); //生产者,每次生产1张图片
NSString * str = [_imagesArrayM objectAtIndex:_currentIndex]; [self.imageNamesArrayM addObject:str]; _currentIndex ++; //建立完图片则发出信号唤醒其余等待线程
[self.condition signal]; } [self.condition unlock]; } #pragma mark 加载图片
- (void)loadButtonClickd { if (_currentIndex > 8) { UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"图片张数超过8张,返回重试!" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [self.navigationController popViewControllerAnimated:YES]; }]; [alert addAction:cancel]; [self presentViewController:alert animated:YES completion:nil]; } else { // 多线程下载图片 //建立一个串行队列
dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //建立多个线程用于填充图片 //异步执行队列任务
dispatch_async(serialQueue, ^{ [self loadImage:_currentIndex - 1]; }); } } #pragma mark 加载图片
-(void)loadImage:(NSUInteger)index{ // 加锁
[self.condition lock]; // 若是当前有图片资源则加载,不然等待
if (self.imageNamesArrayM.count > 0) { NSLog(@"loadImage work,index is %lu",(unsigned long)index); [self loadAnUpdateImageWithIndex:index]; [_condition broadcast]; }else{ NSLog(@"loadImage wait,index is %lu",(unsigned long)index); //线程等待
[_condition wait]; NSLog(@"loadImage resore,index is %lu",(unsigned long)index); //一旦建立完图片当即加载
[self loadAnUpdateImageWithIndex:index]; } [self.condition unlock]; } -(void)loadAnUpdateImageWithIndex:(NSUInteger)index{ //请求数据
NSData *data= [self requestData:index]; //更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= self.imageViewsArrayM[index]; imageView.image=image; }); } -(NSData *)requestData:(NSUInteger)index{ NSData *data; NSString *name; name = [self.imageNamesArrayM lastObject]; [self.imageNamesArrayM removeObject:name]; if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark - 网络请求
- (void)sendNetWorking { //建立图片连接
NSArray * array = @[ @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175103820-2144487664.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175109523-327441423.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175115961-859836922.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175131195-2009565896.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175136617-306726060.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175036054-1229067335.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175046054-1410401557.png", @"http://images2015.cnblogs.com/blog/844918/201612/844918-20161230175055570-783207556.png", ]; _imagesArrayM = [NSMutableArray arrayWithArray:array]; } #pragma mark - 实现方法
#pragma mark 基本设置
- (void)basicSetting { self.title = @"串行队列"; self.view.backgroundColor = [UIColor whiteColor]; _currentIndex = 0; } #pragma mark - UI布局
- (void)initUI { NSInteger total = 8; for (int i = 0; i < total; i ++) { NSInteger column = i % 3; // 列数
NSInteger row = i / 3; // 行数
UIImageView * imageView = [[UIImageView alloc] init]; imageView.backgroundColor = [UIColor orangeColor]; [self.view addSubview:imageView]; [imageView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view).with.offset((MARGIN + ItemWH) * column + MARGIN); make.size.mas_equalTo(CGSizeMake(ItemWH, ItemWH)); make.top.mas_equalTo(self.view).with.offset(74 + (MARGIN + ItemWH) * row); }]; [self.imageViewsArrayM addObject:imageView]; } [self.view addSubview:self.createButton]; [self.createButton mas_makeConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo(self.view); make.width.mas_equalTo(ScreenW / 2); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; [self.view addSubview:self.loadButton]; [self.loadButton mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.view); make.width.mas_equalTo(ScreenW / 2); make.bottom.mas_equalTo(self.view); make.height.mas_equalTo(40); }]; } #pragma mark - setter & getter
- (UIButton *)createButton { if (_createButton == nil) { self.createButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.createButton.backgroundColor = [UIColor orangeColor]; [self.createButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.createButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.createButton setTitle:@"产生图片" forState:UIControlStateNormal]; [self.createButton addTarget:self action:@selector(createButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _createButton; } - (UIButton *)loadButton { if (_loadButton == nil) { self.loadButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.loadButton.backgroundColor = [UIColor greenColor]; [self.loadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; self.loadButton.titleLabel.font = [UIFont systemFontOfSize:15]; [self.loadButton setTitle:@"加载图片" forState:UIControlStateNormal]; [self.loadButton addTarget:self action:@selector(loadButtonClickd) forControlEvents:UIControlEventTouchUpInside]; } return _loadButton; } - (NSMutableArray *)imageViewsArrayM { if (_imageViewsArrayM == nil) { self.imageViewsArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageViewsArrayM; } - (NSMutableArray *)imageNamesArrayM { if (_imageNamesArrayM == nil) { self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:0]; } return _imageNamesArrayM; } - (NSCondition *)condition { if (_condition == nil) { self.condition = [[NSCondition alloc] init]; } return _condition; } @end
本篇博客所用的Demo地址: https://github.com/mancongiOS/multithreading
技术支持:
简书.做者:伯恩的遗产. 地址:http://www.jianshu.com/p/0b0d9b1f1f19
博客园.做者:文顶顶. 地址:http://www.cnblogs.com/wendingding/p/3805088.html
博客园.做者:KenshinCui 地址:http://www.cnblogs.com/kenshincui/p/3983982.html