说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。举例来说,每年春节都是一票难求,在12306买票的过程中,成百上千的票瞬间就消失了。不妨假设某辆车有1千张票,同时有几万人在抢这列车的车票,顺利的话前面的人都能买到票。但是如果现在只剩下一张票了,而同时还有几千人在购买这张票,虽然在进入购票环节的时候会判断当前票数,但是当前已经有100个线程进入购票的环节,每个线程处理完票数都会减1,100个线程执行完当前票数为-99,遇到这种情况很明显是不允许的。

iOS提供了两种常用的解决资源抢夺的方法。一种是实用NSLock同步锁,另一种是实用@synchronized代码块。两种方法的实现原理是类似的只是处理上实用代码块更加简单。

这里不妨还拿图片加载来举例,假设现在有9张图片,但是有15个线程都准备加载这9张图片,约定不能重复加载同一张图片,这样就形成了一个资源抢夺的情况。在下面的程序中将创建9张图片,每次读取照片链接时首先判断当前链接数是否大于1,用完一个则立即移除,最多只有9个。

#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
#define IMAGE_COUNT 9 @interface KCMainViewController (){
NSMutableArray *_imageViews;
NSMutableArray *_imageNames;
} @end @implementation KCMainViewController - (void)viewDidLoad {
[super viewDidLoad]; [self layoutUI];
} #pragma mark 界面布局
-(void)layoutUI{
//创建多个图片控件用于显示图片
_imageViews=[NSMutableArray array];
for (int r=; r<ROW_COUNT; r++) {
for (int c=; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView]; }
} UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(, , , );
[button setTitle:@"加载图片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button]; //创建图片链接
_imageNames=[NSMutableArray array];
for (int i=; i<IMAGE_COUNT; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
} } #pragma mark 将图片显示到界面
-(void)updateImageWithData:(NSData *)data andIndex:(int )index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
} #pragma mark 请求图片数据
-(NSData *)requestData:(int )index{
NSData *data;
NSString *name;
if (_imageNames.count>) {
name=[_imageNames lastObject];
[_imageNames removeObject:name];
}
if(name){
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
}
return data;
} #pragma mark 加载图片
-(void)loadImage:(NSNumber *)index{
int i=[index integerValue];
//请求数据
NSData *data= [self requestData:i];
//更新UI界面,此处调用了GCD主线程队列的方法
dispatch_queue_t mainQueue= dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
[self updateImageWithData:data andIndex:i];
});
} #pragma mark 多线程下载图片
-(void)loadImageWithMultiThread{
int count=ROW_COUNT*COLUMN_COUNT; dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
//创建多个线程用于填充图片
for (int i=; i<count; ++i) {
//异步执行队列任务
dispatch_async(globalQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
} }

首先在_imageNames中存储了9个链接用于下载图片,然后在requestData:方法中每次只需先判断_imageNames的个数,如果大于一就读取一个链接加载图片。关键要看从_imageNames读取链接、删除链接的速度,如果足够快可能不会有任何问题,但是如果速度稍慢就会出现加载十五张图片的错误情况。

分析这个问题造成的原因主:当一个线程A已经开始获取图片链接,获取完之后还没有来得及从_imageNames中删除,另一个线程B已经进入相应代码中,由于每次读取的都是_imageNames的最后一个元素,因此后面的线程其实和前面线程取得的是同一个图片链接这样就造成图中看到的情况。要解决这个问题,只要保证线程A进入相应代码之后B无法进入,只有等待A完成相关操作之后B才能进入即可。下面分别使用NSLock和@synchronized对代码进行修改。

NSLock

iOS中对于资源抢占的问题可以使用同步锁NSLock来解决,使用时把需要加锁的代码(加锁代码)放到NSLock的Lock和unLock之间。一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程执行完之后。B线程才能访问加锁代码。需要注意的是Lock和unLock之间的加锁代码应该是抢占资源的读取和修改代码。不要将过多的其他操作代码放到里面,否则一个线程执行的时候另一个线程就一直在等待,就无法发挥多线程的作用了

另外,在上面的代码中 抢占资源 _imageNames定义了成员变量 这么做是不明智的 应该定义为源自属性。对于被抢占资源来说将其定义为原子属性是一个很好的习惯,因为有时候很难保证同一个资源不在别处读取和修改。nonatomic属性读取的是内存数据(寄存器计算好的结果),而atomic就保证直接读取寄存器的数据,这样一来就不会出现一个线程正在修改数据,而另一个线程读取了修改之前(存储在内存中)的数据,永远保证同时只有一个线程在访问一个属性

下面的代码演示了如何使用NSLock进行线程同步:

#define ROW_COUNT 5
#define COLUMN_COUNT 3
#define ROW_HEIGHT 100
#define ROW_WIDTH ROW_HEIGHT
#define CELL_SPACING 10
#define IMAGE_COUNT 9 @interface ViewController () {
NSMutableArray *_imageViews; NSLock *_lock;
} @property (atomic,strong) NSMutableArray *imageNames; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad]; [self layoutUI];
} - (void)layoutUI {
_imageViews = [NSMutableArray array];
for (int r=; r<ROW_COUNT; r++) {
for (int c=; c<COLUMN_COUNT; c++) {
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
imageView.contentMode=UIViewContentModeScaleAspectFit;
// imageView.backgroundColor=[UIColor redColor];
[self.view addSubview:imageView];
[_imageViews addObject:imageView]; }
} UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame=CGRectMake(, , , );
[button setTitle:@"加载图片" forState:UIControlStateNormal];
//添加方法
[button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button]; //创建图片链接
_imageNames=[NSMutableArray array];
for (int i=; i<ROW_COUNT*COLUMN_COUNT; i++) {
[_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
} //初始化锁对象
_lock = [[NSLock alloc] init]; } - (void)loadImageWithMultiThread {
int count = ROW_COUNT * COLUMN_COUNT;
//创建一个串行队列
/*
第一个参数是 队列名称
第二个参数是 队列类型
*/
//注意 dispatch_queue_t的对象不是指针类型
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
//创建多个线程用于填充图片
for (int i = ; i < count; i++) {
//异步执行队列任务
dispatch_async(globalQueue, ^{
[self loadImage:[NSNumber numberWithInt:i]];
});
} } - (void)loadImage:(NSNumber *)index {
//如果在串行队列中会发现当前的线程打印完全一样 因为她们在一个线程中
NSLog(@"thread is: %@",[NSThread currentThread]);
int i = (int)[index integerValue];
//请求数据
NSData *data = [self requestData:i];
//回到主线程更新UI
dispatch_sync(dispatch_get_main_queue(), ^{
[self updateImageWithData:data andIndex:i];
}); } - (void)updateImageWithData:(NSData *)data andIndex:(int) index{
UIImage *image=[UIImage imageWithData:data];
UIImageView *imageView= _imageViews[index];
imageView.image=image;
} - (NSData *)requestData:(int)index {
NSData *data;
NSString *name; //加锁
[_lock lock];
if (_imageNames.count > ) {
name = [_imageNames lastObject];
[_imageNames removeObject:name];
}
//使用完jiesuo
[_lock unlock]; if (name) {
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
} return data;
}

前面也说过使用同步锁时如果一个线程A已经加锁,线程B就无法进入。那么B怎么知道是否资源已经被其他线程锁住呢?可以通过tryLock方法,此方法会返回一个BOOL型的值,如果为YES说明获取锁成功,否则失败。另外还有一个lockBeforeData:方法指定在某个时间内获取锁,同样返回一个BOOL值,如果在这个时间内加锁成功则返回YES,失败则返回NO。

@synchronized代码块

使用@synchronized解决线程同步问题相对比较NSLock简单一点,日常开发中爷推荐使用该方法。首先选择一个对象作为同步对象,一般选择self,然后将 加锁代码 放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另一个线程占用。如果被占用该线程就会处于等待状态。直到同步对象被释放。下面的代码演示了如何使用@synchronized进行线程同步。

- (NSData *)requestData:(int)index {
NSData *data;
NSString *name; //加锁
// [_lock lock];
// if (_imageNames.count > 0) {
// name = [_imageNames lastObject];
// [_imageNames removeObject:name];
// }
// //使用完jiesuo
// [_lock unlock];
//同步线程
@synchronized(self) {
if (_imageNames.count > ) {
name = [_imageNames lastObject];
[NSThread sleepForTimeInterval:0.001f];
[_imageNames removeObject:name];
}
} if (name) {
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
} return data;
}
http://www.cocoachina.com/ios/20160707/16957.html

 使用GCD解决资源抢占问题

在GCD中提供了一种信号机制,也可以枪战资源抢夺的问题(和同步锁的极致并不一样)。GCD 中信号量是dispatch_semaphore_t类型,支持信号通知和信号等待等。每当发送一个信号通知,信号量加一;每当发送一个等待信号则信号量减一。如果信号量为0则信号会处于等待状态,直到信号大于0才开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入加锁代码之后 就调用信号等待命令(此时信号量为0)开始等待。此时其他线程无法进入 执行完毕后发送信号通知(此时信号量为1)其他线程开始进入执行,如此一来就达到了线程同步的目的。

- (NSData *)requestData:(int)index {
NSData *data;
NSString *name; //加锁
// [_lock lock];
// if (_imageNames.count > 0) {
// name = [_imageNames lastObject];
// [_imageNames removeObject:name];
// }
// //使用完jiesuo
// [_lock unlock];
//同步线程
// @synchronized(self) {
// if (_imageNames.count > 0) {
// name = [_imageNames lastObject];
// [NSThread sleepForTimeInterval:0.001f];
// [_imageNames removeObject:name];
// }
// }
//初始化信号量
_semaphore = dispatch_semaphore_create(); //信号等待
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (_imageNames.count > ) {
name = [_imageNames lastObject];
[_imageNames removeObject:name];
}
//发送信号通知
dispatch_semaphore_signal(_semaphore); if (name) {
NSURL *url=[NSURL URLWithString:name];
data=[NSData dataWithContentsOfURL:url];
} return data;
}

总结

1.无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度。

2.更新UI应该在主线程中执行。并且推荐同步调用,常用的方法如下:

    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait (或者      -(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread])

    [NSOperationQueue mainQueue] addOperationWithBlock:
    dispatch_sync(dispatch_get_main_queue(), ^{})

3.NSThread适合轻量级多线程开发,控制线程顺序比较困难,同时线程总数无法控制。(每次创建并不能重复之前的线程 只能创建一个新的线程)

4.对于简单的多线程开发建议使用NSObject的扩展方法。而不必使用NSthread

5.可以使用NSThread的currentThread方法取得当前线程,使用 sleepForTimeInterval:方法让当前线程休眠。

6.NSOperation进行多线程开发可以控制线程总数及线程依赖关系。

7.创建一个NSOPeration不应该直接调用start方法。(如果直接Start则会在主线程中执行)

8.相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。

9.NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。

10.在GCD中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。

11.在GDC中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)。

12.关于线程同步 抢夺资源的问题@synchronized最简单,但是dispatch_semaphore_t更加高效。

附加iOS中保证线程安全的几种方式的性能对比

http://www.cocoachina.com/ios/20160707/16957.html

iOS多线程开发资源抢夺和线程间的通讯问题的更多相关文章

  1. ios 多线程开发(二)线程管理

    线程管理 iOS和OS X中每一个进程(或程序)由一个或多个线程组成.程序由一个运行main方法的线程开始,中间可以产生其他线程来执行一些指定的功能. 当程序产生一个新线程后,这个线程在程序进程空间内 ...

  2. iOS多线程开发

    概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的,一个复杂的多步操 ...

  3. ios 多线程开发(三)Run Loops

    Run loops是线程相关的一些基本东西.一个run loop是一个处理消息的循环.用来处理计划任务或者收到的事件.run loop的作用是在有事做的时候保持线程繁忙,没事的时候让线程挂起. Run ...

  4. java线程间的通讯

    主要通过wait()和notify()方法进行线程间的通讯 class Product extends Thread{ String name; float price; boolean flag = ...

  5. ios 多线程开发(一)简介

    简介 线程是在一个程序中并发的执行代码的方法之一.虽然有一些新的技术(operations, GCD)提供了更先进高效的并发实现,OS X和iOS同时也提供了创建和维护线程的接口. 这里将要介绍线程相 ...

  6. iOS多线程开发--NSThread NSOperation GCD

    多线程 当用户播放音频.下载资源.进行图像处理时往往希望做这些事情的时候其他操作不会被中 断或者希望这些操作过程中更加顺畅.在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势 ...

  7. C++多线程编程(三)线程间通信

    多线程编程之三——线程间通讯 作者:韩耀旭 原文地址:http://www.vckbase.com/document/viewdoc/?id=1707 七.线程间通讯 一般而言,应用程序中的一个次要线 ...

  8. iOS多线程开发之离不开的GCD(上篇)

    一.GCD基本概念 GCD 全称Grand Central Dispatch(大中枢队列调度),是一套低层API,提供了⼀种新的方法来进⾏并发程序编写.从基本功能上讲,GCD有点像NSOperatio ...

  9. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

随机推荐

  1. JavaScript学习03 JS函数

    JavaScript学习03 JS函数 函数就是包裹在花括号中的代码块,前面使用了关键词function: function functionName() { 这里是要执行的代码 } 函数参数 函数的 ...

  2. Android的生命周期学习

    掌握Android的生命周期对于如何一个刚刚接触Android初学者来说是至关重要的,在然后的开发中会给我留有更多的时刻余地,当自己正在认识Android中整个声明周期后,会编写出更加的流畅的程序 应 ...

  3. C#生成注册码

    string t = DateTime.Now.Ticks.ToString(); t = DESKey.DESEncrypt(t, DESKey.DesKeyStr); string[] strid ...

  4. teiid入门

    teiid,是jboss组件中进行数据虚拟化的部件(data virtualization).但是对teiid的介绍比较少,这里记录一下我的学习过程. 一.编译安装teiid teiid的安装,可以选 ...

  5. U-BLOX GPS 模块及GPRMC指令解析

    受朋友所托,调试一款GPS模块,该模块是UBLOX的NEO-6M GPS模组.想到用这款GPS的人较多,自己日后也有可能在用到这个模块,就写下这份笔记. 1. 介绍 基本信息如下: 1, 模块采用U- ...

  6. C#显示SQL语句格式

    --SQL SERVER生成测试环境: Create database Test; go USE [Test] GO if OBJECT_ID('Tab','U') is not null drop ...

  7. x01.os.12: 在 windows 中写 OS

    在 windows 中写操作系统,需要一系列的辅助工具.在此,要感谢川谷秀实!所有工具,都在 z_tools 文件夹中.有了大师的帮助,不妨也来尝试在 windows 中写一把 OS. 源代码及工具可 ...

  8. android The public type classname must be defined in its own file 报错

    The public type classname must be defined in its own file classname  为类名 错误提示,公用的类必髯有自己拥有独立.java文件 解 ...

  9. ES6 Set/WeakSet

    ES6里加入了一个新数据解构Set,和Java的Set一样,它里面不存放重复的元素.Set实现为一个类,使用时需要先new. var s1 = new Set() s1.add(1) s1.add(2 ...

  10. C语言中怎么将文件里的数据创建到(读到)链表中?

    定义的结构体: struct student { ]; //学生学号 ]; //学生姓名 struct student *next; //next 指针 指向 struct student 类型的变量 ...