多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器.在一个程序中,这些独立运行的程序片段叫做线程(Thread).利用它编程的概念就叫做多线程.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个一个线程,提升整体处理性能.
一.什么叫进程?
进程是指在系统中正在运行的一个应用程序. 每个进程之间是独立的.每个进程均运行在其赚佣且受保护的内存空间内.
二.什么是线程?
1.用来执行进程的任务的叫线程.
2.每个进程必须至少要有一个线程.
3.线程是进程的基本执行单元.
4.一个进程的所有任务都在线程中执行.
5.一个程序只有一个进程,一个进程可能会有一个或者多个线程.进程包含线程.
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 = ; i < ; 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, ); //3. 多次使用队列中的方法执行任务,只有异步任务
//3.1 执行三次循环
dispatch_group_async(group, queue, ^{
for (int i = ; i < ; i ++) {
NSLog(@"group-01 - %@",[NSThread currentThread]);
}
});
//3.2 主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (int i = ; i < ; i ++) {
NSLog(@"group-02 - %@",[NSThread currentThread]);
}
});
//3.3 执行5次循环
dispatch_group_async(group, queue, ^{
for (int i = ; i < ; 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, ), ^{ // 执行一些耗时间的操作
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,刷新UI,或者点击触发UI事件
});
}); // 实际使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ 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:];
NSLog(@"dispatch_async1");
}); dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"dispatch_async2");
});
dispatch_barrier_sync(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:];
}); dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:];
NSLog(@"dispatch_async3");
});
8. dispatch_apply
多次执行里面的代码块
参数1: 执行的次数
参数2: 在那个队列中执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
dispatch_apply(, 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 = ; 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 = ; i < total; i ++) { NSInteger column = i % ; // 列数
NSInteger row = i / ; // 行数 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( + (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();
}];
} #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:];
[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:];
} return _imageViewsArrayM;
} - (NSMutableArray *)imageNamesArrayM {
if (_imageNamesArrayM == nil) {
self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:];
} 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, ); //创建多个线程用于填充图片
for (int i = ; 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 = ; //创建一个串行队列 dispatch_queue_t serialQueue = dispatch_queue_create(MYQUEUE, DISPATCH_QUEUE_SERIAL); //创建多个线程用于填充图片
for (int i = ; 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才能进入即可。这样才不会出错.
*/ //
[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 > ) {
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 > ) {
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();
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (self.imageNamesArrayM.count > ) {
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 = ; for (int i = ; i < total; i ++) { NSInteger column = i % ; // 列数
NSInteger row = i / ; // 行数 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( + (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();
}];
} #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:];
[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:];
} return _imageViewsArrayM;
} - (NSMutableArray *)imageNamesArrayM {
if (_imageNamesArrayM == nil) {
self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:];
} 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, ); // 创建图片链接
dispatch_async(globalQueue, ^{ [self createImageName];
});
} - (void)createImageName { [self.condition lock]; // 如果当前有图片则不创建,线程处于等待状态
if (self.imageNamesArrayM.count > ) {
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 > ) {
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 - ];
});
}
} #pragma mark 加载图片
-(void)loadImage:(NSUInteger)index{ // 加锁
[self.condition lock]; // 如果当前有图片资源则加载,否则等待
if (self.imageNamesArrayM.count > ) {
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 = ;
} #pragma mark - UI布局
- (void)initUI { NSInteger total = ; for (int i = ; i < total; i ++) { NSInteger column = i % ; // 列数
NSInteger row = i / ; // 行数 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( + (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 / );
make.bottom.mas_equalTo(self.view);
make.height.mas_equalTo();
}]; [self.view addSubview:self.loadButton];
[self.loadButton mas_makeConstraints:^(MASConstraintMaker *make) { make.right.mas_equalTo(self.view);
make.width.mas_equalTo(ScreenW / );
make.bottom.mas_equalTo(self.view);
make.height.mas_equalTo();
}];
} #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:];
[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:];
[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:];
} return _imageViewsArrayM;
} - (NSMutableArray *)imageNamesArrayM {
if (_imageNamesArrayM == nil) {
self.imageNamesArrayM = [NSMutableArray arrayWithCapacity:];
} 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