iOS多线程开发资源抢夺和线程间的通讯问题

时间:2022-09-24 09:12:37

说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。举例来说,每年春节都是一票难求,在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&plus;&plus;多线程编程(三)线程间通信

    多线程编程之三——线程间通讯 作者:韩耀旭 原文地址: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. opencv基于HSV的肤色分割

    //函数功能:在HSV颜色空间对图像进行肤色模型分割 //输入:src-待处理的图像,imgout-输出图像 //返回值:返回一个iplimgae指针,指向处理后的结果 IplImage* SkinS ...

  2. &lbrack;ionic开源项目教程&rsqb; - 第12讲 医疗模块的实现以及Service层loadMore和doRefresh的提取封装

    关注微信订阅号:TongeBlog,可查看[ionic开源项目]全套教程. 这一讲主要实现tab2[医疗]模块,[医疗]模块跟tab1[健康]模块类似. [ionic开源项目教程] - 第12讲 医疗 ...

  3. LINUX CACHE IO THREAD

    http://www.penglixun.com/tech/system/linux_cache_discovery.html http://my.oschina.net/HardySimpson/b ...

  4. android studio——Failed to set up SDK

    最近使用android studio ,在IDE里面使用Gradle构建的时候,一直出现构建失败,失败信息显示Failed to set up SDK.然后 提示无法找到andriod-14平台,我更 ...

  5. jmeter&plus;maven&plus;jenkins自动化接口测试(下)

    maven+jmeter已经写好了,可以通过maven来执行jmeter的接口测试脚本,怎样实现定时执行测试并发送报告邮件就需要通过jenkins了(jmeter或者testng也可以结合不同的邮件j ...

  6. Java IO系列之四:NIO通信模型

    分布式rpc框架有很多,比如dubbo,netty,还有很多其他的产品.但他们大部分都是基于nio的, nio是非阻塞的io,那么它的内部机制是怎么实现的呢. 1.由一个专门的线程处理所有IO事件,并 ...

  7. ENVI&lowbar;REGISTER&lowbar;DOIT&lpar; &rpar;函数

    Envi_Register_Doit()函数利用控制点为裸数据定义投影坐标.   当将裸数据转为等经纬度投影时(Geographic),控制点pts中的经度值没有负值,0E~180E~360E,西经不 ...

  8. Python:random模块

    近排练习代码时候经常会用到random模块,以防后面忘记还是需要记录一下. 首先导入模块: import random random.random():用于生成一个0到1的随机浮点数: 0 <= ...

  9. MatCap冰冻效果Shader

    MatCap方案 使用说明 制作合适的MatCap贴图 这张图决定冰像不像,网上找.Vray渲个球.ASE或者ShaderForge连,甚至直接手绘,总之只要一张长得像下面的图 注意MatCap图只有 ...

  10. hql语句中的分页显示

    public List<User> getUserList(int pageInfo) { DBUtil dbutil = new DBUtil(); Session session = ...