精华内容
下载资源
问答
  • 多处理器多线程软件性能优化多处理器多线程软件性能优化多处理器多线程软件性能优化多处理器多线程软件性能优化
  • 多线程是一种基于硬件或软件的处理技术,它的首要目标是计算型工作中利用并发来提高性能。多线程也可以用于区别各种任务,以便可以将优先权分配给更多时间敏感的流量,如语音、视频或关键数据。而公认的基于软件...
  • 利用多核多线程进行程序优化

    千次阅读 2013-12-21 22:52:52
    利用多核多线程进行程序优化

    样例程序

    程序功能:求从1一直到 APPLE_MAX_VALUE (100000000) 相加累计的和,并赋值给 apple 的 a 和 b ;求 orange 数据结构中的 a[i]+b[i ] 的和,循环 ORANGE_MAX_VALUE(1000000) 次。

    说明:

    1. 由于样例程序是从实际应用中抽象出来的模型,所以本文不会进行 test.a=test.b= test.b+sum 、中间变量(查找表)等类似的优化。
    2. 以下所有程序片断均为部分代码,完整代码请参看本文最下面的附件。
    清单 1. 样例程序
    #define ORANGE_MAX_VALUE      1000000
    #define APPLE_MAX_VALUE       100000000
    #define MSECOND               1000000
    
    struct apple
    {
         unsigned long long a;
    	unsigned long long b;
    };
    
    struct orange
    {
    	int a[ORANGE_MAX_VALUE];
    	int b[ORANGE_MAX_VALUE];
    	
    };
    
    int main (int argc, const char * argv[]) {
        // insert code here...
         struct apple test;
    	struct orange test1;
    	
    	for(sum=0;sum<APPLE_MAX_VALUE;sum++)
    	{
    		test.a += sum;
    		test.b += sum;
    	}
    	
         sum=0;
    	for(index=0;index<ORANGE_MAX_VALUE;index++)
    	{
    		sum += test1.a[index]+test1.b[index];
    	}
    
         return 0;
    }

    在检测程序运行时间这个复杂问题上,将采用 Randal E.Bryant 和 David R. O’Hallaron 提出的 K 次最优测量方法。假设重复的执行一个程序,并纪录 K 次最快的时间,如果发现测量的误差 ε 很小,那么用测量的最快值表示过程的真正执行时间, 称这种方法为“ K 次最优(K-Best)方法”,要求设置三个参数:

    K: 要求在某个接近最快值范围内的测量值数量。

    ε 测量值必须多大程度的接近,即测量值按照升序标号 V1, V2, V3, … , Vi, … ,同时必须满足(1+ ε)Vi >= Vk

    M: 在结束测试之前,测量值的最大数量。

    按照升序的方式维护一个 K 个最快时间的数组,对于每一个新的测量值,如果比当前 K 处的值更快,则用最新的值替换数组中的元素 K ,然后再进行升序排序,持续不断的进行该过程,并满足误差标准,此时就称测量值已经收敛。如果 M 次后,不能满足误差标准,则称为不能收敛。

    在接下来的所有试验中,采用 K=10,ε=2%,M=200 来获取程序运行时间,同时也对 K 次最优测量方法进行了改进,不是采用最小值来表示程序执行的时间,而是采用 K 次测量值的平均值来表示程序的真正运行时间。由于采用的误差 ε 比较大,在所有试验程序的时间收集过程中,均能收敛,但也能说明问题。

    为了可移植性,采用 gettimeofday() 来获取系统时钟(system clock)时间,可以精确到微秒。

    硬件:联想 Dual-core 双核机器,主频 2.4G,内存 2G

    软件:Suse Linunx Enterprise 10,内核版本:linux-2.6.16

    医生治病首先要望闻问切,然后才确定病因,最后再对症下药,如果胡乱医治一通,不死也残废。说起来大家都懂的道理,但在软件优化过程中,往往都喜欢犯这样的错误。不分青红皂白,一上来这里改改,那里改改,其结果往往不如人意。

    一般将软件优化可分为三个层次:系统层面,应用层面及微架构层面。首先从宏观进行考虑,进行望闻问切,即系统层面的优化,把所有与程序相关的信息收集上来,确定病因。确定病因后,开始从微观上进行优化,即进行应用层面和微架构方面的优化。

    1. 系统层面的优化:内存不够,CPU 速度过慢,系统中进程过多等
    2. 应用层面的优化:算法优化、并行设计等
    3. 微架构层面的优化:分支预测、数据结构优化、指令优化等

    软件优化可以在应用开发的任一阶段进行,当然越早越好,这样以后的麻烦就会少很多。

    在实际应用程序中,采用最多的是应用层面的优化,也会采用微架构层面的优化。将某些优化和维护成本进行对比,往往选择的都是后者。如分支预测优化和指令优化,在大型应用程序中,往往采用的比较少,因为维护成本过高。

    本文将从应用层面和微架构层面,对样例程序进行优化。对于应用层面的优化,将采用多线程和 CPU 亲和力技术;在微架构层面,采用 Cache 优化。

    利用并行程序设计模型来设计应用程序,就必须把自己的思维从线性模型中拉出来,重新审视整个处理流程,从头到尾梳理一遍,将能够并行执行的部分识别出来。

    可以将应用程序看成是众多相互依赖的任务的集合。将应用程序划分成多个独立的任务,并确定这些任务之间的相互依赖关系,这个过程被称为分解(Decomosition)。分解问题的方式主要有三种:任务分解、数据分解和数据流分解。关于这部分的详细资料,请参看参考资料一。

    仔细分析样例程序,运用任务分解的方法 ,不难发现计算 apple 的值和计算 orange 的值,属于完全不相关的两个操作,因此可以并行。

    改造后的两线程程序:

    清单 2. 两线程程序
    void* add(void* x)
    {		
    	for(sum=0;sum<APPLE_MAX_VALUE;sum++)
    	{
    		((struct apple *)x)->a += sum;
    		((struct apple *)x)->b += sum;	
    	}
    		
    	return NULL;
    }
    	
    int main (int argc, const char * argv[]) {
    		// insert code here...
    	struct apple test;
    	struct orange test1={{0},{0}};
    	pthread_t ThreadA;
    		
    	pthread_create(&ThreadA,NULL,add,&test);
    		
    	for(index=0;index<ORANGE_MAX_VALUE;index++)
    	{
    		sum += test1.a[index]+test1.b[index];
    	}		
    	
         pthread_join(ThreadA,NULL);
    
    	return 0;
    }

    更甚一步,通过数据分解的方法,还可以发现,计算 apple 的值可以分解为两个线程,一个用于计算 apple a 的值,另外一个线程用于计算 apple b 的值(说明:本方案抽象于实际的应用程序)。但两个线程存在同时访问 apple 的可能性,所以需要加锁访问该数据结构。

    改造后的三线程程序如下:

    清单 3. 三线程程序
    struct apple
    {
         unsigned long long a;
    	unsigned long long b;
    	pthread_rwlock_t rwLock;
    };
    
    void* addx(void* x)
    {
    	pthread_rwlock_wrlock(&((struct apple *)x)->rwLock);
    	for(sum=0;sum<APPLE_MAX_VALUE;sum++)
    	{
    		((struct apple *)x)->a += sum;
    	}
    	pthread_rwlock_unlock(&((struct apple *)x)->rwLock);
    	
    	return NULL;
    }
    
    void* addy(void* y)
    {
    	pthread_rwlock_wrlock(&((struct apple *)y)->rwLock);
    	for(sum=0;sum<APPLE_MAX_VALUE;sum++)
    	{
    		((struct apple *)y)->b += sum;
    	}
    	pthread_rwlock_unlock(&((struct apple *)y)->rwLock);
    	
    	return NULL;
    }
    
    
    
    int main (int argc, const char * argv[]) {
        // insert code here...
         struct apple test;
    	struct orange test1={{0},{0}};
    	pthread_t ThreadA,ThreadB;
    	
    	pthread_create(&ThreadA,NULL,addx,&test);
    	pthread_create(&ThreadB,NULL,addy,&test);
    
    	for(index=0;index<ORANGE_MAX_VALUE;index++)
    	{
    		sum+=test1.a[index]+test1.b[index];
    	}
    	
         pthread_join(ThreadA,NULL);
         pthread_join(ThreadB,NULL);
    	
         return 0;
    }

    这样改造后,真的能达到我们想要的效果吗?通过 K-Best 测量方法,其结果让我们大失所望,如下图:

    图 1. 单线程与多线程耗时对比图
    单线程与多线程耗时对比图

    为什么多线程会比单线程更耗时呢?其原因就在于,线程启停以及线程上下文切换都会引起额外的开销,所以消耗的时间比单线程多。

    为什么加锁后的三线程比两线程还慢呢?其原因也很简单,那把读写锁就是罪魁祸首。通过 Thread Viewer 也可以印证刚才的结果,实际情况并不是并行执行,反而成了串行执行,如图2:

    图 2. 通过 Viewer 观察三线程运行情况
    通过 Viewer 观察三线程运行情况

    其中最下面那个线程是主线程,一个是 addx 线程,另外一个是 addy 线程,从图中不难看出,其他两个线程为串行执行。

    通过数据分解来划分多线程,还存在另外一种方式,一个线程计算从1到 APPLE_MAX_VALUE/2 的值,另外一个线程计算从APPLE_MAX_VALUE/2+1 到 APPLE_MAX_VALUE 的值,但本文会弃用这种模型,有兴趣的读者可以试一试。

    在采用多线程方法设计程序时,如果产生的额外开销大于线程的工作任务,就没有并行的必要。线程并不是越多越好,软件线程的数量尽量能与硬件线程的数量相匹配。最好根据实际的需要,通过不断的调优,来确定线程数量的最佳值。


    加锁与不加锁

    针对加锁的三线程方案,由于两个线程访问的是 apple 的不同元素,根本没有加锁的必要,所以修改 apple 的数据结构(删除读写锁代码),通过不加锁来提高性能。

    测试结果如下:

    图 3. 加锁与不加锁耗时对比图
    加锁与不加锁耗时对比图

    其结果再一次大跌眼镜,可能有些人就会越来越糊涂了,怎么不加锁的效率反而更低呢?将在针对 Cache 的优化一节中细细分析其具体原因。

    在实际测试过程中,不加锁的三线程方案非常不稳定,有时所花费的时间相差4倍多。

    要提高并行程序的性能,在设计时就需要在较少同步和较多同步之间寻求折中。同步太少会导致错误的结果,同步太多又会导致效率过低。尽量使用私有锁,降低锁的粒度。无锁设计既有优点也有缺点,无锁方案能充分提高效率,但使得设计更加复杂,维护操作困难,不得不借助其他机制来保证程序的正确性。

    针对 Cache 的优化

    在串行程序设计过程中,为了节约带宽或者存储空间,比较直接的方法,就是对数据结构做一些针对性的设计,将数据压缩 (pack) 的更紧凑,减少数据的移动,以此来提高程序的性能。但在多核多线程程序中,这种方法往往有时会适得其反。

    数据不仅在执行核和存储器之间移动,还会在执行核之间传输。根据数据相关性,其中有两种读写模式会涉及到数据的移动:写后读和写后写 ,因为这两种模式会引发数据的竞争,表面上是并行执行,但实际只能串行执行,进而影响到性能。

    处理器交换的最小单元是 cache 行,或称 cache 块。在多核体系中,对于不共享 cache 的架构来说,两个独立的 cache 在需要读取同一 cache 行时,会共享该 cache 行,如果在其中一个 cache 中,该 cache 行被写入,而在另一个 cache 中该 cache 行被读取,那么即使读写的地址不相交,也需要在这两个 cache 之间移动数据,这就被称为 cache 伪共享,导致执行核必须在存储总线上来回传递这个 cache 行,这种现象被称为“乒乓效应”。

    同样地,当两个线程写入同一个 cache 的不同部分时,也会互相竞争该 cache 行,也就是写后写的问题。上文曾提到,不加锁的方案反而比加锁的方案更慢,就是互相竞争 cache 的原因。

    在 X86 机器上,某些处理器的一个 cache 行是64字节,具体可以参看 Intel 的参考手册。

    既然不加锁三线程方案的瓶颈在于 cache,那么让 apple 的两个成员 a 和 b 位于不同的 cache 行中,效率会有所提高吗?

    修改后的代码片断如下:

    清单 4. 针对Cache的优化
    struct apple
    {
    	unsigned long long a;
    	char c[128];  /*32,64,128*/
    	unsigned long long b;
    };

    测量结果如下图所示:

    图 4. 增加 Cache 时间耗时对比图
    增加 Cache 时间耗时对比图

    小小的一行代码,尽然带来了如此高的收益,不难看出,我们是用空间来换时间。当然读者也可以采用更简便的方法: __attribute__((__aligned__(L1_CACHE_BYTES))) 来确定 cache 的大小。

    如果对加锁三线程方案中的 apple 数据结构也增加一行类似功能的代码,效率也是否会提升呢?性能不会有所提升,其原因是加锁的三线程方案效率低下的原因不是 Cache 失效造成的,而是那把锁。

    在多核和多线程程序设计过程中,要全盘考虑多个线程的访存需求,不要单独考虑一个线程的需求。在选择并行任务分解方法时,要综合考虑访存带宽和竞争问题,将不同处理器和不同线程使用的数据放在不同的 Cache 行中,将只读数据和可写数据分离开。

    CPU 亲和力可分为两大类:软亲和力和硬亲和力。

    Linux 内核进程调度器天生就具有被称为 CPU 软亲和力(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。但不代表不会进行小范围的迁移。

    CPU 硬亲和力是指进程固定在某个处理器上运行,而不是在不同的处理器之间进行频繁的迁移。这样不仅改善了程序的性能,还提高了程序的可靠性。

    从以上不难看出,在某种程度上硬亲和力比软亲和力具有一定的优势。但在内核开发者不断的努力下,2.6内核软亲和力的缺陷已经比2.4的内核有了很大的改善。

    在双核机器上,针对两线程的方案,如果将计算 apple 的线程绑定到一个 CPU 上,将计算 orange 的线程绑定到另外一个 CPU 上,效率是否会有所提高呢?

    程序如下:

    清单 5. CPU 亲和力
    struct apple
    {
    	unsigned long long a;
    	unsigned long long b;
    };
    	
    struct orange
    {
    	int a[ORANGE_MAX_VALUE];
    	int b[ORANGE_MAX_VALUE];		
    };
    		
    inline int set_cpu(int i)
    {
    	CPU_ZERO(&mask);
    	
    	if(2 <= cpu_nums)
    	{
    		CPU_SET(i,&mask);
    		
    		if(-1 == sched_setaffinity(gettid(),sizeof(&mask),&mask))
    		{
    			return -1;
    		}
    	}
    	return 0;
    }
    
    	
    void* add(void* x)
    {
    	if(-1 == set_cpu(1))
    	{
    		return NULL;
    	} 
    		
    	for(sum=0;sum<APPLE_MAX_VALUE;sum++)
    	{
    		((struct apple *)x)->a += sum;
    		((struct apple *)x)->b += sum;
    	}	
    	
    	return NULL;
    }
    	
    int main (int argc, const char * argv[]) {
    		// insert code here...
    	struct apple test;
    	struct orange test1;
    	
    	cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
    	
    	if(-1 == set_cpu(0))
    	{
    		return -1;
    	} 
    		
    	pthread_create(&ThreadA,NULL,add,&test);
    				
    	for(index=0;index<ORANGE_MAX_VALUE;index++)
    	{
    		sum+=test1.a[index]+test1.b[index];
    	}		
    		
    	pthread_join(ThreadA,NULL);
    		
    	return 0;
    }

    测量结果为:

    图 5. 采用硬亲和力时间对比图(两线程)
    采用硬亲和力时间对比图(两线程)

    其测量结果正是我们所希望的,但花费的时间还是比单线程的多,其原因与上面分析的类似。

    进一步分析不难发现,样例程序大部分时间都消耗在计算 apple 上,如果将计算 a 和 b 的值,分布到不同的 CPU 上进行计算,同时考虑 Cache 的影响,效率是否也会有所提升呢?

    图 6. 采用硬亲和力时间对比图(三线程)
    采用硬亲和力时间对比图(三线程)

    从时间上观察,设置亲和力的程序所花费的时间略高于采用 Cache 的三线程方案。由于考虑了 Cache 的影响,排除了一级缓存造成的瓶颈,多出的时间主要消耗在系统调用及内核上,可以通过 time 命令来验证:

    #time ./unlockcachemultiprocess
        real   0m0.834s      user  0m1.644s       sys    0m0.004s
    #time ./affinityunlockcacheprocess
        real   0m0.875s      user  0m1.716s       sys    0m0.008s

    通过设置 CPU 亲和力来利用多核特性,为提高应用程序性能提供了捷径。同时也是一把双刃剑,如果忽略负载均衡、数据竞争等因素,效率将大打折扣,甚至带来事倍功半的结果。

    在进行具体的设计过程中,需要设计良好的数据结构和算法,使其适合于应用的数据移动和处理器的性能特性。


    总结

    根据以上分析及实验,对所有改进方案的测试时间做一个综合对比,如下图所示:

    图 7. 各方案时间对比图
    各方案时间对比图

    单线程原始程序平均耗时:1.049046s,最慢的不加锁三线程方案平均耗时:2.217413s,最快的三线程( Cache 为128)平均耗时:0.826674s,效率提升约26%。当然,还可以进一步优化,让效率得到更高的提升。

    从上图不难得出结论:采用多核多线程并行设计方案,能有效提高性能,但如果考虑不全面,如忽略带宽、数据竞争及数据同步不当等因素,效率反而降低,程序执行越来越慢。

    如果抛开本文开篇时的限制,采用上文曾提到的另外一种数据分解模型,同时结合硬亲和力对样例程序进行优化,测试时间为0.54s,效率提升了92%。

    软件优化是一个贯穿整个软件开发周期,从开始设计到最终完成一直进行的连续过程。在优化前,需要找出瓶颈和热点所在。正如最伟大的 C 语言大师 Rob Pike 所说:

    如果你无法断定程序会在什么地方耗费运行时间,瓶颈经常出现在意想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在。

    将这句话送给所有的优化人员,和大家共勉。


    参考资料

    • 请参考书籍《多核程序设计技术》,了解更多关于多线程设计的理念
    • 请参考书籍《软件优化技术》,了解更多关于软件优化的技术
    • 请参考书籍《UNIX编程艺术》, 了解更多关于软件架构方面的知识
    • 参考文章《CPU Affinity》,了解更多关于CPU亲和力的信息
    • 参考文章《管理处理器的亲和性(affinity)》,了解更多关于CPU亲和力的信息


    以下的两篇博文我感觉也不错, 与大家分享一下:


    展开全文
  • 存的多线程编程技术,由一些具有国际影响力的大规模软件和硬件厂商共同定义标准。它是 一种编译指导语句指导多线程、共享内存并行的应用程序编程接口(API)。本章介绍OpenMP 编程的概况、编写OpenMP 程序所需要的...
  • 快速掌握多线程编程优化技术

    千次阅读 2006-12-30 11:04:00
    随着英特尔多核平台编程优化大赛的进行, 我们收到了不少网友的优化报告, 其中许多网友取得非常优化成绩. 但是也许多网友通过各种渠道给我们反馈, 由于平时没有接触太大优化相关...多线程设计由于多核技术的发展,多

    随着英特尔多核平台编程优化大赛的进行, 我们收到了不少网友的优化报告, 其中许多网友取得非常好的优化成绩. 但是也许多网友通过各种渠道给我们反馈, 由于平时没有接触太大优化相关的工作,一时间对于提供的代码难以入手,希望我们针对大赛,提供一些学习的资源,掌握优化方面内容参加比赛. 所有我们提供了下面一些链接, 希望对一些没有没有太多优化经验的网友提供一些帮助.

    1.多线程设计
    由于多核技术的发展,多线程程序的调优会越来越为重要. 所有我们要求参赛网友对原有程序进行多线程设计. 程序多线程化是通过我们测试的一个条件.一般来说, 多线程设计主要有两种方法: 使用本地线程(Native Threading), OpenMP Threading.在大赛中,这两种方法都可以使用.

    关于Windows本地的线程,大家可以去MSDN* 网站去学习.下面一个链接,提供提供一个简单的介绍,:
    http://www.codeproject.com/threads/sync.asp

    OpenMP threading 一些详细内容可以参考OpenMP的网站(www.openmp.org).下面一篇开始学习的文章:
    Getting Started with OpenMP*:  http://www3.intel.com/cd/ids/developer/asmo-na/eng/20365.htm

    如果是使用OpenMP threading, 需要用Intel C++ Compiler 或 Microsoft .Net 2005* 编译器. 

    对程序的多线程化,需要要选择关键的代码(费时间的代码)进行多线程设计. 如果大家不能确定程序主要的计算部分, Intel® VTune™ Analyzers 可以帮助分析程序. 

    多线程化后, 下一步主要是对多线程的程序进一步优化. 

    2.优化工具
    学习英特尔的相关工具使用方法也是我们大赛的一个主要目的. 在大赛的参赛的主页上,大家可以下载到相关的工具. 这里我们简单对每个工具进行说明:

    Intel® C++ Compiler: 我们建议使用Intel C++ 编译器进行编译程序, 安装完Intel C++ 编译器后, 大家可以在命令行下, 或在Microsoft .Net 2005* 编译环境中选择Intel C++ 编译器. Intel C++ 编译器提供不同的编译开关. 如果大家需要进一步学习这方面的内容可以去 Intel 软件学院在线学习网站:
    Enhancing Performance with the Intel® Compiler: http://or1cedar.cps.intel.com/softwarecollege/CourseDetails.asp?courseID=105

    Intel® Math Kernel Library (Intel MKL).  Intel MKL 函数库中提供了VML 函数, 这些函数可以对超越函数(sin, cos, exp, log等)进行优化, 有关这方面的讨论,大家可以参见以前的一些帖子, 不少网友讨论VML函数的使用.

    Intel Threading Checker & Intel Threading Profiler: Thread Checker 用于查找线程错误, 能够检测资源竞争、线程死锁等问题. 大家程序在多线程后,可以用Threading Checker 检测一下有没有多线程相关的错误. Thread Profiler是线程性能检测工具,多线程化有, 可能会有负载比平衡, 同步开销过大等等线程相关的性能问题。Thread Profiler 能够帮助定位这些问题的原因.
    一些可以学习网站:
    Getting Started with the Intel® Thread Checker: http://or1cedar.cps.intel.com/softwarecollege/CourseDetails.asp?courseID=178
    Getting Started with the Thread Profiler: http://or1cedar.cps.intel.com/softwarecollege/CourseDetails.asp?courseID=179

    这些只是是一个初步的介绍,大家遇到什么问题, 欢迎一起来讨论. 一些网友在优化方面也非常有经验,可以一起来交流学习.

    关于本次大赛:

    由英特尔软件网络部主办,CSDN协办的“英特尔多核平台编程优化大赛”于 2006-12-6 日拉开帷幕,得到了广大网友的关注和支持。本次比赛要求参赛者使用大赛提供的样品代码,利用推荐的一个或多个英特尔® 软件开发工具对代码进行调优。参赛者可以在大赛网站上提交作品。参赛的作品将在大赛推荐的硬件平台上进行统一的评测,并在大赛网站上公布最终结果。所有通过测试的作品,选手都可以获得由英特尔提供的技术工具书一本和U盘一个。

    更多大赛相关信息,请前往大赛官方网站

    更多精彩话题,请前往CSDN技术社区-英特尔多核计算技术讨论区

    展开全文
  • android 多线程数据库读写分析与优化

    千次阅读 2015-03-05 09:23:35
    时间 2013-08-04 10:43:21 CSDN博客 ...最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SD
     
    

    最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SDK。

    android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到数据库级了,不能跟别的数据库有表锁,行锁。

    所以对写实在有要求的,可以使用多个数据库文件。

    哎,这数据库在多线程并发读写方面本身就挺操蛋的。

    下面分析一下不同情况下,在同一个数据库文件上操作,sqlite的表现。

    测试程序在2.2虚拟手机,4.2.1虚拟手机,4.2.1真手机上跑。

    1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。

    先看看相关的源码

    //SQLiteDatabase.java 
    
    public long insertWithOnConflict(String table, String nullColumnHack,
                ContentValues initialValues, int conflictAlgorithm) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
    
            .... 省略
    
            lock();
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql.toString());
    
                // Bind the values
                if (entrySet != null) {
                    int size = entrySet.size();
                    Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
                    for (int i = 0; i < size; i++) {
                        Map.Entry<String, Object> entry = entriesIter.next();
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
                    }
                }
    
                // Run the program and then cleanup
                statement.execute();
    
                long insertedRowId = lastInsertRow();
                if (insertedRowId == -1) {
                    Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
                } else {
                    if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "Inserting row " + insertedRowId + " from "
                                + initialValues + " using " + sql);
                    }
                }
                return insertedRowId;
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }

    //SQLiteDatabase.java 
    
    
     private final ReentrantLock mLock = new ReentrantLock(true);
    
    /* package */ void lock() {
    
           if (!mLockingEnabled) return; 
    
                 mLock.lock(); 
    
                 if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { 
    
                     if (mLock.getHoldCount() == 1) { 
    
                           // Use elapsed real-time since the CPU may sleep when waiting for IO
    
                           mLockAcquiredWallTime = SystemClock.elapsedRealtime(); 
    
                           mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); 
    
                     } 
    
          } 
    
    }

    通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。

    经测试不会引发异常。

    但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。

    2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。

    E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

     E/Database(1471):     at android.database.sqlite.SQLiteStatement.native_execute(Native Method)

    E/Database(1471):     at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)

    E/Database(1471):     at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)

    多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法  SQLiteStatement.native_execute

    抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。

    所以,多线程写必须使用同一个SQLiteOpenHelper对象。

    3,多线程读

    看SQLiteDatabase的源码可以知道,insert  , update ,  execSQL   都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。

    仔细看,发现


    最后,查询结果是一个SQLiteCursor对象。

    SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。

    在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()

    /**
         * Reads rows into a buffer. This method acquires the database lock.
         *
         * @param window The window to fill into
         * @return number of total rows in the query
         */
        /* package */ int fillWindow(CursorWindow window,
                int maxRead, int lastPos) {
            long timeStart = SystemClock.uptimeMillis();
            mDatabase.lock();
            mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
            try {
                acquireReference();
                try {
                    window.acquireReference();
                    // if the start pos is not equal to 0, then most likely window is
                    // too small for the data set, loading by another thread
                    // is not safe in this situation. the native code will ignore maxRead
                    int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
                            maxRead, lastPos);
    
                    // Logging
                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                        Log.d(TAG, "fillWindow(): " + mSql);
                    }
                    mDatabase.logTimeStat(mSql, timeStart);
                    return numRows;
                } catch (IllegalStateException e){
                    // simply ignore it
                    return 0;
                } catch (SQLiteDatabaseCorruptException e) {
                    mDatabase.onCorruption();
                    throw e;
                } finally {
                    window.releaseReference();
                }
            } finally {
                releaseReference();
                mDatabase.unlock();
            }
        }

    所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。

    4,多线程读写

    我们最终想要达到的目的,是多线程并发读写

    多线程写之前已经知道结果了,同一时间只能有一个写。

    多线程读可以并发

    所以,使用下面的策略:

    一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。

    这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。

    发现有插入异常。

    E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407
    E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
    E/SQLiteDatabase(18263):     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)

    插入异常,说明在有线程读的时候写数据库,会抛出异常。

    分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。

    //  SQLiteOpenHelper.java
    
      public synchronized SQLiteDatabase getReadableDatabase() {
            if (mDatabase != null && mDatabase.isOpen()) {
                return mDatabase;  // The database is already open for business
            }
    
            if (mIsInitializing) {
                throw new IllegalStateException("getReadableDatabase called recursively");
            }
    
            try {
                return getWritableDatabase();
            } catch (SQLiteException e) {
                if (mName == null) throw e;  // Can't open a temp database read-only!
                Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
            }
    
            SQLiteDatabase db = null;
            try {
                mIsInitializing = true;
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                Log.w(TAG, "Opened " + mName + " in read-only mode");
                mDatabase = db;
                return mDatabase;
            } finally {
                mIsInitializing = false;
                if (db != null && db != mDatabase) db.close();
            }
        }
    因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。

    所以写了个新方法,来获得只读SQLiteDatabase

    //DbHelper.java 
    //DbHelper extends SQLiteOpenHelper
    public SQLiteDatabase getOnlyReadDatabase() {
        	try{
        		getWritableDatabase(); //保证数据库版本最新
        	}catch(SQLiteException e){
        		Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):",e);
        	}
        	
            SQLiteDatabase db = null;
            try {
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                readOnlyDbs.add(db);
                return db;
            } finally {
            }
    }

    使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。
    但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。

    4.1.2上测试,读异常。
     E/SQLiteLog(18263): (5) database is locked
    W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)
     E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8
    E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t

    看来此路不同啊。

    其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING

    可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。

    这个属性是什么意思呢?

    参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。

    当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。

    在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。
    有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。

    所以只能判断sdk版本,如果3.0以上,就打开这个属性

    public DbHelper(Context context , boolean enableWAL) {
        this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);
        if( enableWAL && Build.VERSION.SDK_INT >= 11){
          getWritableDatabase().enableWriteAheadLogging();
        }
    }

    关于SQLiteDatabase的这个属性,参考api文档,也可以看看SQLiteSession.java里对多线程数据库读写的描述。

    SQLiteSession.java

    结论

    想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。

    如果还是达不到要求,就使用多个db文件吧。

    另:

    单位有一个三星 note2手机,上面所有的例子跑起来都啥问题也没有。。。。很好很强大。

    最后,附上我的测试程序。

    https://github.com/zebulon988/SqliteTest.git

    独家之言,如有问题请回复我,谢谢!


    SQLite数据库锁问题

    分类: SQLite 177人阅读 评论(0) 收藏 举报

    记得以前设计评审时,想用SQLite数据库实现某个功能,被教导说应该用Postgresql数据库,因为Postgresql数据库是行锁,而SQLite的锁粒度太粗了。当时还没有什么感觉。

    后来在另一个产品的群里面,经常看到其中的开发和测试说SQLite数据库死锁了。这才留了一下心。

    最近又要使用它,于是拜读了下《SQLite权威指南》,里面赫赫一句话:SQLite处理并发读没有什么问题,但是如果你的应用需要并发写的话,那么SQLite就不适合你了。

    看了一下SQLite数据库的锁机制:

    它包括四种锁,共享锁(Shared lock)、预留锁(Reserved lock)和未决锁(Pending lock)、排他锁(Exclusive lock)

    其中读操作,用的是Shared lock,所以并发的多个读数据库。如果有一个读操作存在,那么都不会允许写。

    而写就比较麻烦,

    1.它首先会申请一个预留锁(Reserved lock),在启用Reserved lock之后,已存在的读可以继续读,也可以有新的读请求。

    2.然后,它会把需要更新的数据写到缓冲区中。

    3.需要写到缓冲区的更新写完以后,就需要将更新刷到硬盘db了。这时,它会申请Pending lock,就不能再有新的Shared lock申请了,也就是阻止了新的读操作。但是已经存在的读操作还是可以继续读的。然后它就等待,直到没有读操作存在(即所有的读都已经结束)这个时候,它就会申请排他锁,此时不允许有其他锁的存在,然后进行commit,将缓冲区的数据写入db中。

    书上给举了个例子:

    B进行写操作,申请了预留锁;然后A进行读操作,申请了共享锁(有预留锁时,是允许读操作申请的);然后A又同时想进行写操作(未释放共享锁的情况),此时申请预留锁(因为已经有预留锁存在了)失败;B写完缓存,想commit时,申请了未决锁,但是无法从未决锁提升到排他锁(因为有共享锁存在)。此时,发生死锁,A和B都想等待对方释放锁。


    对应一下自己的场景:

    1.页面有多个读

    2.后台会定时写

    按照书上说的,写锁的时长大概是几毫秒。我写程序也尽量注意了。也许在极端情况下,在写时,恰好有读锁未释放,不过几毫秒内,概率不算很大。

    另外,就算是这种极端情况未写成功,在下一个5分钟写时,也会把上一个5分钟未commit的给补救上去。从前台看来,就是数据会有一定时延。


    另外一个隐含的需求:页面可能也需要进行更新数据的操作,这个写是有用户的某个动作触发的,那么在多用户的情况下,读写同时、写写同时的概率就会很大。对此,希望是采取规避的方式,在后台提供与此更新操作的脚本,而非在前台页面提供。


    由此可见,SQLite作为一个嵌入式数据库,不太适合用在高并发的场景下;另外,以上都是理论,希望有时间阅读源码,能够彻底弄清楚一切。

    讨论了下,为了规避风险,还是不用SQLite了。什么时候,SQLite才能把数据库级别的锁改为行锁?不过如果真的SQLite支持行锁,那么就违背它轻量、简单的初衷了。所以,这个终究是一个梦。
    展开全文
  • 多线程开发指南

    2015-08-28 12:01:50
    讲述多线程在iOS中的应用及注意点。更优化软件
  • android sqlite多线程读写分析与优化

    千次阅读 2015-03-10 22:31:09
    android 多线程数据库读写分析与优化 时间 2013-08-04 10:43:21 CSDN博客 原文  http://blog.csdn.net/lize1988/article/details/9700723 最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个...

    android 多线程数据库读写分析与优化

    最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的SDK。

    android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到数据库级了,不能跟别的数据库有表锁,行锁。

    所以对写实在有要求的,可以使用多个数据库文件。

    哎,这数据库在多线程并发读写方面本身就挺操蛋的。

    下面分析一下不同情况下,在同一个数据库文件上操作,sqlite的表现。

    测试程序在2.2虚拟手机,4.2.1虚拟手机,4.2.1真手机上跑。

    1,多线程写,使用一个SQLiteOpenHelper。也就保证了多线程使用一个SQLiteDatabase。

    先看看相关的源码

    //SQLiteDatabase.java 
    
    public long insertWithOnConflict(String table, String nullColumnHack,
                ContentValues initialValues, int conflictAlgorithm) {
            if (!isOpen()) {
                throw new IllegalStateException("database not open");
            }
    
            .... 省略
    
            lock();
            SQLiteStatement statement = null;
            try {
                statement = compileStatement(sql.toString());
    
                // Bind the values
                if (entrySet != null) {
                    int size = entrySet.size();
                    Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();
                    for (int i = 0; i < size; i++) {
                        Map.Entry<String, Object> entry = entriesIter.next();
                        DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
                    }
                }
    
                // Run the program and then cleanup
                statement.execute();
    
                long insertedRowId = lastInsertRow();
                if (insertedRowId == -1) {
                    Log.e(TAG, "Error inserting " + initialValues + " using " + sql);
                } else {
                    if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
                        Log.v(TAG, "Inserting row " + insertedRowId + " from "
                                + initialValues + " using " + sql);
                    }
                }
                return insertedRowId;
            } catch (SQLiteDatabaseCorruptException e) {
                onCorruption();
                throw e;
            } finally {
                if (statement != null) {
                    statement.close();
                }
                unlock();
            }
        }

    //SQLiteDatabase.java 
    
    
     private final ReentrantLock mLock = new ReentrantLock(true);
    
    /* package */ void lock() {
    
           if (!mLockingEnabled) return; 
    
                 mLock.lock(); 
    
                 if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) { 
    
                     if (mLock.getHoldCount() == 1) { 
    
                           // Use elapsed real-time since the CPU may sleep when waiting for IO
    
                           mLockAcquiredWallTime = SystemClock.elapsedRealtime(); 
    
                           mLockAcquiredThreadTime = Debug.threadCpuTimeNanos(); 
    
                     } 
    
          } 
    
    }

    通过源码可以知道,在执行插入时,会请求SQLiteDatabase对象的成员对象 mlock 的锁,来保证插入不会并发执行。

    经测试不会引发异常。

    但是我们可以通过使用多个SQLiteDatabase对象同时插入,来绕过这个锁。

    2,多线程写,使用多个SQLiteOpenHelper,插入时可能引发异常,导致插入错误。

    E/Database(1471): android.database.sqlite.SQLiteException: error code 5: database is locked08-01

     E/Database(1471):     at android.database.sqlite.SQLiteStatement.native_execute(Native Method)

    E/Database(1471):     at android.database.sqlite.SQLiteStatement.execute(SQLiteStatement.java:55)

    E/Database(1471):     at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1549)

    多线程写,每个线程使用一个SQLiteOpenHelper,也就使得每个线程使用一个SQLiteDatabase对象。多个线程同时执行insert, 最后调用到本地方法  SQLiteStatement.native_execute

    抛出异常,可见android 框架,多线程写数据库的本地方法里没有同步锁保护,并发写会抛出异常。

    所以,多线程写必须使用同一个SQLiteOpenHelper对象。

    3,多线程读

    看SQLiteDatabase的源码可以知道,insert  , update ,  execSQL   都会 调用lock(), 乍一看唯有query 没有调用lock()。可是。。。

    仔细看,发现


    最后,查询结果是一个SQLiteCursor对象。

    SQLiteCursor保存了查询条件,但是并没有立即执行查询,而是使用了lazy的策略,在需要时加载部分数据。

    在加载数据时,调用了SQLiteQuery的fillWindow方法,而该方法依然会调用SQLiteDatabase.lock()


    /**
         * Reads rows into a buffer. This method acquires the database lock.
         *
         * @param window The window to fill into
         * @return number of total rows in the query
         */
        /* package */ int fillWindow(CursorWindow window,
                int maxRead, int lastPos) {
            long timeStart = SystemClock.uptimeMillis();
            mDatabase.lock();
            mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
            try {
                acquireReference();
                try {
                    window.acquireReference();
                    // if the start pos is not equal to 0, then most likely window is
                    // too small for the data set, loading by another thread
                    // is not safe in this situation. the native code will ignore maxRead
                    int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
                            maxRead, lastPos);
    
                    // Logging
                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                        Log.d(TAG, "fillWindow(): " + mSql);
                    }
                    mDatabase.logTimeStat(mSql, timeStart);
                    return numRows;
                } catch (IllegalStateException e){
                    // simply ignore it
                    return 0;
                } catch (SQLiteDatabaseCorruptException e) {
                    mDatabase.onCorruption();
                    throw e;
                } finally {
                    window.releaseReference();
                }
            } finally {
                releaseReference();
                mDatabase.unlock();
            }
        }

    所以想要多线程读,读之间没有同步锁,也得每个线程使用各自的SQLiteOpenHelper对象,经测试,没有问题。

    4,多线程读写

    我们最终想要达到的目的,是多线程并发读写

    多线程写之前已经知道结果了,同一时间只能有一个写。

    多线程读可以并发

    所以,使用下面的策略:

    一个线程写,多个线程同时读,每个线程都用各自SQLiteOpenHelper。

    这样,在java层,所有线程之间都不会锁住,也就是说,写与读之间不会锁,读与读之间也不会锁。

    发现有插入异常。

    E/SQLiteDatabase(18263): Error inserting descreption=InsertThread#01375493606407
    E/SQLiteDatabase(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
    E/SQLiteDatabase(18263):     at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)

    插入异常,说明在有线程读的时候写数据库,会抛出异常。

    分析源码可以知道, SQLiteOpenHelper.getReadableDatabase() 不见得获得的就是只读SQLiteDatabase 。

    //  SQLiteOpenHelper.java
    
      public synchronized SQLiteDatabase getReadableDatabase() {
            if (mDatabase != null && mDatabase.isOpen()) {
                return mDatabase;  // The database is already open for business
            }
    
            if (mIsInitializing) {
                throw new IllegalStateException("getReadableDatabase called recursively");
            }
    
            try {
                return getWritableDatabase();
            } catch (SQLiteException e) {
                if (mName == null) throw e;  // Can't open a temp database read-only!
                Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
            }
    
            SQLiteDatabase db = null;
            try {
                mIsInitializing = true;
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                Log.w(TAG, "Opened " + mName + " in read-only mode");
                mDatabase = db;
                return mDatabase;
            } finally {
                mIsInitializing = false;
                if (db != null && db != mDatabase) db.close();
            }
        }
    因为它先看有没有已经创建的SQLiteDatabase,没有的话先尝试创建读写 SQLiteDatabase ,失败后才尝试创建只读SQLiteDatabase 。

    所以写了个新方法,来获得只读SQLiteDatabase

    //DbHelper.java 
    //DbHelper extends SQLiteOpenHelper
    public SQLiteDatabase getOnlyReadDatabase() {
        	try{
        		getWritableDatabase(); //保证数据库版本最新
        	}catch(SQLiteException e){
        		Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):",e);
        	}
        	
            SQLiteDatabase db = null;
            try {
                String path = mContext.getDatabasePath(mName).getPath();
                db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
                if (db.getVersion() != mNewVersion) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + path);
                }
    
                onOpen(db);
                readOnlyDbs.add(db);
                return db;
            } finally {
            }
    }

    使用策略:一个线程写,多个线程同时读,只用一个SQLiteOpenHelper,读线程使用自己写的getOnlyReadDatabase()方法获得只读。
    但是经过测试,还是会抛出异常,2.2上只有插入异常,4.1.2上甚至还有读异常。

    4.1.2上测试,读异常。
     E/SQLiteLog(18263): (5) database is locked
    W/dalvikvm(18263): threadid=21: thread exiting with uncaught exception (group=0x41e2c300)
     E/AndroidRuntime(18263): FATAL EXCEPTION: onlyReadThread#8
    E/AndroidRuntime(18263): android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: SELECT * FROM test_t

    看来此路不同啊。

    其实SQLiteDataBase 在API 11 多了一个 属性 ENABLE_WRITE_AHEAD_LOGGING

    可以打,enableWriteAheadLogging(),可以关闭disableWriteAheadLogging(),默认是关闭的。

    这个属性是什么意思呢?

    参考api文档,这个属性关闭时,不允许读,写同时进行,通过 锁 来保证。

    当打开时,它允许一个写线程与多个读线程同时在一个SQLiteDatabase上起作用。实现原理是写操作其实是在一个单独的文件,不是原数据库文件。所以写在执行时,不会影响读操作,读操作读的是原数据文件,是写操作开始之前的内容。

    在写操作执行成功后,会把修改合并会原数据库文件。此时读操作才能读到修改后的内容。但是这样将花费更多的内存。
    有了它,多线程读写问题就解决了,可惜只能在API 11 以上使用。

    所以只能判断sdk版本,如果3.0以上,就打开这个属性

    public DbHelper(Context context , boolean enableWAL) {
        this(context, DEFAULT_DB_NAME, null, DEFAULT_VERSION);
        if( enableWAL && Build.VERSION.SDK_INT >= 11){
          getWritableDatabase().enableWriteAheadLogging();
        }
    }

    关于SQLiteDatabase的这个属性,参考api文档,也可以看看SQLiteSession.java里对多线程数据库读写的描述。

    SQLiteSession.java

    结论

    想要多线程并发读写,3.0以下就不要想了,3.0以上,直接设置enableWriteAheadLogging()就ok。

    如果还是达不到要求,就使用多个db文件吧。

    另:

    单位有一个三星 note2手机,上面所有的例子跑起来都啥问题也没有。。。。很好很强大。

    最后,附上我的测试程序。

    https://github.com/zebulon988/SqliteTest.git

    独家之言,如有问题请回复我,谢谢!


    展开全文
  • 来操作数据库,现在需要多线程并发读写。Android 的数据库系统用的是sqlite ,sqlite的每一个数据库其实都是一个.db文件,它的同步锁也就精确到文件级别了。下面分析一下不同情况下,在同一个数据库文件上操作...
  • 多线程的那点儿事(之多线程数据结构)

    万次阅读 多人点赞 2011-12-09 21:05:35
     要想编写多线程,那就要使用锁。而在软件编写中,数据结构是少不了的。所以,我们在编写多线程的时候,就需要考虑一下如何在数据结构中插入锁。当然,有些数据结构是没有锁的,所以自然这个锁并不一定是必须的。 ...
  • 多线程技术

    千次阅读 2016-11-07 15:29:41
    多任务、多线程和多处理这些术语经常被交替地使用,但是它们在本质上是不同的概念。多任务是指操作系统具有在任务间快速切换使得这些任务看起来是在同步执行的能力。在一个抢占式多任务系统中,应用程序可以随时被...
  • 多线程高并发编程】二 实现多线程的几种方式

    万次阅读 多人点赞 2020-02-17 23:32:54
    本文我们来看看多线程的应用场景,为什么要用多线程,以及实现一个多线程有几种方式。
  • C# 多线程

    万次阅读 多人点赞 2019-05-29 17:56:35
    一、基本概念 1、进程 首先打开任务管理器,查看当前运行的进程: 从任务管理器里面可以看到当前所有正在运行的进程。...线程是操作系统分配处理器时间的基本单元,在进程中可以有线程同时执行代码。进...
  • 同步多线程

    千次阅读 2013-11-02 08:50:57
    同步多线程(SMT)是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术。本质上,同步多线程是一种将线程级并行处理(多CPU)转化为指令级并行处理(同一CPU)的方法。 同步多线程是单个物理...
  • 多线程多少算多?

    千次阅读 2016-10-03 00:56:41
    关于多线程的一点思考:真正干活的不是线程,而是CPU;线程越多,干活不一定越快
  • 一、同步问题 1.管程 ...Java内存模型:描述多线程的逻辑结构 JMM 线程工作内存(线程私有,不同线程间相互隔离) 所有变量的读写必须在工作内存中进行,使用的变量均是从主内存中拷贝的副本。 ...
  • 这个回答非常形象,系统思维的模式。------------------------------------------------------作者:...主要是任务可分解性和结果可预期性。数据类的工作是很容易做到这点的。游戏并不是这种类型。多核、多线程、多g...
  • 多线程学习笔记

    千次阅读 2009-07-18 00:02:00
    多线程学习笔记 多线程概述 进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,...
  • 多线程与多进程

    千次阅读 2019-02-27 10:26:49
    多线程 优劣 数据共享、同步 数据是分开的:共享复杂,需要用IPC;同步简单 多线程共享进程数据:共享简单;同步复杂 各有优势 内存、CPU 占用内存多,切换复杂,CPU利用率低 占用内存少,切换...
  • C#中的多线程与线程死锁

    千次阅读 2017-09-18 18:01:22
    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多...
  • 多线程和高并发介绍

    万次阅读 2021-06-06 05:00:46
    本文主要是针对多线程和高并发的概念做了简单的描述,介绍了什么是多线程,什么是高并发,并且对多线程和高并发的关系做了比较描述。 一、什么是多线程? 1.多线程介绍 什么是多线程,首先看下百度百科对多线程...
  • 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。 一般而言,多线程的应用,主要解决的是吞吐量(单线程,处理一个请求,多线程,处理多个请求。同一时间,可以处理多个用户的...
  • Java提供了多种多线程锁机制的实现方式,常见的有: synchronized ReentrantLock Semaphore AtomicInteger等 每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。...
  • python多线程编程

    千次阅读 2016-05-22 13:27:18
    1、多线程的发展背景 随着计算机的发展,无论是硬件还是软件都在快速的发展。 在最开始的时候,计算机都是只有一个cpu来进行指令控制和运算,程序执行的时候都是一个进程一个进程的运行,也就是顺序执行的方式,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 150,751
精华内容 60,300
关键字:

多线程优化比较好的软件