iOS的多核编程和内存管理

 这篇文章转自  http://anxonli.iteye.com/blog/1097777,集中与iOS的多核编程和内存管理,大家完全可以使用苹果的多核编程框架来写出更加responsive的应用。

多核运算

    在iOS中concurrency编程的框架就是GCD(Grand Central Dispatch), GCD的使用非常简单。它把任务分派到不同的queue队列来处理。开发者把任务代码装到一个个block里面,操作系统把这些任务代码分派到不同的资源里去处理,一个简单的例子来说,为什么初学者写tableview的时候,滑动列表时总会很卡,因为很多初学者把图片装载放到main thread主线程去执行,例如我们要滑动畅顺的话,iOS最快可以1秒内刷新60次,如何你的一个cell的文字和图片装载超过1/60秒的话,自然就会卡。所以一般我们会把图片装载这些需要多点时间的移出main thread来处理,对于用GCD来说,就是把图片载入放到另外一个queue队列中来异步执行,当资源准备好了后,放回到main thread中显示出来。main thread在GCD中就是main queue。

    创建一个新queue队列的代码:

 dispatch_queue_t network_queue;  
 network_queue = dispatch_queue_create("com.myapp.network", nill); 

    例如,我们图片读取放到network_queue来进行异步执行

    dispatch_async(network_queue, ^{    
        UIImage *cellImage = [self loadMyImageFromNetwork:image_url];    
        // 将图片cache到本地    
        [self cacheImage:cellImage];    
        
        .....    
            
    } ); 

    dispatch_async的意思就是将任务进行异步并行处理,不一定需要一个任务处理完后才能处理下一个。以上代码loadMyImageFromNetwork的意思就是从网络中读取图片,这个任务交给network_queue来处理。这样读取图片的时间过长也不会阻塞主线程界面的处理。

    当我们处理完图片后,应该更新界面,从queue的概念去设计,就是要将更新界面的代码放到main queue中去,因为iOS里面永远是主线程来刷新重画UI。所以代码应该为,

    dispatch_async(network_queue, ^{    
        UIImage *cellImage = [self loadMyImageFromNetwork:image_url];    
        // 将图片cache到本地    
        [self cacheImage:cellImage];    
        
       // 回到主线程    
       dispatch_async(dispatch_get_main_queue(), ^{    
          // 显示图片到界面    
          [self displayImageToTableView:cellImage];    
       }];    
            
    } ); 

    dispatch_get_main_queue() 函数就是返回主线程,^{} 封装的就是任务代码,这样嵌套方式就可以从一个队列queue,跳到另一个queue,就是这么简单。

    我们一般可以把networking有关的代码放到一个queue,把图片resize的代码放到另外一个queue,处理完后更新界面,只需要嵌套跳回到 main queue。这样加上几行代码,你的程序就可以利用到系统多核资源,把具体的调度工作交给了操作系统自己来分配。有了这样的代码,不管你的硬件是单核,双核还是iMac的4核,甚至8核,都可以非常好地并行处理。

内存管理

    我一直惊叹iOS和Objective-C内存处理能力,例如iPad版本的iWork,Pages应用就是一个内存处理技术应用的鬼斧神工之作。想想256M内存的iPad,可以带来如此的华丽的界面同时获得如此流畅的用户体验,真是不简单。原因就是iOS一直提倡开发者在有限硬件资源内写出最优化的代码,使用CPU最少,占用内存最小。(以下代码适用于iOS SDK 4.1, 由于新SDK 4.2对内存使用有新改动,所以可能有不同。。。)

  1. 尽量少的UIView层
        通常我们喜欢把很多控件层(UILabel,UIButton,UIView等)一起放到一个大的UIView容器来显示我们的内容,这个方法一般是可以的,但是如果要经常重新刷新内容的大区域界面,多数发生在iPad的应用中,这个方法会带来过多的内存使用和动画的延迟(比较卡),例如,scrollview的动画比较卡,又或者,经常收到内存警告。其中一个重要原因是每个控件,特别是透明底的,会多次重新绘制(drawRect)过多。其解决办法是,尽量将几个控件合并到一个层上来显示,这样系统会减少系统调用drawRect,从而带来性能上的提升。
        很简单的一个例子,就是iNotes提供手写功能,用户可以在iPad屏幕上写出不同的笔画,开始的设计是,用户每写一划,iNotes就会生成一个新的透明底UIView来保持这个笔画,用户写了10笔,系统就生产了10个UIView,每个view的大小都是整个屏幕的,以便用户的undo操作。这个方案带来严重的内存问题,因为系统将每个层都保持一个bitmap图,一个像素需要4bit来算,一个层的大小就是 4x1024x768 ~ 3M, 10个层就是 10x3M = 30M,很明显,iPad很快爆出内存警告。
        这个例子最后的方案是,所有笔画都画在同一个层,iNotes可以保存笔画的点进行undo操作。这样的方案就是无论用户画多少笔画,界面重画需要的资源都是一样的。
  2. 显示最佳尺寸的图片
        很多程序员比较懒,网络上拿下来的图片,直接就用UIImageView将其显示给用户,这样的后果就是,程序需要一直保存着大尺寸的图片到内存。解决办法应该是先将图片缩小到需要显示的尺寸,释放大尺寸图片的内存,然后再显示到界面给用户。
  3. 尽量使用图片pattern,而不是一张大的图片
        例如,很多界面设计者喜欢在界面上放一个大底图,但这个底图是老是占用着内存的,最佳方案是,设计出一个小的pattern图,然后用这个方案显示成底图。
        UIImage *smallImage = [[UIImage alloc] initWithContentsOfFile:path]; 
        backgroundView.backgroundColor = [UIColor colorWithPatternImage:smallImage];  
        [smallImage release]; 
  4. 使用完资源后,立即释放
        一般objective-c的习惯是,用完的资源要立即释放,因为明白什么时候用完某个资源的是程序员你自己。例如,我们要读较大的图片,把它缩小后,显示到界面去。当大图片使用完成后,应该立即释放。代码如下:
        UIImage *fullscreenImage = [[UIImage alloc] initWithContentOfFile:path];  
        UIImage *smallImage = [self resizeImage:fullscreenImage]; 
        [fullscreenImage release];        
        imageView.image = smallImage;  

          
        ...... 
  5. 循环中大量生成的自动释放autorelease对象,可以考虑使用autorelease pool封装。代码范例:
        for(UIView *subview in bigView.subviews) {  
            // 使用autorelease pool自动释放对象池  
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  
          
            UIImageView *imageView = (UIImageView *)subview;  
              
            // subview处理代码  
            .......  
          
            // 销毁自动释放对象  
            [pool  drain];  
        }  

    自动释放对象池把每个循环内生成的临时对象使用完后立即释放

    以上的意见是本人多年来编写iPad/iPhone程序的经验,另外iOS4.0的multi-tasking特性发布后,程序可以被调入后台运行,苹果 工程师的意见是,进入后台运行时,你的应用应该释放掉能释放的对象,尽量保持在16M左右,这样别的程序运行时才不容易把你的应用挤掉。


 

Sqlite的多线程问题

  483人阅读  评论(0)  收藏  举报

SQLite是线程安全的吗?

有时候是的。为了线程安全,SQLite 必须在编译时把 THREADSAFE 预处理宏设为1。在缺省的发行的已编译版本中 Windows 版的是线程安全的,而 Linux 版的不是。如果要求线程安全,Linux 版的要重新编译。

“线程安全”是指二个或三个线程可以同时调用独立的不同的sqlite3_open() 返回的"sqlite3"结构。而不是在多线程中同时使用同一个 sqlite3 结构指针。

一个sqlite3结构只能在调用 sqlite3_open创建它的那个进程中使用。你不能在一个线程中打开一个数据库然后把指针传递给另一个线程使用。这是因为大多数多线程系统的限制(或 Bugs?)例如RedHat9上。在这些有问题的系统上,一个线程创建的fcntl()锁不能由另一个线程删除或修改。由于SQLite依赖fcntl()锁来进行并发控制,当在线程间传递数据库连接时会出现严重的问题。

也许在Linux下有办法解决fcntl()锁的问题,但那十分复杂并且对于正确性的测试将是极度困难的。因此,SQLite目前不允许在线程间共享句柄。

在UNIX下,你不能通过一个 fork() 系统调用把一个打开的 SQLite 数据库放入子过程中,否则会出错。

 

 

多进程可以同时打开同一个数据库,也可以同时 SELECT 。但只有一个进程可以立即改数据库。

SQLite 使用读/写锁定来控制数据库访问。(Win95/98/ME 操作系统缺乏读/写锁定支持,在低于 2.7.0 的版本中,这意味着在 windows 下在同一时间内只能有一个进程读数据库。在版本 2.7.0 中 这个问题通过在 windows 接口代码中执行一个用户间隔几率读写锁定策略解决了。) 但如果数据库文件在一个 NFS 文件系统中,控制并发读书的锁定机制可以会出错。因为 NFS 的fcntl() 文件锁定有时会出问题。如果有多进程可能并发读数据库则因当避免把数据库文件放在 NFS 文件系统中。根据微软的文档,如果不运行 Share.exe 后台程序则 FAT 文件系统中的锁定可能不工作。对 Windows 非常有经验的人告诉我网络文件的锁定有许多问题并且不可靠。如果是这样,在2个或以上 Windows 系统中共享一个 SQLite 数据库文件会导致不可预知的问题。

我们知道没有其他的嵌入式 SQL数据库引擎比SQLite支持更多的并发性。 SQLite允许多进程 同时打开和读取数据库。任何一个进程需要写入时,整个数据库将在这一过程中被锁定。但这一般仅耗时几毫秒。其他进程只需等待然后继续其他事务。其他嵌入式SQL数据库引擎往往只允许单进程访问数据库。

但是,client/server 型的数据库引擎 (如 PostgreSQL, MySQL, 以及 Oracle) 通常支持更高的并发度,并支持多进程同时写入同一个数据库。由于总有一个控制良好的服务器协调数据库的访问,这才保证了以上特性的实现。如果你的应用需要很高的并发度,你应该考虑使用client/server数据库。事实上,经验告诉我们大多数应用所需要的并发度比他们的设计者们想象的要少得多。

当 SQLite 尝试操作一个被另一个进程锁定的文件时,缺省的行为是返回 SQLITE_BUSY。你可以用 C代码更改这一行为。 使用 sqlite3_busy_handler() 或sqlite3_busy_timeout() API函数。

如果两个或更多进程同时打开同一个数据库,其中一个进程创建了新的表或索引,则其它进程可能不能立即看见新的表。其它进程可能需要关闭并重新连结数据库。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值