多线程处理(精选12篇)
多线程处理 篇1
当前我国大部分船舶设备单位嵌入式系统, 所包含的多线程调度基本上是借助软件实现的, 使用操作系统来对线程调度进行管理存在一定缺陷, 如系统进行时间片轮询切换过程中可能消耗大量时间, 导致处理器的实施用户不具备高效率, 另外, 大部分的嵌入式处理器在硬件上都没有设置完善的环境保护机制, 导致多线程程序在运行过程中很容易出现安全隐患, 或者是用户在对多线程编程进行开发的过程中, 需要按照系统函数和编程来对一些复杂问题进行解决, 操作有一定难度。因此, 对多线程调度进行适当的管理具有非常重要的意义。
1 嵌入式的硬件多线程处理器如何设计
当前芯片集成技术以及处理器的设计水平都在不断地发展和进步, 不同的逻辑处理器都能在单一的处理器上进行实现, 这也为硬件的多线程并发运行创造了良好的条件, 针对嵌入式的系统来说, 它的设计必须要充分估计到成本以及复杂程度两方面。在本文当中描述的同时多线程拥有单一的执行内核, 它将传统的处理核心作为基础, 拓展出一个借助硬件逻辑来完成控制的调度管理系统电路, 来完成在硬件层面上的多线程调度作用。
另外, 在处理器中线程调度单元中, 能够将外存中的指令代码按照一定的顺序放置于待执行的线程列当中, 与此同时按照时间片的顺序将其中的指令代码按照次序传送到执行单元当中进行执行。这种处理器的环境之下, 每一个线程都能够受到硬件的控制, 而且能够在自身的时间片当中使用处理器中的资源。在某个特别的时间段当中, 只能有一个单一的线程存在并对资源进行应用。这种形式的处理器具备比较好的处理器内核, 不但降低了设计的复杂性, 它所实现的处理范围比起同等级的处理器来说没有明显的增加, 由此我们可以认为, 借助同时多线程技术可以在最大程度上让操作系统软件不会过多参与, 降低成本, 并且完成单一处理器的芯片系统当中多线程的操作。
对多线程处理器中硬件进行调度控制主要是硬件多线程处理器方面的结构设计以及存储器的资源保护还有指令节拍状态的设计等。
2 如何实现嵌入式的硬件多线程具体执行机制
2.1 多线程机制
对于硬件结构来说, 同时多线程这种技术大部分情况下其处理器都只能存在于处理器的单一核心当中, 执行多线程主要依据的是单位时间片的顺序调度, 实现多种不同的线程方法, 在不同的单位时间片处理器会按照一定次序来完成指令、调整工作时间, 同时保护工作的环境等。
另外, 在处理器进行空间指令的执行流程当中, 一部分内部资源是没有必要一直进行使用的, 像在通用寄存器当中的资源一般只需要在处理器WB节拍以及EX节拍当中才会得到应用。由此我们可以认为, 必须要适当地安排操作的次序, 还要在硬件的结构当中设置一个缓冲寄存器, 在处理器运行当中顺利执行当前的线程, 对下一线程的工作空间进行适当的恢复与保护。
对各个线程运作的空间进行保护的系统主要是由寄存器构成的, 它可以通过在处理器的内部对同等资源进行反复的设定, 将分散资源在页内进行全面实现, 如果处理器需要切换线程就可以通过换页来完成切换。资源分页符需要借助处理器当中的选择器来形成线程号, 在具体的实现过程中怎样能够将操作的过程在最短时间内依次运作完成也是比较重要的问题。
目前在我国的船舶设备研究试验行业当中, 对工作环境进行保护的方式大多都是在多线程的运行环境当中寻求处理器对寄存器以及存储器的保护, 通常依靠这样的两种方法来完成, 其中之一是对页面进行适当管理的方法, 另一个是资源的迅速切换。
2.2 选择线程模块
通过同时多线程这种技术得以实现的线程处理器, 它的每一个硬件线程当中都存在着比较特殊的标志, 依照这些线程号就能够对处理器内部各个硬件的线程进行适当的区分, 在当前获取执行线程号可以借助对控制器进行查询, 获取其最高的三位数。
执行线程的时候, 选择器可以很好地控制线程的执行顺序, 它的主要工作原理如图1。
图1所示的选择器当中存在一个选择线程的阵列, 它输出端所输出的内容是在下一个时间单位片上需要进行运作的线程号;另外, 在控制线程的寄存器当中记录的内容是在当前的运作环境下被成功激活的线程, 借助该寄存器可以很好地提供出一个对处理器进行控制的线程接口。面对当前我国的船舶设备行业的不断发展和进步, 嵌入式的硬件多线程技术也逐渐得到了普及, 特别是在对水声信号进行分析的情况下, 但是需要注意的是, 在不同的环境下它也有不同的利用方式, 这还需要进行更加全面系统的分析。
3 结语
总的来说, 在本文当中进行详细探讨的内容主要是通过硬件的控制来进行多线程调度具体的方式, 在多线程的背景下, 硬件处理器能够拥有非常高效的执行力, 不过想要对其性能进行系统评价, 还需要将多种影响因素进行综合, 以此为依据来进行。
摘要:本文详细探讨了在同时利用多线程技术过程中, 所涉及的硬件多线程设计处理器, 同时借助处理器内的硬件机制对多线程调度进行适当的管理, 试图实现将硬件时间片轮询作为最基本的内容的多线程处理器调度机制, 提升处理器的实施用户所拥有的线程效率, 尽可能的简易化操作, 对多线程工作环境中的线程起到一定保护作用。
关键词:多线程处理器,嵌入式,设计,多线程
参考文献
[1]汪汝.对嵌入式硬件多线程处理器的研究[J].硅谷, 2014 (20) .
[2]张文波, 苑凌娇, 谭小波, 付立冬.嵌入式多线程处理器的执行控制设计方法的研究[J].沈阳理工大学学报, 2012 (04) .
多线程处理 篇2
本文主要论述IOS创建锁的方法(总结):
一、使用关键字
1)@synchronized(互斥锁)
优点:使用@synchronized关键字可以很方便地创建锁对象,而且不用显式的创建锁对象。
缺点:会隐式添加一个异常处理来保护代码,该异常处理会在异常抛出的时候自动释放互斥锁。而这种隐式的异常处理会带来系统的额外开销,为优化资源,你可以使用锁对象。
二、“Object-C”语言
1)NSLock(互斥锁)
2)NSRecursiveLock(递归锁)
条件锁,递归或循环方法时使用此方法实现锁,可避免死锁等问题。
3)NSConditionLock(条件锁)
使用此方法可以指定,只有满足条件的时候才可以解锁。
4)NSDistributedLock(分布式锁)
在IOS中不需要用到,也没有这个方法,因此本文不作介绍,这里写出来只是想让大家知道有这个锁存在。
如果想要学习NSDistributedLock的话,你可以创建MAC OS的项目自己演练,方法请自行Google,谢谢。
三、C语言
1)pthread_mutex_t(互斥锁)
2)GCD-信号量(“互斥锁”)
3)pthread_cond_t(条件锁)
线程安全 —— 锁
一、使用关键字:
1)@synchronized
// 实例类person
Person *person = [[Person alloc] init];
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(person) {
[person personA];
[NSThread sleepForTimeInterval:3]; // 线程休眠3秒
}
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(person) {
[person personB];
}
});
关键字@synchronized的使用,锁定的对象为锁的唯一标识,只有标识相同时,才满足互斥。如果线程B锁对象person改为self或其它标识,那么线程B将不会被阻塞。你是否看到@synchronized(self) ,也是对的。它可以锁任何对象,描述为@synchronized(anObj)。
二、Object-C语言
1)使用NSLock实现锁
// 实例类person
Person *person = [[Person alloc] init];
// 创建锁
NSLock *myLock = [[NSLock alloc] init];
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[myLock lock];
[person personA];
[NSThread sleepForTimeInterval:5];
[myLock unlock];
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[myLock lock];
[person personB];
[myLock unlock];
});
程序运行结果:线程B会等待线程A解锁后,才会去执行线程B。如果线程B把lock和unlock方法去掉之后,则线程B不会被阻塞,这个和synchronized的一样,需要使用同样的锁对象才会互斥。
NSLock类还提供tryLock方法,意思是尝试锁定,当锁定失败时,不会阻塞进程,而是会返回NO。你也可以使用lockBeforeDate:方法,意思是在指定时间之前尝试锁定,如果在指定时间前都不能锁定,也是会返回NO。
注意:锁定(lock)和解锁(unLock)必须配对使用
2)使用NSRecursiveLock类实现锁
// 实例类person
Person *person = [[Person alloc] init];
// 创建锁对象
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
// 创建递归方法
static void (^testCode)(int);
testCode = ^(int value) {
[theLock tryLock];
if (value > 0)
{
[person personA];
[NSThread sleepForTimeInterval:1];
testCode(value - 1);
}
[theLock unlock];
};
//线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
testCode(5);
});
//线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[theLock lock];
[person personB];
[theLock unlock];
});
如果我们把NSRecursiveLock类换成NSLock类,那么程序就会死锁。因为在此例子中,递归方法会造成锁被多次锁定(Lock),所以自己也被阻塞了。而使用NSRecursiveLock类,则可以避免这个问题。
3)使用NSConditionLock(条件锁)类实现锁:
使用此方法可以创建一个指定开锁的条件,只有满足条件,才能开锁。
// 实例类person
Person *person = [[Person alloc] init];
// 创建条件锁
NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lock];
[person personA];
[NSThread sleepForTimeInterval:5];
[conditionLock unlockWithCondition:10];
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lockWhenCondition:10];
[person personB];
[conditionLock unlock];
});
线程A使用的是lock方法,因此会直接进行锁定,并且指定了只有满足10的情况下,才能成功解锁,
unlockWithCondition:方法,创建条件锁,参数传入“整型”。lockWhenCondition:方法,则为解锁,也是传入一个“整型”的参数。
三、C语言
1)使用pthread_mutex_t实现锁
注意:必须在头文件导入:#import
// 实例类person
Person *person = [[Person alloc] init];
// 创建锁对象
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
[person personA];
[NSThread sleepForTimeInterval:5];
pthread_mutex_unlock(&mutex);
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
[person personB];
pthread_mutex_unlock(&mutex);
});
实现效果和上例的相一致
2)使用GCD实现“锁”(信号量)
GCD提供一种信号的机制,使用它我们可以创建“锁”(信号量和锁是有区别的,具体请自行百度)。
// 实例类person
Person *person = [[Person alloc] init];
// 创建并设置信量
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[person personA];
[NSThread sleepForTimeInterval:5];
dispatch_semaphore_signal(semaphore);
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[person personB];
dispatch_semaphore_signal(semaphore);
});
效果也是和上例介绍的相一致。
我在这里解释一下代码。dispatch_semaphore_wait方法是把信号量加1,dispatch_semaphore_signal是把信号量减1。
我们把信号量当作是一个计数器,当计数器是一个非负整数时,所有通过它的线程都应该把这个整数减1。如果计数器大于0,那么则允许访问,并把计数器减1。如果为0,则访问被禁止,所有通过它的线程都处于等待的状态。
3)使用POSIX(条件锁)创建锁
// 实例类person
Person *person = [[Person alloc] init];
// 创建互斥锁
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// 创建条件锁
__block pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
[person personA];
pthread_mutex_unlock(&mutex);
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
[person personB];
[NSThread sleepForTimeInterval:5];
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
});
效果:程序会首先调用线程B,在5秒后再调用线程A。因为在线程A中创建了等待条件锁,线程B有激活锁,只有当线程B执行完后会激活线程A。
pthread_cond_wait方法为等待条件锁。
pthread_cond_signal方法为激动一个相同条件的条件锁。
简单总结:
浅谈多线程安全 篇3
【关键词】多线程 编程 安全
一、什么是线程安全
当你的程序所在的进程之中有多个线程在同时运行,而这些线程可能会同时运行一段代码或者会同时访问一个对象,如果每次运行这段代码或对对象访问之后,所得到的结果和单线程情况下运行结果一样,而且其他的变量的值也和预期保持一致,我们就认为是线程安全的。也就是说,当多个线程同时运行同一段代码时不会造成资源的冲突,不会产生错误的结果就是线程安全的。如过有一段线程安全的代码,它在多个线程中使用时不需要做同步处理;而线程不安全的代码在多线程环境中使用必须要做同步处理,否则将会出现不可预期的后果。
二、线程不安全所造成的影响
在不进行数据保护操作时,当一段代码或者对象可能被多个线程访问,而且访问顺序不能确定,那么一旦这段代码或者对象处于有条件的线程安全,线程兼容,线程对立这一分类,就可能出现多个线程先后更改数据,从而造成得到的数据不一致,或者数据出现污染,更严重的时候会出现脏数据,甚至程序崩溃。线程不安全是可怕的,一方面它对于程序编写人员来说是一场灾难,因为线程不安全导致的错误会被程序复杂的逻辑掩盖,它所造成的错误与逻辑不严谨所造成的错误基本上相似,这就为除错造成了困难,很多时候需要重新审视整个代码,甚至很长时间无法发现问题所在。另一方面它对于程序的使用者造成的损失基本无法估计,在当下的数据时代,任何数据的不一致,数据的污染都会给一个公司和个人造成无法挽回的影响。
三、线程不安全的分类
线程的安全程度并没有一个统一的分类,在乔希·布洛赫所给出线程分类描述,线程安全性分为五类,分别是:不可变、线程安全、有条件线程安全、线程兼容和线程对立。当然,若在明确的明白下线程安全特性的情况下,无论是否使用这种分类系统都没有关系。乔希·布洛赫所提出的分类方法也有局限性,各个分类之间的界限并不是绝对的明朗,但是作为对线程安全的分类,确是简单明了的。下面就各个类别进行详细的说明:
1,不可变的
不可变的对象一定是线程安全的,因为其不可变的特性,它永远也不需要进行额外的同步操作。只要一个不可变的对象在构建时候是正确的,那么永远也不会看到它处于不一致的状态。再代码中,基本类型,基本数值类,字符常量都是不可变的。
2,线程安全的
所有线程安全的代码或者对象都具有上面所提到的线程安全的属性,也就是说,这段代码或对象在多线程环境下,被多个线程访问,不管这些线程的访问顺序是何种顺序,所有访问的线程都不需要进行额外的同步工作。这种线程安全的要求是严格的,满足这种要求的和满足不可变類型的所有代码或者对象都是绝对的线程安全。
3,有条件的线程安全
有条件的线程安全,指的是对于这段代码或者对象仅仅在一些操作访问顺序不同需要额外的同步操作。最显著的有条件的线程安全是在遍历一些返回迭代器对象的时候,如果在遍历这些对象的同时存在另一个或多个线程进行了添加或移除内部元素的操作,必然会出现迭代器的失效。为了保证在某一线程在执行遍历操作的时候该对象不会被其他线程访问,应通过相应的手段,阻止其他线程做出导致该对象的迭代器失效的操作。进行迭代的线程应该确保它是独占性的访问该对象,从而保证了遍历的完整性。
4,线程兼容的
线程兼容的代码或者对象不是线程安全的,但是可以通过正常使用同步而在多线程环境下安全的使用。
5,线程对立的
线程对立是指一段代码或者对象在运行中,一旦存在多进程对它进行操作,总是不能保证线程安全的,这种不能保证不管是否进行了同步的操作。线程对立是一种罕见且特殊的情况,例如当一段代码或对象中的静态数据被修改之后,被修改的数据可能会影响到其他代码或者其他对象的行为时,这种时候就会出现线程对立。
四、防范线程不安全影响的措施
1.对于可能会运行在多线程环境下的代码和对象的编写人员,应尽量的保证该代码在多线程环境每次运行结果和单线程情况下运行结果一致,而且其他的变量和值也能和预期结果保持一致。这种情况从根本上保证了线程安全。
2.对于可能会运行在多线程环境下的代码和对象的编写人员,在编写代码和对象的时候,应注意辨别是否能在多线程下正确运行,根据上文提出的线程安全的分类,对于有条件的线程安全的,线程兼容的,线程对立的应该给以相应的标记,或在文档之中详细的写出说明。保证该代码或者对象的潜在使用者能够通过查看注释或文档,清楚明白的知道该代码或对象在多线程环境下的运行状态。
3.对于某段代码或者某个对象的使用者,在使用这段代码或对象之前,应该清楚的知道该代码和对象是否是线程安全的,并且应该清楚的知道接下来使用这段代码或对象的运行环境是否为多线程环境,根据不同的情况决定是否需要进行线程间的同步,或者用互斥锁等方法使得线程能够有序的访问该代码或对象。
4.对于某段代码或者某个对象的使用者,如果确定为多线程环境,且确定接下来使用的代码或对象有可能会被多个线程访问,并且无法确定其是否是线程安全的时候,应尽量通过多种方式确定其线程安全与否,这些方式包括联系作者或者通过自己的测试,以及查看源代码。
5.对于软件设计者来说,当程序不能按照设计运行,应当在检测逻辑错误的同时,检测是否因为线程不安全而导致的错误是必要的。依次检查所使用别人代码或别人提供的对象是否存在线程安全问题是最为合适的。
6.对于已经发现因为线程安全问题导致错误的软件使用者,应及时停止使用该软件,及时联系软件厂商,要求修改。
参考文献:
1 Lubomir F.Bic,Alan C.Shaw.操作系统原理[M].北京:清华大学出版社,2005
2 安德鲁斯.多线程,并行与分布式程序设计基础(影印版)[M].北京:高等教育出版社,2002
多线程处理 篇4
1 Android系统组成
Android的系统架构和其他操作系统一样, 采用了分层的架构。从架构图看 (如图1) , android分为四个层, 从高层到低层分别是应用程序层 (Applications) 、应用程序框架层 (Application Framework) 、系统运行库层 (Libraries、Android Runtime) 和linux核心层 (Linux Kernel) 。
1) 应用程序层:Android应用程序层中, 所有的应用程序都是使用JAVA语言编写的。我们可以编写自己的核心应用程序。
2) 应用程序框架:在android中, 应用程序所使用的API, 开发者可以完全访问。android应用程序的架构设计使得所有开发组件可以非常方便的复用和修改。
3) 函数库层 (Libraries) :Android包含了一些C/C++库, Android系统中的不同的组件都可以使用这些类库。这些类库通过Android应用程序框架为开发者提供开发服务, 进行应用程序的开发。
4) Android运行时库层 (Android Runtime) :Android使用了自有的Android Runtime来执行。
5) linux核心层 (Linux Kernel) :Android系统用的是为2.6的Linux内核版本, 主要包含的功能有内存管理 (Memory Management) 机制、安全控制 (Security) 机制、进程管理 (Process Managemen) 机制、硬件驱动 (Driver Model) 机制、网络协议栈 (Network Stack) 机制等, 同时Linux内核也作为硬件和软件栈之间的抽象层 (HAL) 。
2 Android开发基本组件
1) 活动 (Activity) 是Android中最基本的应用程序组件, 在应用程序中, 通常一个活动就是一个单独的屏幕。每一个活动都是从活动基类中继承而来, Activity类将会显示由几个Views控件组成的用户接口, 并对用户事件做出响应。
2) 服务 (Service) 是一段长生命周期的, 没有用户界面的程序, 在后台一直运行。
3) 内容提供者 (Content Provider) :内容提供者是用来管理和共享应用数据类库, 它可以实现多个程序之间的数据共享。
4) Intent (意图) :Intent在Android起着一个中介的作用, 专门用于提供应用组件相互调用时的相关信息, 可以实现调用者与被调用者之间的解耦合。
5) 广播接收器 (Broadcast receiver) :可以使用它对外部事件进行过滤, 使得应用程序只处理感兴趣的外部事件。
3 Android中多线程技术的使用
在android应用中, UI线程 (主线程) 超过5秒没响应的话就会抛出无响应异常 (ANR) 。在通过网络获取下载的大资源文件时, 如果处理不当, 是很容易出现异常问题的。Android开发框架中提供两种方法来处理这种问题:
1) 首先, 启动一个新的线程来获取下载资源文件, 资源获取结束后通过Handler机制发送消息 (Message) , 并同时在UI线程中处理消息, 从而达到在异步线程中处理事件 (event) 的效果, 然后通过Handler Message方法来更新UI线程; (实现过程见图2)
2) 使用Android中提供的Async Task方式来完成异步操作。Async Task是使用java.util.concurrent框架来管理线程以及任务的执行的, concurrent框架是一个非常成熟, 高效的框架, 经过了严格的测试。这说明Async Task的设计很好的解决了匿名线程存在的问题。
4 多线程技术具体实现
4.1 使用Handler实现多线程
我们都知道, 在Android中主线程是线程不安全的, 也就是说, 更新UI界面只能在应用程序的主线程 (UI) 中进行更新, 在子线程中进行UI更新操作是十分危险的 (及容易造成线程崩溃) 。android为了解决这样的问题, 让我们就使用Handler机制来进行处理。由于Handler运行在主线程中 (UI线程中) , 它与子线程可以通过Message对象来传递数据进行通信, 这个时候, Handler就承担着接受子线程传过来的 (子线程用sed Message () 方法) 传递Message对象, (里面包含数据) , 然后, 把这些消息放入主线程队列中, 进而配合主线程进行UI的更新操作。
实例代码如下:
Handler可以说是线程和Activity交互的桥梁, 我们只要在run方法中发送Message, 而在Handler里, 通过不同的Message执行不同的任务。从而可以很好地降低异步线程和主线程之间的耦合度。
4.2 使用Async Task类实现多线程
为了要使用AsyncTask, 首先需要定义下面的几个方法
1) onPreExecute ()
该方法将在执行后台操作前被UI线程调用。我们可以在该方法中做一些事先准备工作, 当然, 这个方法也可以不用实现。
2) doInBackground (Params...)
在on Pre Execute方法执行后马上执行, 该方法运行在后台线程中。将负责执行那些很耗时的后台处理工作。该方法是抽象方法, 根据java语言的规则, 该方法必须实现。
3) on Progress Update (Progress...)
在publish Progress方法被调用后, UI线程将调用这个方法从而在界面上展示任务的进展情况,
4) on Post Execute (Result)
在do In Background执行完成后, on Post Execute方法将被UI线程回调, 后台的计算结果将通过该方法传递到UI线程中, 并在界面上进行显示。
5) on Cancelled ()
应用程序取消线程操作的时候调用。
实例代码如下: (用于更新进度条)
实现效果见图3。
Async Task是Android为开发者提供的用于处理一些比较耗时的后台任务的基类, 通过使用Async Task可以优化应用程序, 可以给用户带来良好用户体验的。
5 结束语
本文对Android系统的整体架构进行了简单的介绍, 对Android中的开发组件进行了大致的分析, 提出了使用多线程技术来避免应用出现ANR (应用程序无响应) 的方法, 通过Handler和Async Task机制把耗时较多的任务放在子线程中执行, 来保持UI主线程的流畅顺滑, 从而获得良好的用户体验。我们很难想象没有使用的多线程技术的应用程序, 多线程技术的使用对于提高应用程序性能和用户体验是十分重要的。具有一定的研究价值。
摘要:在Android程序的开发过程中, 程序运行的流畅性是十分重要的。如果主线程处理的事件耗时过长将会出现ANR (应用程序无响应) , 导致程序崩溃。这就有必要将耗时较多的事件交给后台线程处理, 从而来提升用户体验, 改善应用程序性能。该文就Android中多线程技术的使用进行讲解。
关键词:Android,多线程,Handler,AsyncTask
参考文献
[1]Dave MacLean.精通Android3[M].杨越, 译.北京:人民邮电出版社, 2011:255-260, 324-328.
[2]余志龙, 陈小凤.AndroidSDK开发范例大[M].北京:清华大学出版社, 2010.
Java线程及多线程技术及应用 篇5
生产者消费者问题也是一个典型的线程问题。我们举一个这方面的实例来说明:在一个果园里,有农夫和小孩,农夫会不停的采摘水果放入果园中心的一个水果筐直到水果筐满,而小孩会不停的从水果筐里拿水果来吃,直到水果拿完。分析这个模型我们可以看出:农夫可以看成是一个生产者的线程,小孩可以看成是一个消费者的线程,而大水果筐是共享资源。
2、用Java程序表述的代码示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 水果类*/
public class Fruit {
/*** 水果编号*/
private int id;
/*** 水果编号计数器*/
private static int number = 0;
/*** 水果品种 */
private String variety;
/*** 水果品种数组 */
private String[] varietys = 苹果,桃子,梨子,香蕉,西瓜,荔枝,葡萄.split(,);
public Fruit() {
super();
this.variety = varietys[new Random().nextInt(7)];
this.id = ++number;
}
}
水果筐应该设计成类似于栈的数据结构,其中包含一个数组来存放筐里的水果,而数组的下标就是水果筐的容量。设定一个索引index表示指向下一个将要放入水果的位置。类中的push方法模拟农夫向水果筐中放入水果,pop方法模拟小孩从水果筐中拿水果。这两个方法都要操作共享资源,所以push和pop方法都是同步互斥方法。
3、如何避免出现死锁
那同步的问题解决后是否会出现死锁呢?大家试想一下,如果生产的速度大于消费的速度就会导致功大于求,水果筐很容易就满了,然而生产者又一直抱着水果筐不放,没有机会给消费者使用,消费者不消费生产者就无法生产,所以就造成了死锁。
怎样解决呢?在两个同步互斥方法中用到了wait和notify方法,这两个方法是为了防止死锁的。
l wait是Object类的方法,它的作用是拥有互斥锁的线程放弃锁的使用权,进入wait池进行等待,那么互斥锁就有可能被其他线程获得以执行其他任务。
l notify也是Object类的方法,它的作用是从wait池中唤醒一条正在等待的线程进入就绪状态,被唤醒的这条线程就很可能重新获得cup和互斥锁来完成它的任务。
l notifyAll和Notify很相似,它是从wait池中唤醒所有正在等待的线程进入就绪状态。
需要注意的是以上三个方法都只能在synchronized方法中应用,否者会出现下面的异常信息:IllegalMonitorStateException:current thread not owner。
4、实现的代码示例
package com.px1987.j2se.thread.ProducerConsumer;
import java.text.DecimalFormat;
import java.util.Arrays;
/*** 水果框类,类似一个栈的模型 */
public class FruitBasket {
/*** 容量为10的水果数组,也就是说水果框最多能放下10个水果 */
private Fruit[] fruits = new Fruit[10];
/*** 下一个将要放入水果的位置*/
private int index = 0;
/*** 水果框中是否为空 @return true为空,false为不空 */
public boolean isEmpty() {
return index == 0 ? true : false;
}
/*** 水果框是否装满* @return true为满,false为未满*/
public boolean isFull() {
return index == fruits.length ? true : false;
}
/*** 进栈方法,模拟农夫把水果放入筐中,@param name 农夫的名字,@param fruit 水果对象 */
public synchronized void push(String name, Fruit fruit) {
//用while循环,不用if,避免IndexOutOfBoundsException异常的产生
while (isFull()) {
//如果水果筐满了,需要等待
try {
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
//将水果放入index指示的位置,index再上移一格
fruits[index++] = fruit;
System.out.println(name + 向水果框中放入编号为 + fruit.getId() + 的+
fruit.getVariety());
display();
this.notify(); //通知其他等待的农夫或孩子可以开始工作啦
}
/*** 出栈方法,模拟小孩从水果筐中取出水果,@param name 小孩的名字,@return 取出的水果*/
public synchronized Fruit pop(String name) {
//用while循环,不用if,避免IndexOutOfBoundsException异常的产生
while (isEmpty()) {
try { //如果水果筐空,需要等待
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
Fruit fruit = null;
fruit = fruits[--index]; //index下移一位,取出指示位置上的水果
System.out.println(name + 从水果框中拿出编号为 + fruit.getId() + 的+
fruit.getVariety());
display();
this.notify();
return fruit;
}
/*** 显示水果筐中水果存放情况*/
public void display() {
for (int i = 0; i < index; i++)
System.out.printf(%-10s, NO: +
new DecimalFormat(00).format(fruits[i].getId())
+ fruits[i].getVariety() + |);
for (int i = index; i < fruits.length; i++) {
System.out.printf(%-10s, 【 + (i + 1) + 】 |);
}
System.out.println();
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/** 果园里的农夫类,他是生产者,实现Runnable接口*/
public class Farmer implements Runnable {
/** 姓名*/
private String name;
/** 水果框*/
private FruitBasket fruitBasket;
/** 农夫会不停地重复这一系列动作:从水果树上采摘一个水果放入水果框中,然后随机的休息0-2秒*/
public void run() {
while (true) {
fruitBasket.push(name, new Fruit());
try {
Thread.sleep(new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Farmer(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 果园中的小孩类,他是消费者,实现Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/
public void run() {
while (true) {
fruitBasket.pop(name);
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Child(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
package com.px1987.j2se.thread.ProducerConsumer;
import java.util.Random;
/*** 果园中的小孩类,他是消费者,实现Runnable接口*/
public class Child implements Runnable {
/*** 姓名*/
private String name;
/*** 水果框*/
private FruitBasket fruitBasket;
/*** 小孩会不停地重复这一系列动作:从水果框中拿出水果吃,然后随机休息0-5秒钟*/
public void run() {
while (true) {
fruitBasket.pop(name);
try {
Thread.sleep(new Random().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Child(String name, FruitBasket fruitBasket) {
super();
this.name = name;
this.fruitBasket = fruitBasket;
}
}
测试时使用多个生产者线程和多个消费者线程
package com.px1987.j2se.thread.ProducerConsumer;
/** 果园类,测试*/
public class Orchard {
public static void main(String[] args) {
FruitBasket fruitBasket = new FruitBasket();
Thread farmerThread1 = new Thread(new Farmer(农夫1, fruitBasket));
Thread farmerThread2 = new Thread(new Farmer(农夫2, fruitBasket));
Thread farmerThread3 = new Thread(new Farmer(农夫3, fruitBasket));
Thread childThread1 = new Thread(new Child(小孩1, fruitBasket));
Thread childThread2 = new Thread(new Child(小孩2, fruitBasket));
Thread childThread3 = new Thread(new Child(小孩3, fruitBasket));
farmerThread1.start();
farmerThread2.start();
farmerThread3.start();
childThread1.start();
childThread2.start();
childThread3.start();
}
}
程序的运行结果如下:
农夫1 向水果框中放入编号为1的苹果
NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫3 向水果框中放入编号为2的荔枝
NO:01苹果 | NO:02荔枝 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩2 从水果框中拿出编号为2的荔枝
NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫2 向水果框中放入编号为3的香蕉
NO:01苹果 | NO:03香蕉 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩1 从水果框中拿出编号为3的香蕉
NO:01苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩3 从水果框中拿出编号为1的苹果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫2 向水果框中放入编号为4的苹果
NO:04苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩1 从水果框中拿出编号为4的苹果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫1 向水果框中放入编号为5的苹果
NO:05苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫3 向水果框中放入编号为6的西瓜
NO:05苹果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫2 向水果框中放入编号为7的苹果
NO:05苹果 | NO:06西瓜 | NO:07苹果 | 【4】 | 【5】 | 【6】 |
小孩3 从水果框中拿出编号为7的苹果
NO:05苹果 | NO:06西瓜 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩3 从水果框中拿出编号为6的西瓜
NO:05苹果 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
小孩2 从水果框中拿出编号为5的苹果
【1】 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫2 向水果框中放入编号为8的桃子
NO:08桃子 | 【2】 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫1 向水果框中放入编号为9的荔枝
NO:08桃子 | NO:09荔枝 | 【3】 | 【4】 | 【5】 | 【6】 |
农夫3 向水果框中放入编号为10的香蕉
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | 【4】 | 【5】 | 【6】 |
农夫1 向水果框中放入编号为11的桃子
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | 【5】 | 【6】 |
农夫1 向水果框中放入编号为12的荔枝
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 |
农夫3 向水果框中放入编号为13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | NO:13西瓜 |
小孩1 从水果框中拿出编号为13的西瓜
NO:08桃子 | NO:09荔枝 | NO:10香蕉 | NO:11桃子 | NO:12荔枝 | 【6】 |
农夫2 向水果框中放入编号为14的西瓜
多线程处理 篇6
关键词:进程;多线程;.NET框架;VB.NET
中图分类号:TP311.1
应用程序开发过程中处理并发问题以及多任务管理的问题经常使用多线程编程技术这一程序设计中广泛应用的技术,这一技术是这类问题最简便的解决方案。当应用程序需要多个操作同时运行时,多线程技术可以一个线程在接收键盘输入数据的同时另一个线程计算并进行数据传输,而其它的线程可以同时完成屏幕的刷新显示或从外部设备读入数据等任务。VB.NET通过CLR(Common Language Runtime)提供了对多线程机制的支持。
1 多线程技术的相关概念及优势
1.1 关于进程、线程和多线程概念的理解
进程指的是应用程序的一个具体运行实例,可以认为是程序处于某一次动态执行状态。我们认为操作系统中拥有系统资源的基本单位和独立调度、分派任务的基本单位是进程。线程则是进程内部的一个执行单元或异步代码路径,每个进程可以包含若干个线程。线程是比进程更小的独立运行的基本单位,引入线程的目的是为了减少程序并发执行时的所付出的开销,使操作系统具有更好的并发性。线程只占用一些运行中必不可少的资源(程序计数器、一些寄存器和栈),除此之外不占用其它资源,但是同一进程中的线程可与其他线程共享分配给进程的系统资源,例如分配给进程的虚拟内存空间和其它的系统资源,且同一进程中的线程可以并发执行,线程的并发执行实质上是多个线程以轮流占用时间片的方式共享处理器时间。如果一个应用程序同时包含多个线程时,则称该应用程序使用了多线程技术。
1.2 多线程机制在软件开发中的优势
多线程机制的主要优势表现在提高了处理器处理时间的利用率,能够以更快的速度对用户的需求进行响应,从而提高了应用程序中进程的整体执行效率,并且增强了应用程序的适应性。多线程技术处理一个交互应用程序的时候,当线程的一部分被阻塞的时候,该应用程序还能继续运行,因此对用户增强了响应能力。多线程技术可以让程序中占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。
多线程技术在软件开发的广泛应用正是基于上述优势。例如,当程序需要进行费时的I/O操作时;分布式应用环境下,更多的用户可以通过线程方式分享服务器上的处理器资源以提升扩展性;OA应用程序也可以通过多线程中处理后台操作以提升处理效率。
2 VB.NET对多线程技术的支持
网络应用程序一般情况下均是多任务并发运行环境,要求很高的运行效率,而这正是多线程技术的优势所在。VB.NET基于.NET Framework,而NET Framework框架的重要组成部分CLR(通用语言运行时)实现了多线程机制,从而包含了对多线程技术的支持。可以使用System命名空间下的Threading类在程序中创建多个线程、对线程进行管理并且支持线程池等增强功能的实现。VB.NET,VC#.NET等.NET Framework框架下的语言编在开发多线程应用程序的过程中,均可以使用Threading类的方法和属性,不用像VB6.0那样再去非常麻烦的调用Win32 API函数,使得开发过程更为简化并且有效减少了各种错误的产生。
3 多线程编程在VB.NET中的实现
3.1 线程的创建和控制
VB.NET中线程的创建和控制主要通过.NET基础类库中System.命名空间的Thread类进行实现,Thread类用于创建线程并对线程进行控制操作,并可以获取和设置线程的优先级和当前状态。
对一个线程进行创建和控制操作的大致步骤是首先引入Thread类所属的命名空间:Imports System.Threading,接着创建一个Thread类的对象,并且通过AddressOf子句传送委托给需要调用的具体过程,然后启动运行线程。进行线程创建操作后,可以使用Thread类的Start方法启动线程运行,使用Suspend和Resume方法将线程挂起或将挂起的线程恢复运行,此外Thread类还提供了Interrupt、Sleep、Abort及Join等多种方法以控制操作线程。Thread类还具有Name、IsAlive、PriorityIsBackground和ThreadState等重要属性,通过这些属性可以获取或更改当前线程的状态。
3.2 线程生命周期中的状态转换及线程优先级设置
线程在整个生命周期中处于不同的状态,Thread类的ThreadState属性决定了线程的状态。初始创建线程时,线程处于Unstarted状态,使用Thread类的Start()方法可将线程状态转换为Running状态。Running状态下调用Suspend()方法将线程状态转换为Suspended状态,直到调用resume()方法使线程重新运行而处于Running状态。如果调用Thread.Abort()方法,线程将停止运行并处于处于Stopped状态。
线程的优先级指的是线程对处理器时间的优先占用权。通常情况下线程轮流占用处理器时间片,但当高优先级的线程与低优先级的线程并发执行时,操作系统优先将处理器时间片分配给高优先级的线程。通过Thread类的Priority属性可以设置线程所具有的优先级,Priority属性值为枚举类型ThreadPriority。
3.3 线程间的同步问题
线程同步问题是指多个线程之间相互占用对方资源导致各线程的任务无法继续执行的问题,例如多个线程同时访问同一对象,或者多个线程运行同一段程序代码,这些情况下各个线程均处于阻塞状态。
为了避免这一问题的产生,需要通过某种方法进行线程间的同步。一种同步方法是可以使用VB.NET中的Synclock语句块,将多个线程可能同时访问的对象或同时执行的程序代码段放入Synclock语句块,使的线程可以得到对象引用的独占锁,从而避免多个线程同时访问同一对象,或者多个线程运行同一段程序代码。在SyncLock语句中要得到某一对象引用的独占锁,一般通过调用GetType方法获取与所有类相关联的System.Type对象实现。另一种同步方法是调用Thread类的Join()方法使得调用该方法的线程处于指定时间的阻塞状态,直到其它的线程执行完毕,通过判断Join()方法的返回值可以判断出其它的线程在指定时间内是否已经执行完毕,返回值为True表明已经执行完毕,返回值为False表明尚未执行完毕,通过这种方式来实现线程同步。
4 结束语
多线程技术是提升应用程序执行效率,进一步增强程序并发性,充分利用系统资源的一种重要手段,也是目前编程技术的核心思想之一。使用VB.NET的多线程技术进行应用程序开发,可以有效的提高应用程序开发效率并且大大缩短程序响应时间,而且能够对系统资源进行更加有效的利用。
参考文献:
[1]程伟,肖文敏.Visual Basic.Net的多线程机制[J].电脑开发与应用,2007(11).
[2]张焰林.基于VB.NET的多线程技术应用[J].计算机系统应用,2009(02).
作者简介:陈俊伟(1976-),男,重庆铜梁人,教师,讲师,硕士,研究方向:软件技术,计算机网络。
多线程处理 篇7
关键词:FITS文件,多线程,光谱
1 引言
在天文学研究中,天体的主要信息都存在于光谱中,通过观测所得到的光谱经过一系列处理以数据文件的形式进行存储,现在主要的光谱文件存储格式是FITS格式。对光谱的进一步处理,如分析测量分类等,都需要对FITS文件进行解析。因此如何高效快速地对FITS文件进行分析处理具有很大的意义。目前大部分光谱处理及分析软件都是基于单条FITS文件进行处理,不适于进行快速批量的FITS文件的读取。为了解决这个问题,本文提出了基于多线程的FITS文件的快速处理方法。第1部分介绍了FITS文件的数据结构和一些主要参数;第2部分介绍并分析了当前处理FITS的主要软件;第3部分介绍快速处理FITS文件的方法实现;在第4部分通过对该实现的性能测试给出了实验的结果和结论。
2 FITS文件介绍
FITS(Flexible Image Transport System)是为天文数据传输和交换提供的一种标准格式。它描述了数据定义和数据编码的一般方法[4]。FITS文件是由多个文件头和数据块组成的。一个FITS文件中有七个HDU,每个HDU都包括文件头(ASCII码)和数据记录。下面以第0个HDU为例,给出其文件头和数据记录的主要格式及内容[1]。其中文件头中包含:
Simple: 是否符合文件标准。
BITPIX: 一个数据值的位数,有5种允许的情况,8位,16位,32位二进制整数,浮点数(32和64位,按照IEEE标准)
Spectra classification 光谱分类
这些是主要的文件头的参数,可以看出,跟据文件头定义的标准可以读出很多有关光谱的属性。在FITS文件中,文件头的结束标志为“END”。在文件的数据部分为8位字节流的形式存放。图1为FITS文件7个HDU的结构示意图。
3 当前主要FITS处理软件的分析
随着计算机技术、互联网技术,特别是像软件开发工具的快速发展,天文软件也像其它门类软件一样得到了高速发展。天文软件的开发队伍中既有世界著名的软件公司、专业软件开发人员,也有酷爱软件编程的天文爱好者。现在的天文软件种类繁多,内容几乎涉及天文学的各个领域。
fv是由美国宇航局高能天体物理科学文献研究中心开发的,是适用于多平台的FITS浏览和编辑软件;FITS Tools 是一组用于观察和修改FITS文件的应用程序。利用CFITSIO库函数用C编写,其代码可由用户修改;FTOOLS Package是一个用于FITS光谱分析和测量的大型的软件包,包含了丰富的功能;此外,还有很多处理FITS文件的软件,如FITS IMAGER 2000,FITSX等[2]。这些软件主要是用于FITS文件的读取浏览和FITS的编辑功能。对单条的FITS有很好的编辑功能,其共同的特点就是对单一FITS文件的处理功能强大,集合了很多处理的功能模块,但是不能很好地快速地进行多条FITS文件的并发处理,因而在处理速度和效率上存在着不足。
4 FITS的快速处理方法
4.1 快速FITS读取的设计原理
进行快速FITS处理的基本思想是采用计算机处理的多线程技术。通过多线程技术进行多FITS文件并发读写,将读出的光谱数据进行自动编号并按所需格式进行存储。
从FITS的构成中可知,每个FITS文件中包含一个主HDU,并有六个扩展头组成。在本设计中,主要实现了FITS文件头的读取、数据部分的读取以及把读出的光谱数据按特定格式进行存储。本实现中,所读取的光谱数据统一存放于自定义的浮点格式的.CODE文件中。
在读取文件过程中,文件I/O是最耗费时间的。因此在文件读取和写入过程中,通过建立多个线程,为每个线程分配一个FITS文件,多个线程并发执行,从而加快处理速度。同时使用加锁机制避免各线程的对同一文件写入会引起文件写入的冲突。FITS处理系统的示意如图2所示,线程的工作流程如图3所示。
4.2 快速FITS读取的具体实现:
开发环境为visual studio.net,采用C#进行编码。文件读写所用到的最基本的FileStream类,它打开文件可进行读写字节操作,它是由Stream类抽象而来的,它的许多属性和方法都是从Stream类派生得到的[3]。
由于FITS文件的数据块部分为8位的字节流,所以就恰恰用到了FileStream类的读写字节的方法。但是FileStream类同样存在着一些问题,由于C#在强制类型转换方面的不灵活就导致了FileStream类的Read和Write方法使用并且只能使用字节数组,在读文件数据块中虽然起到了很好的作用,但在文件的其它操作方面增加了转换的复杂度和效率,不是很令人满意。为了解决这个问题,又用了System.IO类具有的几个专门用于文件读写的类。图4是对象的层次示意。
在系统中,为了并发处理的方便,在进行FITS文件的选择方面,可进行多个文件的选择(图5),或者指定文件夹(图6),选定目录下的FITS格式文件及子目录中的FITS文件,进而进行并发读取处理。
5 实验结果及结论
通过对程序运行时间的测试,对比运用多线程和顺序的方法进行读取,实验证明在运用多线程的方法读取FITS文件的效率在读大量文件时要比顺序读取好。在测试中实验运行的硬件环境为Intel P4 2.93GHz,512M内存,软件环境为Win XP SP2。分别读取了1条,10条,50条,100条,200条,640条FITS文件进行运行时间测试,分别测试10次求平均值,得到如(表1)的结果。其中的FITS文件来自于SDSS(Sloan Digital Sky Survey)[5]。
在实现中,多线程可以有效地提高CPU的利用率,但是并非线程数越多越好,线程的调度和线程间的切换也会占用处理时间。所以要解决好线程数目和处理时间的关系,选择一个最为有效的线程数。本实现中,利用测量得出的读取处理时间来确定线程的最有效数目。方法是在运行时依次建立3-10个线程对样本数据进行读取处理,记录运行时间,选择运行时间最少时的线程数目作为有效线程数。其中的处理时间对比见表1。
在表中可以看到随着FITS文件数目的增加建立3条线程的读取方式明显效率和速度都很高。3个线程提高了CPU的利用率,达到了提高处理速度和效率的目的。同时,实验结果表明并非线程数越多越好,线程数目越多,花费在线程切换的时间也就越多。所以在运用多线程来处理的时候要找到一个理想的线程数,既可以提高CPU的利用率,又不至于把太多时间浪费在线程切换上面,最终达到快速处理的目的。当然,对于不同指标的计算机,其最优线程数是不同的。
在此FITS文件处理中,由于文件IO是占用时间最长的,为提高处理速度做了一系列的代码优化,比如为了缩小文件的存储空间,并且为后续处理提供方便,数据的写入格式不用科学计数法而直接采用浮点格式,为提高读取效率而设置较大的缓冲区等。
参考文献
[1]胡新华,邓元勇,王先平.Fits图像处理技术荟萃及在太阳观测中的应用.天文研究与技术,2008.3:55-65.
[2]崔辰州,李文等.FITS数据文件的检索和访问.天文研究与技术,2008.2:116-124
[3]H.M.Deitel,P.J.Deitel,J.A.ListField,T.R.Nieto,C.H.Yaeger,M.Zlakina,C#for Experienced Programmers,2003.
[4]The FITS support office at NASA/GSFC.http://FITS.gsfc.nasa.gov/.
多线程处理 篇8
一、多线程的同步处理机制
当多个线程操作共享数据时, 为了保证数据的一致性和完整性, 通常要求一个线程对共享数据操作时, 另一个线程则不能同时操作该共享数据, 否则会导致线程共享数据发生冲突[1]。例如银行在各账户间转移账目, 各账户数据就是共享资源对象, 一些游戏 (如象棋、扑克) 中各个玩家对棋子、牌的操作, 这些棋子和牌也是共享资源对象。
1.1问题分析。Share Data对象是一个共享资源对象, 该对象里面有一个int类型的属性i, 这个属性就是两个线程共同访问的数据。具体程序代码如下:
对程序进行编译运行后, 结果如图1所示。
问题分析:程序结果并没有将1到10依序打印出来, 问题就归结到那个一千万次的循环。假设t1先执行, 当它执行到将i的值累加1之后 (变成1) , 接着执行一千万次的循环。可是可能只循环到五百万次时, 因为CPU使用时间用完了, 被系统给换了下去, 然后系统又挑选t2开始执行。t2也执行到将i的值累加1, 刚刚t1已经累加过一次了所以i的值变成了2。然而刚刚t1还来不及将1输出时, 就被换了下去, 然后t2将i累加变成了2, 因此在结果中没有出现1。最后输出两个10, 是因为t2在执行循环到一半时被换了下来, 接着t1将i累加变成10之后, 虽然它可能在执行循环时被换了下来, 可是系统又挑选到t1继续执行, 所以t1顺利地输出了10, 这时t1已经结束它的工作了。接着系统挑选t2执行, t2也执行完循环后再输出一次i的值, 因此结果中出现了两次10。
程序中有如下代码:
这个代码段被称为临界区。在多线程的应用程序中, 这个临界区代码不能被分割执行, 否则会造成数据不一致的结果。同一个进程的多个线程共享同一块内存空间, 在提高了CPU的利用率的同时, 也产生了一系列的访问冲突的问题[2]。因此需要运用数据同步处理机制解决多线程间资源利用上的协调问题, 从而保证程序中数据的一致性和完整性。
1.2线程的同步机制。线程同步是指在多线程环境中, 可能出现两个或多个线程同时使用同一个有限资源的情况。为了避免出现资源冲突问题, 实现在临界区上的互斥访问, Java提供了同步机制来保证任一时刻只有一个线程能够进入临界区[3]。该同步机制就是通过使用synchronized关键字为临界区代码设定一个锁, 当某个线程对象想要执行该段程序代码时, 就要取得那个锁才能执行。这个锁对于同一份数据只有一个, 当锁被某个线程抢先占有时, 其它线程则会进入到锁定池 (lock pool) 中等待, 直到该锁被释放归还后, 该等待线程才能开始执行被锁上的程序代码。
(1) synchronized同步块:要将一段程序代码加锁, 可以使用synchronized关键字, 语法如下:
Synchronized (要取得锁的对象)
{要锁定的程序代码}
受到synchronized保护的程序代码中, 要访问的对象属性必须设定为private, 因为如果不设定为private, 则就可以用不同的方式来访问它, 这样就达不到保护的效果了, 失去了线程同步的意义。
(2) synchronized方法:synchronized指令还有另外一种用法, 就是把它写在方法的声明上, 例如:
public synchronized void method () {…//方法体代码}
这种方式的优点是, 用javadoc制作说明文档时, 可以知道哪些方法是synchronized。但缺点是, 方法中有些代码是不需要被保护的, 如果该方法执行时间较长, 那么其它想要获得锁的线程必须耗费较多时间等待锁被释放;另一个缺点是, 只能取得自己这个对象的锁, 有时候程序设计的需求, 可能会需要取得其它对象的锁, 那么使用这种方式就无效了。而第一种方式, 它的优点是可以针对某段程序代码进行锁定, 不需要浪费时间在别的代码上, 而且还可以取得不同对象的锁。因此, 第一种方式适当的弥补了synchronized方法声明的缺陷。
1.3多线程死锁预防。在多线程应用程序中, 可能出现这样一种情况:当一个线程A执行某段锁定的代码, 它取得甲对象的锁, 有另一个线程B执行另一段代码, 它取得乙对象的锁。如果在A执行的代码中, 需要执行另一段锁定的代码, 而这一段代码的执行需要取得乙对象的锁, 然而乙对象的锁已经被线程B抢占且未释放;同样地情况也发生在线程B上, B也去执行了需要甲对象锁的程序代码, 然而甲对象的锁已经被线程A抢占且未释放。这是A和B都在等待状态, 双方各自拥有对方需要的锁, 两个线程彼此无穷等待都不能顺利继续执行, 导致死锁。
Java中并未提供检测死锁的机制, 为了在设计多线程的程序时, 系统能更稳定, 下面给出几个死锁预防的策略:
(1) 尽可能将加锁的时间减至最短。将锁定的程序代码中不需要被保护的部分代码剔除出锁定范围, 以减少被锁定代码的执行时间。
(2) 另一个最简单的预防方式是, 将每个锁都标上优先权顺序, 在编写程序时要严格遵守这个顺序, 必须依序取得需要的锁;如果某个优先权较高的锁无法取得时, 必须释放掉本线程所拥有的全部锁, 然后重新再执行一次。释放锁时要依照相反的顺序。
总之, 在设计大型程序时, 整个系统的规划非常重要, 要充分考虑到所有可能出现的状况, 将系统规划好后再编写程序代码, 使线程在执行期间按预期的目标顺利进行。
二、线程的“Monitor Model”机制
在多线程程序中可能会出现这样的状况:线程A正在等待另一个线程B将数据传送过来, 数据还未到达之前, A已进入等待状态, 等到数据到达时, B将通知A可以处理数据了。在操作系统中, 最有名的就是生产者与消费者问题。在Java线程中可以通过“Monitor Model”机制来完成这种线程的高级操作。当某个线程调用wait方法后, 释放掉该线程拥有的全部的锁, 然后进入等待池 (wait pool) 。当另一个线程调用notify方法时, 系统从等待池中任意挑选一个正在等待的线程进入锁定池中。其完整的线程状态转移如图2所示。当然, 在具体的多线程应用中, 可以设定要等待的时间, 也可以调用notify All方法将等待池中所有的线程全部转移到锁定池中。
Java提供的“Monitor Model”机制有两个主要目的:第一个是为了保证共用的数据会在一致的状态;另一个是保证系统不会进入死锁状态。需要注意的是, 线程类中suspended、stop、resume三个方法的调用容易导致线程进入死锁状态, 虽然可以利用“Monitor Model”机制来重新恢复这三个方法, 但出于安全性的考虑, 建议不再使用这三个方法。
三、Android中多线程处理
Android是Google推出的智能手机操作系统, 目前, Android应用开发是Java程序员发展的一个新方向, 而基于Java语言的Android平台是支持多线程技术的, 这样在很大程度上能够提高Android程序的运行效率, 充分利用Android的硬件资源[4]。例如, 在Android开发中如果遇到需要耗时的工作的时候, 就要使用多线程来解决。下面就通过一个线程间通讯的例子说明多线程技术在Android开发中的应用处理。
线程之间通讯的基本原理是“handler+message”机制, 即在主线程中定义接受子线程消息的handler对象, 子线程创建message对象调用send Message方法向主线程发送消息[5]。首先修改activity_main.xml文件, 定义文本提示标签打印接受来自子线程的消息, 代码如下所示:
整个通讯的消息都是在子线程中得到的, 然后每隔2秒调用send Message方法发送一次子线程中得到的日期给主线程, 主线程创建handler对象自动调用handler Message方法接收message消息, 当然, 也可以加入多个子线程, 分别发送消息给主线程, 这就是实现一个及时通讯软件的最基本的原理了, 更能使得android程序流畅运行[4]。
四、结语
本文首先通过实例分析讨论了多线程数据同步处理的意义, 然后通过synchronized块和方法声明两种方式实现了多线程的数据同步互斥访问, 并提出解决共用数据一致性问题的“Monitor Model”机制和预防线程死锁的方法, 有效地避免了Java中数据同步处理混乱的问题。最后结合android手机开发中的多线程处理, 提出了线程之间通讯的“handler+message”机制, 将需要耗时的工作放在子线程中进行, 避免了应用程序无响应 (ANR) 的现象, 这种技术在android手机应用开发中表现得尤为重要和实用。
参考文献
[1]李娟.Java多线程同步机制研究分析[J].中国科教创新导刊, 2014 (7) :183-184.
[2]陈益.Java多线程同步机制的应用分析[J].智能计算机与应用, 2012 (6) :86-89.
[3]吴红萍.Java的多线程机制分析与应用[J].软件导刊, 2014 (1) :114-115.
[4]闫伟.多线程技术在android手机开发中的应用[J].信息通信, 2012 (1) :46-47.
多线程处理 篇9
气象信息系统中,气象数据是其核心和基本的组成部分[1],也是气象信息系统提供服务的主要内容,实时气象数据库系统的建设变得日益重要。
运用电子计算机对各种观测或探测手段所获取的大气状态和现象的资料进行加工和管理称之为气象资料处(Meteorological Data Processing ,MDP)。按时效不同,可把气象资料分为实时资料和非实时资料两种[2]。实时资料处理要求迅速、及时,主要通过电子计算机程序控制,把从资料传输系统接收的资料进行识别、分类、质量检查和编集,再按需要送往各个终端或天气预报用的电子计算机系统。非实时资料常采取事后收集加工的方式,没有严格的时限要求,因而可以加工得更加完善。本文重点研究实时气象资料,它对天气预报、数值预报、气候预测,灾害预防等各项实时气象业务尤为重要。因此各种实时观测资料从通信系统进行收集后,必须得到有效的存储管理,并提供方便、快捷的共享服务。
传统的基于进程的单一线程的资料处理只是在获得单类(单个)资料之后进行操作,任务完后才能使进程响应其他资料处理和用户操作,不能同时综合处理多种实时资料。各类别的资料往往需要安装配置各自对应的处理软硬件系统。资料处理效率低且自动化、集成化不高,耗费大量的时间,占用较多的网络和数据库资源,设备利用率低,不能很好地满足气象数据实时采集与处理要求。
基于多线程技术的实时与非确定时气象资料处理系统能够同时执行多项任务、 实现数据并行处理、 减少后台过程和前台界面的相互影响,使多任务并行执行成为可能。通过研究多线程技术和实时气象资料的特性,针对实时气象资料的特性和资料采集处理性能要求和气象业务需求,提出依据不同的实时气象资料的特点分别采取实时和非确定时的分析处理策略,通过多线程编程技术实现。经过实例结果分析,该方法在处理时间上和CPU使用率上比传统方式有明显改进。
1 处理策略研究
1.1 总体结构
实时资料处理系统与气象通信系统、气象业务平台紧密耦合。实时气象资料一般通过气象部门专用网实时获取,气象数据要为气象业务平台提供支持。资料处理软件运行在处理平台上,通过气象部门内网从资料服务器获得源文件,分析处理完成之后存储到专有数据库中,为气象业务平台提供数据支持。业务平台依靠数据支持为气象部门内部提供专业的服务和产品支持,同时也可通过Internet向外网中的公众提供气象共享信息服务。总体结构如图1所示。
1.2 处理策略与流程
实时气象资料的处理策略在本系统中至关重要,它会影响整个系统的数据处理的性能。根据实时气象资料生成的属性将其分为确定时实时资料和非确定时实时资料。确定时实时资料指的是生成、发布时间一般比较固定的资料,而非确定时实时资料的生成、发布时间就相对随机,这两类资料本文采用不同的处理策略。
确定时间生成发布资料获取时间范围确定。例如自动站报文每隔6分钟生成一次,城市短时天气预报每日发布6时、11时、17时预报各一次。处理这类资料是在规定延迟时间内定时启动程序调用相应线程进行实时处理。
非确定时间生成发布资料的获取的时间比较随机。一般取决于地域、季节、气候等外界因素,资料生成无固定时刻,例如预警信息。在处理这类资料时,系统按照预先设置间隔时间扫描文件。扫描时若有新的待处理资料则主程序给相应的处理程序分配一个线程进行处理。这类资料的处理是在一个相对随机的时间进行,因此称非确定时处理。
采取这样的策略:首先,提高系统的可用性,可以通过设置系统中定时模块满足在资料发布业务时效性规程改变情况下不影响系统的使用;其次,优化了系统资源分配,省略了定时发布资料在不必要时间段内的文件扫描,对于非确定时发布资料可以根据地域、季节、气候等特点设定某些资料的扫描频率。处理策略流程如图2所示。
2 多线程优化
2.1 单一线程的进程
应用程序启动时操作系统为程序分配内存及其他资源,内存和资源的物理分离叫做进程,是程序在计算机上的一次执行活动[3]。线程是进程内部的一条可执行路径,是CPU调度的单位,是进程的一个执行单元。每个进程应至少有一个线程,即主线程。
2.2 多线程的进程
一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效地提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量[4]。
一个应用程序可以创建多个线程,多个不同的执行流,并同时运行这些线程。线程机制使系统具有多任务功能,用户可以同时运行多个程序,且每个程序中又可同时运行多个线程[5],这样多个线程并发的在同一进程中。多线程并行效率模型如图3所示。
2.3 .NET下多线程实现
从本质上和结构来说,.NET是一个多线程的环境。CRL内置支持多线程应用,任何 .NET框架下的语言,包括Visual C#在内,实现多线程时不再需要使用Win32API,可直接使用系统类提供的对象和方法。在VS2005中实现多线程非常方便,System.Threading命名空间提供一些使得可以进行多线程编程的类和接口,可以直接使用它建立多线程应用程序。
System.Threading.Thread类是创建并控制线程,其定义了多种方法,其中用Thread. Start()、Thread.Stop()、Thread.Resume()、Thread.Abort()、 Thread. Suspend()、Thread.Join()等方法操纵线程,还可通过Thread.Sleep()、Thread.isAlive()、Thread。
IsBackground()等方法设置线程状态。
System.Threading.Thread.Priority枚举了线程的优先级别,从而决定了线程能够得到多少CPU时间。高优先级的线程通常会比一般优先级的线程得到更多的CPU时间,如果不止一个高优先级的线程,操作系统将在这些线程之间循环分配CPU时间。C#中线程优先级从高到低分为Highest、Above、Normal、Normal、BelowNormal、Lowest 5级[6,7,8]。
3 气象资料处理
资料处理系统定时实时处理测报月报表(A报、B报)、SP和DG、城镇预报、短时天气预报这几类测报和预报产品,非确定时间实时处理预警信号、台风、自动站实时报文这几类测报产品。一般情况下,创建少数几个关键线程,用它来解决某个特定的问题[9] 。本系统创建了9个数据处理子线程,当程序启动时读取ini文件,完成数据库连接设置,定时初始化,资料扫描路径等初始化设置后,创建相应子程序线程。多线程处理流程如图4所示,具体步骤为:(1)应用程序启动后,读取INI初始化,如需更改设置,可在程序运行过程中更改设置,改变的信息写入INI。(2)初始化完毕后,系统创建资料处理线程。(3)线程进入while循环,确定时处理进入休眠—等待状态,非确定时处理线程扫描INI路径下文件进入间隔扫描—休眠状态。 (4)定时唤醒实时处理线程,扫描INI路径下文件。(5)有新文件,分别进入各自处理函数、分析解析文件、处理入库,更新日志,修改已处理文件名。(6)如无新文件,进入历史文件操作函数,处理符合条件的历史文件。(7)程序重复上述工作。(8)终止线程,程序结束。
4 实例与结果分析
气象资料种类繁多,资料格式复杂程度、数据量大小、处理精度差异很大。该系统处理的9类实时气象资料中,采取定时实时处理的几种报文结构复杂,资料处理占用时间和空间资源大,在实际业务中对系统的性能要求较高,特别是实时资料处理的时效性和专有处理设备的利用率两个指标比较重要,以下实例验证分别对这两个关键指标性能进行分析。
试验环境为: Windows XP Professional SP3,.NET Framework 2.0,CPU为Inter(R)Core(TM)2 Duo CPU E7400,内存3.5G,SuSe11企业版+Oracle10g IBM X3800 8866-1RC专用数据库服务器,100M局域网。
4.1 时间效率分析
随机选取某地区同一个站点A报、B报和城镇天气预报报文6个月的资料进行测试分析如表1所示。
该系统采用的多线程并行处理运行时间效率与传统的采用基于进程的单一线程处理对比如图5所示。可以看出:(1)对同一组资料处理,该方法较传统方式节省时间。(2)随着数据量的增加,数据量越大,该方法在时间效率性能越好。
4.2 CPU使用率分析
一个进程的所有线程可同时执行,Win32自动在一个进程的多个线程间进行切换。Windows操作系统能将可利用的 CPU时间分配给当前进程的各个线程[10]。使用同一组待处理数据资料,在进程启动之后100秒内,记录该进程在Windows任务管理器中CPU使用率的90次变化数据。将文中多线程
技术并行处理A报,B报和城镇预报与传统的基于进程的单一线程分别处理A报,B报和城镇预报时CPU使用效率作对比,如图6所示。可以看出:采用多文中方法后,CPU的使用效率有了显著提高。
5 结束语
气象资料的处理在气象业务信息化进程中十分重要,本文提出并实现的基于多线程技术的实时和非确定时气象资料处理比传统采用单一线程的进程处理具有较好的时间效率和CPU的利用率。产品已推广到地市一级的气象业务单位,有效地提高了业务过程的自动化、集成化分析处理性能,改善了业务流程的信息化水平,特别是在处理海量历史数据时性能更优。文中提出的针对实时资料生成特点采取定时实时处理或非确定时处理的策略,对其他行业海量实时数据的处理也具有参考和借鉴作用。
参考文献
[1]赵芳.气象数据库系统的建设[C].信息技术在气象领域应用开发,2006:191.
[2]刘旭,祖雪梅.对气象信息化资料重要性的探析[J].林业勘查设计,2010,3:29-30.
[3]Tobin Titus,Fabio Claudi Ferracchiati,Tejaswi Redkar,et al.C#Threading Handbook[M].New York:Apress,2004:3-13.
[4]Josepb Albabari,Ben Albabari.C#4.0IN A NUTSHELL The De-finitive Reference[M].Sebastopol:O'Reilly Media,Inc,2010:873-878.
[5]张能立,张彬.谈谈在.Net下的多线程编程[J].中国水运:理论版,2006,4(3):104-105.
[6]卜春芬.C#后台处理与多线程技术的应用[J].昆明学院学报,2010,32(3):82-85.
[7]欧阳志鹏,沈富可.基于改进线程池技术服务器程序的设计与实[J].计算机与数字工程,2005,33(10):133-135.
[8]Christian Nagel,Bill Evjen,Jay Glynn,等.C#高级编程[M].李铭,译.北京:清华大学出版社,2008:483-489.
[9]郭辉.多线程效率[J].计算机应用,2008,28(12):143.
Java多线程机制研究 篇10
1、线程的生命周期
一个线程创建之后, 总处于某种状态之中, 线程的状态表示了线程正在进行的活动以及在这段时间内线程能完成的任务, 在线程的生命周期中有四种状态:创建新线程态、可运行态、不可运行态、死亡态, 通过对线程进行操作来改变其状态, 如下图所示。
1.1 创建状态
创建了一个线程而还没有启动它, 则处于创建状态, 此时仅是一个空的线程对象, 并不获得应有资源, 只有启动后, 系统才为它分配资源。处于创建状态的线程可以调用start () 方法启动, 使其进入可运行状态;也可调用stop () 方法, 使其进入死亡状态。
1.2 可运行状态
可运行状态只说明该线程具备了运行的条件, 但并不一定是运行状态, 因为在单处理器系统中运行多线程程序, 实际上在每个"时刻"至多有一个线程在运行, 而系统中可能有多个线程都处于可运行状态, 系统通过快速切换和调度使所有可运行的线程共享处理器, 造成宏观上的多线程并发运行。在可运行状态, 线程运行的是线程体, 线程体由run () 方法规定, 在自己定义的线程类中重写。
在可运行状态下可进行多种操作:调用suspend () 方法, 使线程挂起, 从而进入不可运行状态;调用sleep () 方法, 使线程睡眠, 从而进入不可运行状态;调用wait () 方法, 使线程等待, 从而进入不可运行状态;调用yield () 方法, 使线程退让, 把CPU控制权提前交给同级优先权的其他线程;调用stop () 方法, 使线程终止, 从而进入消亡状态。正常的情况下是执行完run () 方法, 使线程结束, 进入死亡状态。
1.3 不可运行状态
处于可运行状态的线程遇到下列情况进入不可运行状态:调用了suspend () 方法;调用了sleep () 方法;调用了wait () 方法;如果一个线程是和I/O操作有关的, 则在执行I/O指令时, 由于外设速度远远低于CPU速度而使线程受到阻塞, 此时也进入不可运行状态。
当线程处于不可运行状态时, 可通过下面途径恢复到可运行状态:通过sleep () 方法进入不可运行状态的线程, 在过了指定的睡眠时间后自动恢复;由于I/O阻塞而进入不可运行状态的线程在外设完成I/O操作后自动恢复;通过suspend () 方法进入不可运行状态的线程, 通过调用resume () 方法恢复;通过wai () 方法进入不可运行状态的线程, 通过调用notify () 或notifyAl () 方法恢复。在不可运行状态, 也可调用stop () 方法进入死亡状态。
1.4 死亡状态
有两种情况使线程进入死亡状态:从可运行状态执行完run () 方法, 自然撤消;从任何一种其它状态调用stop () 方法。从死亡状态不能转换到别的状态, 因为到此线程的生命周期就结束了。
2、线程的建立和使用
在Java程序中可通过对Thread类派生一个子类, 再由这个子类生成对象来实现线程的创建;也可直接定义一个类实现接口Runnable, 然后再由这个类生成对象从而创建线程。
2.1 继承类方式
用这种方式创建线程并使之进入可运行状态, 可参考下面步骤:
(1) 从Thread类派生一个子类。例如:
这就有了一名叫MyThread的线程的类, 用这个类每生成一个对象, 就创建一个线程。
(2) 在run方法中实现线程的功能。一个Thread类的内部结构使得总是执行线程本身的run方法。
(3) 生成类对象, 创建线程。例如:
My Thread myFristThread=new MyThread () ;
(4) 调用start方法, 使之运行。例如:
myFristThread.start () ;
另外在可运行状态中, 可以调用其它方法进入不可运行状态, 或再返回可运行状态, 也可调用stop方法使线程退出。
2.2 实现接口的方式
用继承类的方式创建线程比较方便, 但由于Java是单继承的, 如果定义的线程类已经继承了其它的类, 就不能再继承Thread了, 此时可用实现接口的方式。用这种方式创建线程与继承类的方式很相似, 只是创建新线程时增加中间过渡语句。
例:数字时钟, 显示当前系统时间, 每隔一秒钟刷新一次。
3、结束语
Java具有平台独立、面向对象、多线程、安全性等许多优点, 是目前最为优秀的编程语言之一。用其它一些语言也能实现多线程程序, 但必须靠外部的线程软件包来完成, 而Java本身是使用线程的思想开发的, 他的所有类都是在多线程的思想下定义的, 因而Java从语言级提供对多线程的支持。
摘要:多线程是Java的一个重要特点, 这使得在一个Java程序内部可同时进行多种运算, 从而充分利用系统资源, 提高程序运行效率。本文论述在Java程序中创建线程和实现线程体的机制。
关键词:Java,程序设计,多线程
参考文献
[1].Y.Daniel Liang, Introduction to Java Programming, 机械工业出版社, 2008年6月
[2].Bruce Eckel, Thinking in Java, 机械工业出版社, 2002年9月
[3].H.M.Deitel, P.J.Deitel, How to Java Program, 清华大学出版社, 2004年3月
[4].Horstmann Gay S, JAVA核心技术卷1, 机械工业出版社, 2008年
多线程处理 篇11
关键词:分布式系统;多任务调度;进程;线程;负载均衡
中图分类号:TP316.4文献标识码:A文章编号:1007-9599 (2011) 06-0000-02
Multi-task Assignment and Process/Thread Adjustment in Distribution System
Yao Dong
(Beijing University of Posts and Telecommunications,BeiJing100083,China)
Abstract:Distributed multi-task operating system distribution and task scheduling,load balancing is difficult to achieve,which processes and threads to achieve the task execution and distribution of one of the most important concepts.How to implement a distributed system processes and threads created,and the interaction between the information is a vital part of distributed system design.On the traditional operating system processes and threads and to compare the realization of the mechanism,further discussed in the distributed operating system,how to multi-task allocation,how the different distribution among the host of the process as well as load balancing.
Keywords:Distributed system;Multi-task scheduling;Process;
Thread;Load balancing
一、引言
分布式系统是计算机发展的一个大趋势,目前云计算、云存储的概念已经逐渐落地,实际上云计算就是分布式系统的一种实现。在分布式系统中,进程(在很多操作系统中也称为任务)是十分重要的概念,是实现任务调度与执行的关键,而线程是轻量级的进程,在响应速度与执行效率上相比进程有很大的改进。在分布式系统中如何实现多任务执行,如何在分布的主机以及CPU上进行创建和分配,涉及到调度策略。另外,如何实现分布式系统中进程间以及线程间的通信,也是需要重点考虑的问题。并且关乎分布式系统执行的效率和效果。
我在对分布式操作系统的研究和学习中发现,许多传统单机操作系统的概念实际上是可以沿用的,但是由于分布式系统自身的特性决定了,这些概念的复用是需要根据分布式系统进行调整和完善的。希望通过本文对传统进程与线程的简单分析和比较,从而探讨如何在分布式环境中进行进程与线程的创建与调度,如何在分布式环境中对多任务进行负载均衡。
二、进程与线程
现代操作系统最基本的构件就是进程。进程是进行系统资源分配、调度和管理的最小独立运行单位,操作系统的各种活动都与进程有关。每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。
传统的UNIX进程概念在开发分布式系统应用时已力不从心,这些问题的最好解决之道就是线程,线程推广了进程的概念使一个进程可以包含多个活动。如今,由于线程概念的普及,在UNIX系统中已经普遍实现了线程机制,开发并发应用的程序员现在也可以广泛接触到线程的函数库了。
在传统的Unix模型中,当一个进程需要由另一个实体来执行某件事情时,它就fork一个子进程,让子进程去进行处理。尽管这种模式已经成功使用了很多年,但是仍然暴露出以下问题:
fork开销很大。内存映像需要从父进程拷贝到子进程,所有描述字要在子进程中复制一份,等等。当前的系统实现使用一种称为写时拷贝(copy-on-write)的技术,可以避免父进程数据一开始就向子进程拷贝,直到子进程确实需要自己的拷贝为止。尽管有这种优化技术,但fork的开销仍然很大。
Fork子进程后,需要用进程间通信(IPC)在父子进程间传递信息。Fork之前由父进程准备好的数据很容易传递,因为子进程是从父进程的数据空间及所有描述字的一个拷贝开始的,但是从子进程返回信息给父进程却颇费周折。
线程有助于解决这两个问题。线程有时称为轻权进程,因为线程比进程“权轻”。也就是说,创建线程可能比创建进程快10-100倍:
在一个已存在的进程中创建一个线程比创建一个新进程开销更小。创建新的进程需要进行内存的拷贝操作,这就额外的增加了系统负担,而线程则不需要这个拷贝过程。不过由于现在的操作系统的实现是仅仅当内存需要改变的时候才拷贝改动的部分,所以这里的影响相对还是比较小的。
线程通常用在需要比较好的同步操作的场合。例如,某个问题可以分解为多个几乎对等同步处理的任务的话,则是用线程是很好的选择。进程则适合应用在不需要严格的同步的场合。
线程之间共享数据很方便,因为不同的线程本来就是共享同样的存储空间。而不同进程之间共享数据则需要使用一些IPC机制,例如管道、共享内存、套接字等等。
三、分布式操作系统中进程与线程的创建与调度
事实上,当前使用的操作系统都是把任务分割为进程和线程,分时运行在一个处理器中。这种类似的任务处理方式也可以在很多高性能的应用程序中可以看到,例如数据库引擎、科学计算程序、工作站的工程工具和多媒体程序等。
为了增加处理能力,多数操作系统、应用程序都设计为可以在双处理器和多处理器环境中运行,这样可以利用对称多处理器(SMP)结构将进程和线程放到处理器池中进行运算。通过在一个单芯片中执行两个逻辑CPU,超级线程技术在当前的操作系统和高性能应用中进行进程和线程级的并行处理。多CPU之间进行进程分配的思路与分布式操作系统中进程与线程的创建和调度类似。
在分布式操作系统中,一个新进程的创建分为三个步骤:
1.选择一个目标主机。
2.创建一个执行环境。
3.在执行环境中创建线程。
这三个步骤,除选择目标主机以外,其他两项都与传统的操作系统实现机制相同。需要注意的是,不论分布式操作系统的控制权是如何实现的,作为服务器的主机,必须存放各主机中进程的信息,或者在每个主机上有备份。各目标主机间的通信,以进程级的通信为基础,不同进程间的线程通信,也是通过其主线程进行的。
四、分布式操作系统中进程、线程与多任务分配
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
在分布式操作系统中,多个应用程序同时运行可以采用两种方式进行:
1.协作式多任务方式。协作这个用语意味着多个应用程序之间必须相互协调,依次实现对操作系统的控制。它们并不是真正的多任务执行,因为其中还有多任务共享系统资源的问题。为了让操作系统把控制权从一个程序转换到另一个程序,当前活动的程序就必须周期地检查一个消息队列。
2.带优先权的多任务方式,称为抢先式多任务。在这种方式下,操作系统可以在需要时中断当前的任务,再按照任务队列中各个任务的优先级别来进行任务的调度。
抢先式多任务执行实际上就是抢先式多线程执行,每个线程有一个优先级值。优先级最低的保留给系统使用。其余的分成四类:空闲、正常、高、和实时。注意这些范围是有重叠的。这样做可使调度更灵活,例如,允许某些后台任务比某些前台任务更重要,尽管在通常情况下,前台任务的优先级应该更高。使用实时优先级时要非常当心。如果你把一个任务的优先级设得太高,也可能无法实现多任务执行功能。这是因为一个任务的优先级太高了,它就完全不允许系统中有其他任务运行。
VMM(虚拟机管理程序)负责在分时抢先的环境里调度各个进程和线程,具体包括以下服务:生成线程、管理线程、撤消线程和线程调度。VMM中有两个调度程序:主调度程序和时间片调度程序。主调度程序负责确定最高优先级的线程。只有最高优先级的线程才会运行,其他优先级较低的都被封锁;时间片调度程序负责为所有具有最高优先级的可运行任务分配时间片。
系统运行过程中,线程的优先级可由系统或设备驱动程序改变。例如,一旦中断产生,则处理这个中断的线程优先级临时提高,以便它立即得到时间来处理该中断。完成后,优先级可以再降低。
五、结束语
在分布式操作系统中,实现多任务分配与传统操作系统的实现机制类似,不同的是,需要在不同主机的进程间进行分配,同时还涉及到多线程的情况。但是,基本的思路是以进程为基础,进程的负荷平衡就能够保证线程负荷均衡。具体进程内部的任务分担可以由进程自己实现,机制可以参考传统操作系统的实现。
参考文献:
[1]W.Richard Stevens.Stephen A.Rago.UNIX环境高级编程[M].北京:人民邮电出版社,2006:242-504
[2]W.Richard Stevens.UNIX网络编程:第2卷,进程间通信[M].北京:人民邮电出版社,2010:141-241
[3]W.Richard Stevens.UNIX网络编程.卷1,套接字联网API[M].北京:人民邮电出版社,2010:423-611
[4]何炎祥.分布式操作系统[M].北京:高等教育出版社,2005:343-436
[5]何炎祥,熊前兴.操作系统原理[M].北京:华中科技大学出版社,2001:125-244
[6]佚名.Windows系统进程全解剖[EB/OL].[2009-12-29].http://www.qqread.com/meetwindows/j486113.html
Linux下的多线程编程 篇12
线程技术(thread)早在20世纪60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期。线程就是程序中的单个顺序控制流。利用多线程进行程序设计,就是将一个程序(进程)的任务划分为执行的多个部分(线程),每一个线程为一个顺序的单控制流,而所有线程都是并发执行的,这样,多线程程序就可以实现并行计算,高效利用多处理器。线程可分为用户级线程和内核级线程两种基本类型。用户级线程不需要内核支持,可以在用户程序中实现,线程调度、同步与互斥都需要用户程序自己完成。
2 多线程的优点
和进程相比,多线程的优点之一是,它是一种非常“节俭”的多任务操作方式。我们知道,在Linux系统下,激活一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,激活一个线程所花费的空间远远小于激活一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
多线程的优点之二是线程之间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其它一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
2.1 提高应用程序响应。
这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置一个新的线程,可以避免这种尴尬的情况。
2.2 使多CPU系统更加有效。
操作系统会保证当线程数目不大于CPU数目时,不同的线程运行不同的CPU上。
2.3 改善程序结构。
一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会便于理解和修改。
3 线程库的主要函数调用
Linux操作系统提供了Linux Threads库,它是符合POSIX1003.1c标准的内核级多线程函数库。在linuxthreads库中提供了一些多线程编程的关键函数,在多线程编程时应包括pthread.h文件,连接时需要使用库libpthread.a。
3.1 线程的创建和终止
int pthread_create(pthread_t*pthread,const pthread_attr_t*attr,void*(*start_routine(*void)),void*arg);调用此函数可以创建一个新的线程,新线程创建后执行start_routine指定的程序。其中参数attr是用户希望创建线程的属性,当为NULL时表示以默认的属性创建线程。arg是向start_routine传递的参数。当成功创建一个新的线程时,系统会自动为新线程分配一个线程ID号,并通过pthread返回给调用者。
void pthread_exit(void*value_ptr);调用该函数可以退出线程,参数value_ptr是一个指向返回状态值的指针。
3.2 线程的管理
pthread_self(void);为了区分线程,在线程创建时系统为其分配一个唯一的ID号,由pthread_create()返回给调用者,也可以通过pthread_self()获取自己的线程ID。
int pthread_join(pthread-t thread,void**status);这个函数的作用是等待一个线程的结束。调用pthread_join()的线程将被挂起直到线程ID为参数thread指定的线程终止。
int pthread_detach(pthread_t pthread);参数pthread代表的线程一旦终止,立即释放掉该线程占有的所有资源。
3.3 线程的互斥
互斥量和临界区类似,只有拥有互斥量的线程才具有访问资源的权限,由于互斥对象只有一个,这就决定了任何情况下共享资源(代码或变量)都不会被多个线程同时访问。使用互斥不仅能够在同一应用程序的不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
pthread_mutex_init(pthread_mutex_t*mutex,const pthread_mutexattr_t*attr);初使化一个互斥体变量mutex,参数attr表示按照attr属性创建互斥体变量mutex,如果参数attr为NULL,则以默认的方式创建。
pthread_mutex_lock(pthread_mutex_t*mutex);给一个互斥体变量上锁,如果mutex指定的互斥体已经被锁住,则调用线程将被阻塞直到拥有mutex的线程对mutex解锁为止。
pthread_mutex_unlock(pthread_mutex_t*mutex);对参数mutex指定的互斥体变量解锁。
3.4 线程的同步
同步就是线程等待某一个事件的发生,当等待的事件发生时,被等待的线程和事件一起继续执行。如果等待的事件未到达则挂起。在linux操作系统中是通过条件变量来实现同步的。
pthread_cond_init(pthread_cond_t*cond,const pthread_cond_t*attr);这个函数按参数attr指定的属性初使化一个条件变量cond。
pthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*mutex);等待一个事件(条件变量)的发生,发出调用的线程自动阻塞,直到相应的条件变量被置1。等待状态的线程不占用CPU时间。
pthread_cond_signal(pthread_cond_t*con d);解除一个等待参数cond指定的条件变量的线程的阻塞状态。
4 线程的调度
调度是用来管理任务执行顺序的方法,调度的方法有多种多样,常见的有如下方法:
先来先服务:首先请求服务的,首先处理(也称为FIFO测略);
最短作业优先:需要最少处理工作量的任务首先被处理;
基于优先级的调度:拥有较高优先级的任务最先执行;
轮转算法(round-robin):每个任务得到一个小的时间片,任务处于环形队列,依次被执行,直到任务结束。
类似于进程,线程也可以处于几个状态之一。图1显示了一个简化的线程状态图,一个线程可以处于运行态、就绪态、阻塞态。
一个线程可以是绑定的或非绑定的,如果线程是绑定的,它的调度策略就由内核调度算法决定,如果线程是非绑定的,则由线程库调度者决定那个线程的活动。Linux下线程的调度受三个因素的影响。首先可以通过pthread_attr_setscope库函数来设置contentionscope属性,将一个POSIX线程定义为绑定或非绑定类型,如果PTHREAD_SCOPE_SYSTEM被设置,则线程与一个LWP一一对应,如果PTHREAD_SCOPE_PROCESS被设置,则线程可通过LWP池中的LWP与同一个进程内的其他线程竞争使用系统资源。第二个因素是调度策略。POSIX为线程提供了三种调度策略:
4.1 SCHED_OTHER:
系统默认策略。常使用的是时间片划分策略,竞争的线程按其优先级得到时间片;
4.2 SCHED_FIFO:
先进先出的调度策略,具有最高优先级的等待时间最长的线程将成为下一个执行的线程;
4.3 SCHED_RR:轮转调度。
第三个影响调度的因素是优先级,处理POSIX线程时具有较高优先级的线程将在优先级低的线程之前调度。对一个已存在的线程的调度策略及其优先级,可以通过使用pthread_setshedparam库函数来设置。
5 多线程编程的应用实例
5.1 实例1
下面是通过创建两个线程来实现对一个数的递加的实例。
该实例主要是说明两个线程的互斥,在主函数中首先创建了thread1,thread1中对全局变量number++,这时number=0,之后创建了thread2,thread2同样对全局变量number++,而且两个线程在对number操作时都用了互斥锁,所以在一个线程对number操作时另外一个线程会停在pthread_mutex_lock(&mut)处,直到另外一个线程执行pthread_mutex_unlock(&mut)。需要说明的是,上面的两处sleep不光是为了演示的需要,也是为了让线程睡眠一段时间,让线程释放互斥锁,等待另一个线程使用此锁。
编译执行的结果是:
5.2 实例2
当count值为0时,decrement_count函数在pthread_cond_wait处被阻塞,并打开互斥锁count_lock.
调用到函数increment_count时,pthread_cond_signal()函数改变条件变量,告知decrement_count()停止阻塞。让两个线程分别运行这两个函数,可以实现线程间的同步。
5.3 多线程编程应注意事项
如何即时且干净地从线程中退出是多线程编程中的一个重要问题,下面我将主要就此问题谈谈见解:
5.3.1 通过调用pthread_exit()退出,这确实是一个干净利落的退出方式,打破了程序应有的单点退出的规则;并且在C++语言环境下他可能不会引发堆栈对象的顺利析构。
5.3.2 通过调用pthread_cancel来结束其它进程执行,虽然它能保证堆栈对象的顺利析构,但是由于取消点的不确定性、在临界区内退出而导致死锁(此问题可以通过调用pthread_cleanup_push和pthread_cleanup_pop解决)、未释放的系统资源(如文件或者套接字)等问题,致使它也并不是很好的问题解决之道。
5.3.3 通过return语句结束线程比较合理,推荐使用多线程程序虽然可以实现并行计算,高效利用多处理器,但这也给调试程序带来了很多麻烦,不过当前C-C++编译器都捆绑了线程调试器,例如Gun C编译器gcc自带了gdb,Solaris的cc-CC编译器自带了dbx,线程调试器会自动识别多线程代码,这些调试器可以用来在多线程程序中单步执行,并且检查互斥锁和TSD的值。为了可以调试程序,在编译时传入-g,这样可以防止编译器自动删除可执行文件中附加的符号表,例如:cc-g test.c-lpthread-o test。
当激活调试器时,需传入可执行文件的文件名。如dbx test。关于dbx的命令列表可参见dbx的帮助手册。
pthread_mutex_lock和pthread_cond_wait都明确表示不应该因为被信号中断而返回,那么他们可能永远阻塞吗?mutex不会,因为它只是将操作串行化,pthread_cond_wait可能会,这就需要我们在必要的时候用pthread_cond_broadcast唤醒所有阻塞在其上的线程。
6 结束语
Linux中基于POSIX标准的很好的支持了多线程技术,它减少了程序并发执行时的系统开销,提高了计算机的工作效率。在具体编程过程中要了解线程的间的关系,还要考虑共享数据的保护,在互斥和同步机制下保证代码的高效运行。
参考文献
[1]Linux系统分析与高级编程技术[M].北京:机械工业出版社,1999.
[2]Linux下的C编程[M].北京:人民邮电出版社,2001.
[3]Linux应用实例与技巧[M].北京:机械工业出版社,2001.