科学网

 找回密码
  注册

tag 标签: 多线程

相关帖子

版块 作者 回复/查看 最后发表

没有相关内容

相关日志

[转载]走向并行系列-perl的多线程(二)
aaa0 2013-8-3 00:29
走向并行系列-perl的多线程(二) ​ 在当今多核CPU主流的形势下,多核并行程序提供了最大话利用CPU的可能性。perl自5.6版本后开始提供ithread模块专门为perl的多线程提供技术支持。在perl的多线程一文中,我们初步探讨了perl的多线程技术。里面使用了is_joinable方法,来判断目标线程是否已经执行完成,并处于等待join的状态。程序源码如下(http://blog.sina.com.cn/s/blog_7ea1e7fc0100r61x.html): use threads;use warnings;use strict; print localtime(time),"\n"; my $j=0; my $thread; while() { last if($j=10); while(scalar(threads-list())5) { $j++; threads-new(\ss,$j,$j); } foreach $thread(threads-list(threads::all)) { if($thread-is_joinable()) { $thread-join(); print scalar(threads-list()),"\t$j\t",localtime(time),"\n"; } } } foreach $thread(threads-list(threads::all)) { $thread-join();print scalar(threads-list()),"\t$j\t",localtime(time),"\n"; } print localtime(time),"\n"; sub ss() { my ($t,$s)=@_; sleep($t); print "$s\t"; } 上述方法有一个极大的缺陷。如果正在执行的五个线程都没有执行完成,最外层的while循环将会一直“空转”,直到有一个线程被join掉,while循环在控制创建新的线程。这个过程中,主线程因为这个while循环的存在,会一直耗费系统资源。其实在任务管理器中可以看到,我们的程序会耗费50%的CPU(双核CPU),实际上这都耗费在了没有执行任何功能的外层while循环上。 在perl创建的线程结束时不会有任何提示,以告诉主线程说自己(从线程)已经结束。所以必须使用附加程序来判断目标线程是否已经执行完成,并立即join掉线程,以释放系统资源。但是这个判断过程及耗系统资源。正如上面的程序。 为此,在一次google了一下,感谢云舒提供的方法(http://icylife.net/yunshu/show.php?id=617),终于学会了。 --------------------------------------------------- 信号量 Thread::Semaphore 包为线程提供了信号量的支持。使用信号量可以控制并行的线程数。 对象申明: my $semaphore = Thread::Semaphore-new(5); or my $semaphore = new Thread:: Semaphore ( 5 ) ; 控制并行的最大线程数 对象方法: $semaphore-down; 获取一个信号量,之后可用的信号量减1,当$semaphore=0时,表示所有的线程都已经创建,无法获得新的信号量, 并且此时主线程处于等待 。直到获得新的信号量为止。 $semaphore-up; 释放一个信号量,之后可用信号量加1.当一个线程执行完毕时,调用此方法,释放一个信号量。此时 $semaphore-down方法获得信号量成功。处于等待的主线程从新唤醒,创建新的线程。 -------------------------------------------------------------------- 收割线程 上次使用的程序收割线程时使用的是join方法,join方法的特点就是如果线程已经执行完毕,那么此时调用join方法理所当然,但是如果join调用过早,线程还没有执行完毕,主线程就会处于拥堵状态。知道从线程执行完毕,join方法完成为止。这极大的限制了程序运行的性能。 perl里面提供了另外一种收割线程的方法:detach。detach的特点是直接把从线程从主线程里面剥离,以后互不相关。当从线程执行完毕时会自动释放占有的资源。算是省心省力了。这里我们将尝试使用detach方法收割线程,并且使用信号量来控制线程的数量。 实例: ------------------------------------------------------------ use threads; use Thread::Semaphore; my $j=0; my $thread;my $max_threads=5; my $semaphore=new Thread::Semaphore($max_threads); print localtime(time),"\n"; while() { if($j10) { last; } $j=$j+1; #获得一个信号量;当执行的线程数为5时,获取失败,主线程等待。直到有一个线程结束,新的信号量可用。回复正常运行; $semaphore-down(); my $thread=threads-new(\ss,$j,$j); #创建线程; $thread-detach(); #剥离线程; } #必须存在的部分,用在控制在主线程结束前保证所有剥离的线程已经执行完成。否则的话,当主线程结束时还有从线程没有执行完成,这时从线程将不得不被强行kill掉(皮之不存毛将焉附)。 waitquit; print localtime(time),"\n"; sub ss() { my ($t,$s)=@_; sleep($t); print "$s\t",scalar(threads-list()),"\t$j\t",localtime(time),"\n"; $semaphore-up(); #当线程执行完成时,释放信号量。 } #来源于云舒(http://icylife.net/yunshu/show.php?id=617); sub waitquit { print "Waiting to quit...\n"; my $num=0; while($num$max_thread) { $semaphore-down(); $num++; print "$num thread quit...\n"; } print "All $max_thread thread quit\n"; } ------------------------------------------------ 程序运行结果和前文一致。就不描述了(http://blog.sina.com.cn/s/blog_7ea1e7fc0100r61x.html)。 与上次使用的代码最大区别在于少了一个外循环用来判断从线程是否已经执行完成。极大的降低了CPU的使用率。
2549 次阅读|0 个评论
[转载]走向并行系列之-perl的多线程
aaa0 2013-8-2 23:51
走向并行系列之-perl的多线程 (2011-05-06 22:13:01) ​ 实验室的大骆驼回来了,粗略浏览了一遍,受益颇多。于是有了下文。实例纯属自己摸索,错了请高手指正。 先转一下多线程的概念: 线程是一个单一的执行流程,它是所有程序执行过程中最小的控制单位,即能被 CPU 所调度的最小任务单元。线程与进程之间既有联系,又完全不同。简单地说,一个线程必然属于某一个进程,而一个进程包含至少一个或者多个线程。早期的计算机系统一次只能运行一个程序,因此,当有多个程序需要执行的时候,唯一的办法就是让它们排成队,按顺序串行执行。进程的出现打破了这种格局,CPU 资源按时间片被分割开来,分配给不同的进程使用。这样一来,从微观上看进程的执行虽然仍是串行的,但是从宏观上看,不同的程序已经是在并行执行了。如果我们把同样的思想运用到进程上,很自然地就会把进程再细分成更小的执行单位,即线程。由于一个进程又往往需要同时执行多个类似的任务,因此这些被细分的线程之间可以共享相同的代码段,数据段和文件句柄等资源。有了进程,我们可以在一台单 CPU 计算机系统上同时运行 Firefox 和 Microsoft Office Word 等多个程序;有了线程,我们可以使 Firefox 在不同的标签里同时加载多个不同的页面,在 Office Word 里编辑文档的同时进行语法错误检查。因此,线程给我们带来了更高的 CPU 利用率、更快速的程序响应、更经济地资源使用方式和对多 CPU 的体系结构更良好的适应性。 关于多线程的详细讲解,可参看:perl 线程模型讲解(http://it.chinawin.net/softwaredev/article-124a1.html) -------------------------------------------------------------- perl中的多线程模块 5.8以后的版本,的多线程模块可参看perldoc(http://perldoc.perl.org/threads.html) --------------------------------------------------------------- perl的多线程实例: 涉及语言:Perl 所用模块:threads 模块中的方法: threads-create(),创建一个新线程; threads- join(),收割已经创建的线程; threads-list(threads::all),返回所有已经创建的线程; threads- is_joinable(),返回目标线程是否已经完成,等待join; 其他的在perldoc上了,字母文字好长。就不翻译了。 -------------------------------------------------------- #begin use threads; #声明模块; use warnings;use strict; print localtime(time),"\n"; #输出系统时间; my $j=0; my $thread; while() { last if($j=10);这里控制一下任务数量,共10个; #控制创建的线程数,这里是5,scalar函数返回列表threads-list()元素的个数; while(scalar(threads-list())5) { $j++; #创建一个线程,这个线程其实就是调用(引用)函数“ss” ; #函数‘ss’包含两个参数($j和$j); threads-new(\ss,$j,$j); } foreach $thread(threads-list(threads::all)) { if($thread-is_joinable()) #判断线程是否运行完成; { $thread-join(); #输出中间结果; print scalar(threads-list()),"\t$j\t",localtime(time),"\n"; } } } #join掉剩下的线程(因为在while中当j=10时,还有4个线程正在运行,但是此时程序将退出while循,所以在这里需要额外程序join掉剩下的4个线程) foreach $thread(threads-list(threads::all)) { $thread-join();print scalar(threads-list()),"\t$j\t",localtime(time),"\n"; } #输出程序结束的时间,和程序开始运行时间比较,看程序运行性能; print localtime(time),"\n"; #下面就是每个线程引用的函数; sub ss() { my ($t,$s)=@_; sleep($t); #sleep函数,睡觉;以秒为单位; print "$s\t"; } ---------------------------------------------------- 结果: 第一列表示程序已经完成的任务数,第二列表示正在运行的线程数-1(join掉一个了),第三列表示在收割掉一个线程后新添加的任务,最后一列表示完成一个线程时的系统时间。 ------------------------------------------------------------ 多线程运行性能 如果单独运行这10个任务,所需要的时间为:1+2+3+4++10=55s; 采用多线程运行(5个)的话,需要的时间为:54-39=16s; ------------------------------------------------------------- 运行过程 简要描述一下程序运行过程,以便更深入理解多线程的概念,呵呵 程序共要运行10个任务,第一个任务的作用是暂停程序1s(sleep(1));第二个任务是暂停程序2s(sleep(2));以此类推,第十个任务是暂停程序10s; 时间(s) 任务 0 1,2,3,4,5(程序初始,5个线程同时运行,需要时间最长的是线程5(5s)) 1 2,3,4,5,6(经过1s后,第一个任务已经完成,被join掉,同时添加新任务6) 2 3,4,5,6,7(同上) 3 4,5,6,7,8 4 5,6,7,8,9 5 6,7,8,9,10 7-end join所有剩下的线程(所有任务都已经添加,程序中while循环退出) 方法$thread-is_joinable()的作用 前面已经说了,这个方法是用来判断线程是否已经运行完成,处于等待join的状态。 当需要处理多个任务,但这些任务完成需要的时间又不一样时,这个方法就显得特别重要。 还是以上面的程序为例。程序初始运行时创建5个线程。第一个线程所需时间最短,为1s。第五个线程所需时间最长5s。如果不适用$thread-is_joinable()而直接join这五个线程的话,如下: foreach $thread(threads-list(threads::all)) { $thread-join(); } 结果是:主程序处于等待状态。在1s后,第一个线程被join,主程序依然处于等待,2s后第二个线程被join,主程序等待。知道5s后第五个线程被join,主程序通畅,重新创建下一组线程(5个)。显然这个过程不能最大话利用CPU的资源。当第一个线程被join后,虽然程序中只有4个线程在运行,但是由于主程序处于等待状态,新的线程不会被创建。 最佳的方法就是判断线程是否可以被join。如上面的程序所写的。这样可以保证程序运行过程中始终是5个线程,最大化的利用CPU资源。 ------------------------------------------------------- 实例 说了这么多,多线程在生物信息中到底可以怎么来运用,下面给一个简单的实例。从KEGG数据库(http://www.genome.jp/kegg/)上搜索同源序列。 所需文件:seqname.txt(用于存放需要搜索的序列KEGG名称); 源码: use strict;use warnings;use threads; use SOAP::Lite; use Cwd; my $path=getcwd; my $wsdl = 'http://soap.genome.jp/KEGG.wsdl'; my $serv = SOAP::Lite-service($wsdl); open(F,"K00006.txt"); my @names=F; chomp @names; close(F); my $i=0; my $thread; print localtime(time); while($iscalar(@names)) { while(scalar(threads-list())10) { threads-new(\orgfile,$names ); $i++; } foreach $thread(threads-list(threads::all)) { if($thread-is_joinable()) { $thread-join(); } } } foreach $thread(threads-list(threads::all)) { $thread-join(); } print localtime(time); sub orgfile { my($seq)=@_; my $offset = 1; my $limit = 100; my $top5 = $serv-get_best_neighbors_by_gene($seq, $offset,$limit); $seq=~s/://; open(F,"$seq.txt"); foreach my $hit (@{$top5}) { print F "$hit-{genes_id1}\t$hit-{genes_id2}\t$hit-{sw_score}\n"; } close(F); print "$seq\n"; }
3045 次阅读|0 个评论
kmeans算法修正版1.0(多线程分组处理数据)未经允许禁止引用
neohua 2013-8-1 14:01
/* * 该程序未经允许禁止引用 * 该程序开启了10条线程,每条线程处理15000条数据 * 该程序使用了多线程处理分类数据 * 该程序在编写时遇到不少问题 * 主要解决线程安全问题 * 程序可以继续改进,将读入数据的同时处理数据的方法同时写入线程,提高效率 * 同时对每次读入的缓存做限制,已解决内存控制问题 * */ import java.awt.BufferCapabilities; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.Buffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Vector; import java.util.Date; import java.util.Random; import java.util.Scanner; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Kmeans { public Kmeans() { // TODO 自动生成的构造函数存根 } /** * @param args */ public static void main(String crita; // 存放每次运行的满意度 String temp=""; BufferedReader reader=null; String ; flff fl; public int getK() { return k; } public void setK(int k) { this.k = k; } public double getCenter() { return center; } public void setCenter(double center) { this.center = center; } public double crita) { this.crita = crita; } public jvff() { // TODO 自动生成的构造函数存根 System.out.println("聚类开始时间"+new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())); try { reader = new BufferedReader(new FileReader("g:/all22_3_26_01.csv"));//g:/all22_3_26_01.csv//g:/testbx.csv } catch (FileNotFoundException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(new SimpleDateFormat("聚类到内存时间"+"yyyyMMddHHmmssSSS").format(new Date())); data=new String )); } trueData.add(tem); tem=new VectorDouble(); } } catch (IOException e) { e.printStackTrace(); } try { reader.close(); } catch (IOException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } linenumber=trueData.size(); System.out.println(linenumber); //输出行数 System.out.println(new SimpleDateFormat("聚类读取文件时间"+"yyyyMMddHHmmssSSS").format(new Date())); Scanner s=new Scanner(System.in); System.out.println("输入分类数"); k = s.nextInt(); System.out.println(new SimpleDateFormat("聚类算法开始时间"+"yyyyMMddHHmmssSSS").format(new Date())); center = new double ; crita =new double ; tt=linenumber/k; for (int i=0;ik;i++) { Random random = new Random(System.currentTimeMillis()); int ret =random.nextInt(tt)+i*tt; //将数据分为k部分,随机从每个部分抽一行记录存放到center数组中 for(int j=0;j2;j++){ center = (double)trueData.get(ret).get(j); } System.out.println(center +","+center ); //输出初始质心 } for(int i=0;ilinenumber;i++){ min=new int ; //计算满意度 for(int j=0;jk;j++){ //用欧几里得距离公式计算满意度 crita =java.lang.StrictMath.pow(((java.lang.StrictMath.pow(((double)trueData.get(i).get(0)-center ),(double)k))+(java.lang.StrictMath.pow(((double)trueData.get(i).get(1)-center ),(double)k))),(1.0/k)); //System.out.println(crita );//测试置信度 } //比较满意度,求最小值 for(int j=1;jk;j++){ if(crita crita ]){ min =j; } } //更新质心 //System.out.println("更新前"+(double)trueData.get(i).get(0)+","+(double)trueData.get(i).get(1)); center ] =((double)trueData.get(i).get(0)+center ] )/2.0; center ] =((double)trueData.get(i).get(1)+center ] )/2.0; //System.out.println("更新后"+center +","+center );//测试 //System.out.println("第"+i+"行数据分类到:"+min ); } System.out.println(new SimpleDateFormat("聚类算法结束时间"+"yyyyMMddHHmmssSSS").format(new Date())); System.out.println("*****************聚类最终结果*************************"); for(int i=0;ik;i++){ System.out.println(center +","+center ); } } } class flff implements Runnable { double ; al=new ArrayList(); Scanner s1=new Scanner(System.in); System.out.println("输入测试数据"); String file = s1.next(); System.out.println(new SimpleDateFormat("分类开始时间"+"yyyyMMddHHmmssSSS").format(new Date())); System.out.println(); try { reader1 = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e1) { // TODO 自动生成的 catch 块 e1.printStackTrace(); } System.out.println(new SimpleDateFormat("分类数据读入内存时间"+"yyyyMMddHHmmssSSS").format(new Date())); String temp1=""; String {}; //Double trueData=new Double ; trueData1=new Vector(); VectorDouble tem1=new Vector(); linenumber1=0; try { while((temp1=reader1.readLine())!=null) { data1=temp1.split(","); for(int i=0; idata1.length; i++) { tem1.add(Double.valueOf(data1 )); } trueData1.add(tem1); tem1=new VectorDouble(); linenumber1++; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { reader1.close(); } catch (IOException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(trueData1.size()); //输出行数 /* * 单线程没问题0线程执行1384次 * 这里想改成多线程 * 多线程出现每个线程全部执行1384次(问题已解决在run方法中循环变量i必须考虑线程安全,和初始问题) */ ExecutorService exec=Executors.newCachedThreadPool(); for(int i=0;i10;i++){ // exec.execute(this); t=new Thread(this); //System.out.println(Thread.currentThread().getName()); t.start(); } //exec.shutdown(); } @Override public void run() { //进行分类 int xcnum=0; while(true) { synchronized (this) { if(xcnumxcsize){ //lock.lock(); //计算满意度xcnumxcsize if(iaclinenumber1){ //System.out.println(Thread.currentThread().getName()+"i=="+iac); for(int j=0;jjl.getK();j++){ //用欧几里得距离公式计算满意度 crita =java.lang.StrictMath.pow(((java.lang.StrictMath.pow(((double)trueData1.get(iac).get(0)-jl.center ),(double)(jl.getK())))+(java.lang.StrictMath.pow(((double)trueData1.get(iac).get(1)-jl.center ),(double)jl.getK()))),(1.0/jl.getK())); //System.out.println(crita );//测试置信度 } //比较满意度,求最小值 for(int j=1;jjl.getK();j++){ if(crita crita ){ min=j; } } //分类统计 al.add(min); //System.out.println(Thread.currentThread().getName()+"正在处理第"+iac); //System.out.println(classification );//测试 }else break; iac++; xcnum++; //System.out.println("xcnum="+xcnum); }else{ break; } } } //lock.unlock(); //} // for(int i=0;ial.size();i++){ // System.out.println(al.get(i)); // } System.out.println("al.size()"+al.size()); System.out.println("第"+Thread.currentThread().getName()+"结束,时间为:"+(new SimpleDateFormat("分类数据读入内存时间"+"yyyyMMddHHmmssSSS").format(new Date()))); } public int getXcid() { return xcid; } public void setXcid(int xcid) { this.xcid = xcid; } }
个人分类: 数据挖掘|2870 次阅读|0 个评论
kmeans算法3.0(使用多线程处理分类数据)未经许可请勿发表
neohua 2013-8-1 09:13
/* * 该程序耗费不少精力,转载请注明出处,未经许可请勿发表 * 该程序使用了多线程处理分类数据 * 该程序在编写时遇到不少问题 * 该程序由neohua与qinchuanqing共同编写 * 主要解决线程安全问题 * 程序可以继续改进,将读入数据的同时处理数据的方法同时写入线程,提高效率 * 同时对每次读入的缓存做限制,已解决内存控制问题 * */ import java.awt.BufferCapabilities; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.nio.Buffer; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Vector; import java.util.Date; import java.util.Random; import java.util.Scanner; import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Kmeans { public Kmeans() { // TODO 自动生成的构造函数存根 } /** * @param args */ public static void main(String crita; // 存放每次运行的满意度 String temp=""; BufferedReader reader=null; String ; flff fl; public int getK() { return k; } public void setK(int k) { this.k = k; } public double getCenter() { return center; } public void setCenter(double center) { this.center = center; } public double crita) { this.crita = crita; } public jvff() { // TODO 自动生成的构造函数存根 System.out.println("聚类开始时间"+new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date())); try { reader = new BufferedReader(new FileReader("g:/all22_3_26_01.csv"));//g:/all22_3_26_01.csv//g:/testbx.csv } catch (FileNotFoundException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(new SimpleDateFormat("聚类到内存时间"+"yyyyMMddHHmmssSSS").format(new Date())); data=new String )); } trueData.add(tem); tem=new VectorDouble(); } } catch (IOException e) { e.printStackTrace(); } try { reader.close(); } catch (IOException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } linenumber=trueData.size(); System.out.println(linenumber); //输出行数 System.out.println(new SimpleDateFormat("聚类读取文件时间"+"yyyyMMddHHmmssSSS").format(new Date())); Scanner s=new Scanner(System.in); System.out.println("输入分类数"); k = s.nextInt(); System.out.println(new SimpleDateFormat("聚类算法开始时间"+"yyyyMMddHHmmssSSS").format(new Date())); center = new double ; crita =new double ; tt=linenumber/k; for (int i=0;ik;i++) { Random random = new Random(System.currentTimeMillis()); int ret =random.nextInt(tt)+i*tt; //将数据分为k部分,随机从每个部分抽一行记录存放到center数组中 for(int j=0;j2;j++){ center = (double)trueData.get(ret).get(j); } System.out.println(center +","+center ); //输出初始质心 } for(int i=0;ilinenumber;i++){ min=new int ; //计算满意度 for(int j=0;jk;j++){ //用欧几里得距离公式计算满意度 crita =java.lang.StrictMath.pow(((java.lang.StrictMath.pow(((double)trueData.get(i).get(0)-center ),(double)k))+(java.lang.StrictMath.pow(((double)trueData.get(i).get(1)-center ),(double)k))),(1.0/k)); //System.out.println(crita );//测试置信度 } //比较满意度,求最小值 for(int j=1;jk;j++){ if(crita crita ]){ min =j; } } //更新质心 //System.out.println("更新前"+(double)trueData.get(i).get(0)+","+(double)trueData.get(i).get(1)); center ] =((double)trueData.get(i).get(0)+center ] )/2.0; center ] =((double)trueData.get(i).get(1)+center ] )/2.0; //System.out.println("更新后"+center +","+center );//测试 //System.out.println("第"+i+"行数据分类到:"+min ); } System.out.println(new SimpleDateFormat("聚类算法结束时间"+"yyyyMMddHHmmssSSS").format(new Date())); System.out.println("*****************聚类最终结果*************************"); for(int i=0;ik;i++){ System.out.println(center +","+center ); } } } class flff implements Runnable { double ; al=new ArrayList(); Scanner s1=new Scanner(System.in); System.out.println("输入测试数据"); String file = s1.next(); System.out.println(new SimpleDateFormat("分类开始时间"+"yyyyMMddHHmmssSSS").format(new Date())); System.out.println(); try { reader1 = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e1) { // TODO 自动生成的 catch 块 e1.printStackTrace(); } System.out.println(new SimpleDateFormat("分类数据读入内存时间"+"yyyyMMddHHmmssSSS").format(new Date())); String temp1=""; String {}; //Double trueData=new Double ; trueData1=new Vector(); VectorDouble tem1=new Vector(); linenumber1=0; try { while((temp1=reader1.readLine())!=null) { data1=temp1.split(","); for(int i=0; idata1.length; i++) { tem1.add(Double.valueOf(data1 )); } trueData1.add(tem1); tem1=new VectorDouble(); linenumber1++; } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { reader1.close(); } catch (IOException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } System.out.println(trueData1.size()); //输出行数 /* * 单线程没问题0线程执行1384次 * 这里想改成多线程 * 多线程出现每个线程全部执行1384次(问题已解决在run方法中循环变量i必须考虑线程安全,和初始问题) */ ExecutorService exec=Executors.newCachedThreadPool(); for(int i=0;i3;i++){ // exec.execute(this); t=new Thread(this); //System.out.println(Thread.currentThread().getName()); t.start(); } //exec.shutdown(); } @Override public void run() { //进行分类 while(true) { synchronized (this) { //lock.lock(); //计算满意度xcnumxcsize if(iaclinenumber1){ //System.out.println(Thread.currentThread().getName()+"i=="+iac); for(int j=0;jjl.getK();j++){ //用欧几里得距离公式计算满意度 crita =java.lang.StrictMath.pow(((java.lang.StrictMath.pow(((double)trueData1.get(iac).get(0)-jl.center ),(double)(jl.getK())))+(java.lang.StrictMath.pow(((double)trueData1.get(iac).get(1)-jl.center ),(double)jl.getK()))),(1.0/jl.getK())); //System.out.println(crita );//测试置信度 } //比较满意度,求最小值 for(int j=1;jjl.getK();j++){ if(crita crita ){ min=j; } } //分类统计 al.add(min); //System.out.println(classification );//测试 }else break; } iac++; } //lock.unlock(); // } for(int i=0;ial.size();i++){ System.out.println(al.get(i)); } System.out.println("al.size()"+al.size()); System.out.println(new SimpleDateFormat("分类结束时间"+"yyyyMMddHHmmssSSS").format(new Date())); } public int getXcid() { return xcid; } public void setXcid(int xcid) { this.xcid = xcid; } }
个人分类: 数据挖掘|3356 次阅读|0 个评论
坦克大战2.0(实现子弹发射,使用了线程技术(shot,Mypanel))
neohua 2013-7-26 17:24
package com.test2; import java.awt.Color; import java.awt.Graphics; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.Vector; import javax.swing.JFrame; import javax.swing.JPanel; public class MyTankGame2 extends JFrame { Mypanel mp = null; public MyTankGame2() { // TODO 自动生成的构造函数存 mp = new Mypanel(); this.add(mp); Thread t=new Thread(mp); t.start(); // 3,在事件源上注册监听 this.addKeyListener(mp); this.setSize(400, 300); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setVisible(true); } /** * 监听的实现步骤 * 1,实现相应接口(keylistener,mouselistener···) * 2,重写接口方法 * 3,在事件源上注册监听 * 4,事件传递靠事件对象(e.getactioncommand().equals()) */ public static void main(String[] args) { // TODO 自动生成的方法存根 MyTankGame2 mt = new MyTankGame2(); } } // 我的面板 // 1,实现相应接口(keylistener,mouselistener···) // 2,重写接口方法 class Mypanel extends JPanel implements KeyListener ,Runnable{ // 定义我的坦克 Mytank hero = null; //定义敌人坦克 VectorEnemyTank ets=new VectorEnemyTank(); int enemynumber=3; int x = 200; int y = 200; public Mypanel() { hero = new Mytank(x, y); //初始化敌人坦克 for(int i=0;ienemynumber;i++){ EnemyTank et=new EnemyTank((i+1)*50,0); et.setDirect(2); et.setColor(0); ets.add(et); } } // 重写paint public void paint(Graphics g) { super.paint(g); g.fillRect(0, 0, 400, 300); this.drawTank(hero.getX(), hero.getY(), g, this.hero.getDirect(), 1); //画出子弹 if(hero.s!=nullhero.s.isLive==true) { g.draw3DRect(hero.s.x, hero.s.y, 2, 2, false); } //画出敌人坦克 for(int i=0;iets.size();i++){ this.drawTank(ets.get(i).getX(), ets.get(i).getY(), g, ets.get(i).getDirect(), 0); } } // 画出坦克的函数 public void drawTank(int x, int y, Graphics g, int direct, int type) { switch (type) { case 0: g.setColor(Color.cyan); break; case 1: g.setColor(Color.yellow); break; } // 判断方向//坦克方向,0 up 2 down 1 right 3 left switch (direct) { // 向上 case 0: // 画出坦克(封装) g.fill3DRect(x, y, 5, 30, false); g.fill3DRect(x + 15, y, 5, 30, false); g.fill3DRect(x + 5, y + 5, 10, 20, false); g.fillOval(x + 5, y + 10, 10, 10); g.drawLine(x + 9, y + 15, x + 9, y); break; case 3: //向左 g.fill3DRect(x, y, 30, 5, false); g.fill3DRect(x, y+15, 30, 5, false); g.fill3DRect(x+5, y+5, 20, 10, false); g.fillOval(x+10, y+5, 10, 10); g.drawLine(x+15, y+9, x, y+9); break; case 2: // 向下 g.fill3DRect(x, y, 5, 30, false); g.fill3DRect(x + 15, y, 5, 30, false); g.fill3DRect(x + 5, y + 5, 10, 20, false); g.fillOval(x + 5, y + 10, 10, 10); g.drawLine(x + 9, y + 15, x + 9, y+30); break; case 1: //向右 g.fill3DRect(x, y, 30, 5, false); g.fill3DRect(x, y+15, 30, 5, false); g.fill3DRect(x+5, y+5, 20, 10, false); g.fillOval(x+10, y+5, 10, 10); g.drawLine(x+15, y+9, x+30, y+9); break; } } @Override public void keyTyped(KeyEvent e) { // TODO 自动生成的方法存根 } @Override public void keyPressed(KeyEvent e) { // TODO 自动生成的方法存根 if (e.getKeyCode() == KeyEvent.VK_S) { this.hero.setDirect(2); this.hero.moveDown(); } else if (e.getKeyCode() == KeyEvent.VK_W) { this.hero.setDirect(0); this.hero.moveUp(); } else if (e.getKeyCode() == KeyEvent.VK_A) { this.hero.setDirect(3); this.hero.moveLeft(); } else if (e.getKeyCode() == KeyEvent.VK_D) { this.hero.setDirect(1); this.hero.moveRight(); } //开火 if (e.getKeyCode() == KeyEvent.VK_J) { this.hero.shotEnemy(); } this.repaint(); } @Override public void keyReleased(KeyEvent e) { // TODO 自动生成的方法存根 } @Override public void run() { // TODO 自动生成的方法存根 while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } this.repaint(); } } } /**************************************华丽的分割线*****************************************************/ package com.test2; class Tank { // 坦克横纵坐标 int x = 0; int y = 0; //设置速度 int speed=1; int color; public int getColor() { return color; } public void setColor(int color) { this.color = color; } public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } //坦克方向,0 up 2 down 1 right 3 left int direct=0; public int getDirect() { return direct; } public void setDirect(int direct) { this.direct = direct; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } public Tank(int x, int y) { this.x = x; this.y = y; } } class Mytank extends Tank { //子弹 Shot s=null; //向上 public void moveUp() { y-=speed; } public void moveDown() { y+=speed; } public void moveLeft() { x-=speed; } public void moveRight() { x+=speed; } public Mytank(int x, int y) { super(x, y); // TODO 自动生成的构造函数存根 } public void shotEnemy() { switch (this.direct) { case 0: s=new Shot(x+10, y,0); break; case 1: s=new Shot(x+30, y+10,1); break; case 2: s=new Shot(x+10, y+30,2); break; case 3: s=new Shot(x, y+10,3); break; } Thread t=new Thread(s); t.start(); } } //敌人坦克 class EnemyTank extends Tank { public EnemyTank(int x, int y) { super(x, y); // TODO 自动生成的构造函数存根 } } //子弹 class Shot implements Runnable { int x ; int y ; int direct; int speed=2; boolean isLive=true; public Shot(int x,int y,int direct) { // TODO 自动生成的构造函数存根 this.x=x; this.y=y; this.direct=direct; } @Override public void run() { // TODO 自动生成的方法存根 while(true){ try { Thread.sleep(50); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } switch (direct) { case 0: y-=speed; break; case 1: x+=speed; break; case 2: y+=speed; break; case 3: x-=speed; break; } System.err.println("子弹坐标"+x+","+y); //判断子弹是否碰到边缘 if (x0||x400||y0||y300) { this.isLive=false; break; } } } }
个人分类: java|2771 次阅读|0 个评论
多线程编程
grapeson 2012-12-8 22:53
53 次阅读|0 个评论
[转载]浅谈多核CPU、多线程与并行计算
ywk1573 2012-6-3 14:22
0.前言 最近发觉自己博客转帖的太多,于是决定自己写一个原创的。笔者用过MPI和C#线程池,参加过比赛,有所感受,将近一年来,对多线程编程兴趣一直不减,一直有所关注,决定写篇文章,算是对知识的总结吧。有说的不对的地方,欢迎各位大哥们指正:) 1.CPU发展趋势 核心数目依旧会越来越多,依据摩尔定律,由于单个核心性能提升有着严重的瓶颈问题,普通的桌面PC有望在2017年末2018年初达到24核心(或者16核32线程),我们如何来面对这突如其来的核心数目的增加?编程也要与时俱进。笔者斗胆预测,CPU各个核心之间的片内总线将会采用4路组相连:),因为全相连太过复杂,单总线又不够给力。而且应该是非对称多核处理器,可能其中会混杂几个DSP处理器或流处理器。 2.多线程与并行计算的区别 (1)多线程的作用不只是用作并行计算,他还有很多很有益的作用。 还在单核时代,多线程就有很广泛的应用,这时候多线程大多用于降低阻塞 (意思是类似于 while(1) { if(flag==1) break; sleep(1); } 这样的代码) 带来的CPU资源闲置,注意这里没有浪费CPU资源,去掉sleep(1)就是纯浪费了。 阻塞在什么时候发生呢?一般是等待IO操作(磁盘,数据库,网络等等)。此时如果单线程,CPU会干转不干实事(与本程序无关的事情都算不干实事,因为执行其他程序对我来说没意义),效率低下(针对这个程序而言),例如一个IO操作要耗时10毫秒,CPU就会被阻塞接近10毫秒,这是何等的浪费啊!要知道CPU是数着纳秒过日子的。 所以这种耗时的IO操作就用一个线程Thread去代为执行,创建这个线程的函数(代码)部分不会被IO操作阻塞,继续干这个程序中其他的事情,而不是干等待(或者去执行其他程序)。 同样在这个单核时代,多线程的这个消除阻塞的作用还可以叫做“并发”,这和并行是有着本质的不同的。并发是“伪并行”,看似并行,而实际上还是一个CPU在执行一切事物,只是切换的太快,我们没法察觉罢了。例如基于UI的程序(俗话说就是图形界面),如果你点一个按钮触发的事件需要执行10秒钟,那么这个程序就会假死,因为程序在忙着执行,没空搭理用户的其他操作;而如果你把这个按钮触发的函数赋给一个线程,然后启动线程去执行,那么程序就不会假死,继续相应用户的其他操作。但是,随之而来的就是线程的互斥和同步、死锁等问题,详细见 有关文献 。 现在是多核时代了,这种线程的互斥和同步问题是更加严峻的,单核时代大都算并发,多核时代真的就大为不同,为什么呢?具体细节请参考 有关文献 。我这里简单解释一下,以前volatile型变量的使用可以解决大部分问题,例如多个线程共同访问一个Flag标志位,如果是单核并发,基本不会出问题(P.S.在什么情况下会出问题呢?Flag有多个,或者是一个数组,这时候只能通过逻辑手段搞定这个问题了,多来几次空转无所谓,别出致命问题就行),因为CPU只有一个,同时访问这个标志位的只能有一个线程,而多核情况下就不太一样了,所以仅仅volatile不太能解决问题,这就要用到具体语言,具体环境中的“信号量”了,Mutex,Monitor,Lock等等,这些类都操作了硬件上的“关中断”,达到“原语”效果,对临界区的访问不被打断的效果,具体就不解释了,读者可以看看《现代操作系统》。 (2)并行计算还可以通过其他手段来获得,而多线程只是其中之一。 其他手段包括:多进程(这又包括共享存储区的和分布式多机,以及混合式的),指令级并行。 ILP(指令级并行) ,x86架构里叫 SMT(同时多线程) ,在MIPS架构里与之对应的是 super scalar(超标量) 和乱序执行,二者有区别,但共同点都是可以达到指令级并行,这是用户没法控制的,不属于编程范围,只能做些有限的优化,而这有限的优化可能只属于编译器管辖的范畴,用户能做的甚少。 (3)典型的适于并行计算的语言 Erlang 和 MPI :这两个前者是语言,后者是C++和Fortran的扩展库,效果是一样的,利用多进程实现并行计算,Erlang是共享存储区的,MPI是混合型的。 C#.NET4.0 :新版本4.0可以用少量代码实现并行For循环,之前版本需要用很繁琐的代码才能实现同样功能。这是利用了多线程实现并行计算。Java和C#3.5都有线程池(ThreadPool),也是不错的很好用的多线程管理类,可以方便高效的使用多线程。 CUDA,还是个初生牛犊,有很大的发展潜力,只不过就目前其应用领域很有限。其目前只能使用C语言,而且还不是C99,比较低级,不能使用函数指针。个人感觉这由于硬件上天生的局限性(平均每个核心可用内存小,与系统内存通讯时间长),只适用于做科学计算,静态图像处理,视频编码解码,其他领域,还不如高端CPU。等以后GPU有操作系统了,能充分调度GPU资源了,GPU就可以当大神了。游戏中的物理加速,实际上多核CPU也能很好的做到。 其他语言。。。恩。。留作将来讨论。 3.线程越多越好吗?什么时候才有必要用多线程? 线程必然不是越多越好,线程切换也是要开销的,当你增加一个线程的时候,增加的额外开销要小于该线程能够消除的阻塞时间,这才叫物有所值。 Linux自从2.6内核开始,就会把不同的线程交给不同的核心去处理。Windows也从NT.4.0开始支持这一特性。 什么时候该使用多线程呢?这要分四种情况讨论: a.多核CPU——计算密集型任务。此时要尽量使用多线程,可以提高任务执行效率,例如加密解密,数据压缩解压缩(视频、音频、普通数据),否则只能使一个核心满载,而其他核心闲置。 b.单核CPU——计算密集型任务。此时的任务已经把CPU资源100%消耗了,就没必要也不可能使用多线程来提高计算效率了;相反,如果要做人机交互,最好还是要用多线程,避免用户没法对计算机进行操作。 c.单核CPU——IO密集型任务,使用多线程还是为了人机交互方便, d.多核CPU——IO密集型任务,这就更不用说了,跟单核时候原因一样。 4.程序员需要掌握的技巧/技术 (1)减少串行化的代码用以提高效率。这是废话。 (2)单一的共享数据分布化:把一个数据复制很多份,让不同线程可以同时访问。 (3)负载均衡,分为静态的和动态的两种。具体的参见 有关文献 。 原文为: http://blog.csdn.net/delacroix_xu/article/details/5928121
个人分类: 并行计算|4345 次阅读|0 个评论
ISE Windows系统多线程设置
zhangjunjie 2012-5-2 10:57
ISE Windows系统多线程设置
map多线程设置
个人分类: Xilinx|2976 次阅读|0 个评论
[转载]QT多线程
huozhenhua 2011-11-24 14:35
1.多线程系列文章 之 线程的初体验 我总觉得学一个东西时候,我们应该保留最简单本质的东西,接着做个demo的程序先跑起来,再慢慢学入进去.这样一来难度也不会那么大,有时我们也需要自我陶醉一翻,有点成就感了,才能有动力下去。 这是多线程系列文章的第一篇,欢迎你进入QT多线程的世界. 每个操作系统平台和语言都有自己创建线程的办法,可我们精力有限,不可能一个个全都知道和去学,一般工作也就是掌握一两个.幸好现在有QT,免去平台差异的东西。qt大概从3.x后就开始有了线程支持,到了4.x的时候更是加入了更多高级功能,比如跨线程signal/slot,线程消息循环等,这也为我们做线程通信提供了很好的环境. 在QT要使用线程的话,必须从QThread派生一个类,这是规定,哪怕你的线程做的事情简单到1+1都得这么做.。这次我们先做个简单的程序,让一个多线程程序跑起来,其他的留到以后再说. 使用一个线程的主要的步骤如下: 1.定义一个线程类,继承自QThread 2.重载protected Run方法 3.实例化类,用QThread 的Start方法 ,开始跑起线程 这样最终创建的线程将和主线程就并发运行了 CTHREAD.H view source print ? 01 #ifndef CTHREAD_H 02 03 #define CTHREAD_H 04 05 #include QThread 06 07 class CThread : public QThread 08 09 { 10 11 public : 12 13 CThread(); 14 15 protected : 16 17 void run(); 18 19 }; 20 21 #endif // CTHREAD_H CTHREAD.cpp view source print ? 01 #include "CThread.h" 02 03 #include QDebug 04 05 CThread::CThread():QThread() 06 07 { 08 09 } 10 11 void CThread::run() 12 13 { 14 15 for ( int i=1;i=10000;i++) 16 17 qDebug()i; 18 19 } 赶紧跑起来运行吧 if ($ != jQuery) { $ = jQuery.noConflict(); } var isLogined = false; var cb_blogId = 73972; var cb_entryId = 1839615; var cb_blogApp = "foxhengxing"; var cb_blogUserGuid = "0c3ed9f3-089f-df11-ba8f-001cf0cd104b"; var cb_entryCreatedDate = '2010/9/30 17:01:00'; 2.多线程系列文章 之 线程的控制 目录 2.1 线程休眠 2.2 线程唤醒 2.3 线程关闭 上回讲到 线程的初步使用 ,写了个线程的创建到运行的过程,可这还没完,线程创建完了之后必须要对其进行限制和控制,我们就是线程的监护人,不能说任由它自由,得对它进行合理约束。接下来我们讲线程的控制部分, 2.1 线程休眠 想象一下一种情形,日常用的电脑,如果我们需要离开一段时间,那么可能会将它暂时休眠一下,为了节约用电,也响应一下环境保护,别忘了现在都讲低炭生活。那么线程其实也一样,如果一个线程暂时不需要用到,我们可以先让它睡会,其目的也是为了让它暂时不要占用资源,主要是一个 cpu 时间片的占用问题。 对于线程的休眠,只要简单调用 Qthread 的 sleep , msleep 或者 usleep 方法就可以了,注意这三个方法都是 Static Protected 的,这意味着你只能在继承类里做这个动作,它们差别仅是时间单位不同而已。 程序方面我们尽量简单点,能看清本质就可以了 , 在 Qthread 派生类的 Run 方法里面用下 void CThread::run() { for(int i=1;i=10000;i++) { qDebug()i; sleep(1); //请不要那么快,睡一下再往下执行 } } 2.2 线程唤醒 既然有线程的休眠,那就有唤醒。如果你已经和线程说 Sleep 10 秒吧,突然人家睡到一半的时候,你又改变主意想让它醒过来,这里我要抱歉的说声是没办法的 , 它就像猪一样,没到时间是不会醒的。比较合适的方案就是线程同步能够解决这样的问题,这个放到 下一篇 线程的同步 的时候再说 . 只要记住 sleep 是强制休眠就可以,但现在没办法提供强制唤醒的办法 . 2.3 线程关闭 如果一个线程运行完了它会自己结束自己的生命。可很多情况不是这么简单,一个线程跑到中间的时候由于某种特殊原因,就想它中止。 2.3.1 线程中止方式 中止有两种方式 强制中止和 优雅中止,这用词可能有点不恰当,先这么说着。在说明这两种方式之前,有必要详细说一下线程关闭的时候它到底干了什么。 线程关闭的时候, OS 会移除这个线程,这部分对我们是透明的,详细的说明还得参阅操作系统的有关书籍,接着线程中分配的堆栈信息将一并清除,但是如果是堆上分配的信息,得由你负责自己清除,因为堆是由进程持有的,它的生命周期和线程没关系。 2.3.1 .1 强制中止: 简单的调用 Qthread 的方法 terminate 就可以进行强制中止,可这将会带来很多灾难性的后果。最为严重的就是一个堆内存泄露的问题,线程强制被中止,根本没法来得及做清理工作,即使你的线程 中有执行到最后清理堆内存,可它没来得及执行 比如以下一段代码 void CThread::run() { int *c = new int; for(int i=1;i=10000;i++) { qDebug()i; } //clean delete c; } } 想象一下线程还没执行 到 delete c ; 的时候你就发出了 terminate, 不幸的事就发生了,由此得出结论我们应该尽最大限度避免去使用。 2.3.1 .2 优雅的中止: 那么怎么优雅的关闭线程呢?我们应该通知线程,让线程自己去接手关闭,各自关注自己所需的事,就都能做得更好,一手抓就会带来很多问题 那么怎么通知线程呢? 一般会采用以下的步骤 1 .在 Qthread 中派生类 定义一个公用方法出来 供中止时调用,比如 stop() 2 .调用者 直接 调用 stop 方法 3 .派生类 stop 方法 ,设置 中止标志 , 一般就是 bool 成员 4 . run 方法 运行的时候 , 检查 bool 成员,判断是否需要退出进程,最后做清理工作 //CThread.h #ifndef CTHREAD_H #define CTHREAD_H #include QThread class CThread : public QThread { public: CThread(); ~CThread(); void stop(); protected: void run(); private: bool mStop; }; #endif // CTHREAD_H //CThread.cpp #include QDebug #include "CThread.h" CThread::CThread():QThread(), mStop(false) { } CThread::~CThread() { stop(); } void CThread::run() { int *c = new int; for(int i=1;i=10000;i++) { if (mStop) // determine to exit the loop { break; } qDebug()i; sleep(1); } //clean up delete c; } void CThread::stop() { mStop = true; wait(); } 3.多线程系列文章 之 线程的同步 目录 2.1 简介 2.2 Qmutex 应用 2.3 QWaitCondition 应用 接下来继续说线程同步的问题,当 n 个线程在一起运行时,为了控制其先后运行顺序,或者 对共享数据的访问 ,这就需要用到线程的功能,这仅是我能想到的两个情形 . QT 提供 了 QMutex, QReadWriteLock, QSemaphore QWaitCondition 用以提供线程同步 . 这里就选用 QMutex,QWaitCondition 结合一个 “下载工具” 例子 分别讲解说明,其他的以后需用到再说. 如果一篇文章全部讲完,实在够呛,能力有限 2.1 Qmutex 应用 首先我们将使用 QMutex来实现 下载工具 中的 “ 下载 / 暂停 / 停止”三种下载状态切换,以下简称为状态切换。 简单说一下 Qmutex ,该类是用来同步线程最基础的类。一般通过调用 lock /unlock 方法.使 用 Qmutex 就是像检测一个标志一样,如果不是 true 就继续等待,只不过这个等待的过程不会占用 cpu 时间片。这和循环检测每个标志位有明显的不同。 使用QMutext 需要注意的两点是: 1.在同一线程中, Qmutex 不能重复lock或unlock,换句话说就是lock/unlock 只能成对出现。幸好的是如果你不小心这么做的时候,debug时有所提示 . 2.线程lock后,只能在同一个线程中进行unlock,例如,A线程lock后,如果企图在B线程直接unlock 来释放,这将会引发错误。 上回讲到 线程的控制 提到 sleep 是无法休眠到一半然后唤醒的 . 使用Qmutex 可以改变 sleep完全没有回应的局面。在这里我们需要用一个变量做开关,使用Qmutex 做同步访问以达到一种唤醒的效果,也就是我们要做的状态切换了,只不过说法不同。 想象一下该下载工具,为了ui的响应,一般运行时有两个线程 . 一个是 main 线程用于 gui ,另一个是 worker 线程,用于下载数据,用户随时决定要进行状态切换. 下面是关键的代码部分 #include QThread #include QMutex class CThread : public QThread { public: CThread(); ~CThread(); public: enum E_downloadState{ DS_INIT, DS_START, DS_PAUSE, DS_STOP}; void startDownload(); void pauseDownload(); void stopDownload(); E_downloadState downState() const; protect: void run(); private: QMutex mDownloadMutex; E_downloadState mState; //用于mutex的变量 void setDownloadMutex(bool lock); void setDownState( E_downloadState state); }; 画个示意图 开始下载 : 暂停下载 中止下载 在这里为了看清整个控制过程,我用 qDebug() 模拟出下载工具的状态切换
个人分类: C/C++|4324 次阅读|0 个评论
[转载]VC中利用多线程技术实现线程之间的通信
malin 2011-8-15 10:25
http://tech.sina.com.cn/s/2006-02-06/1331833818.shtml VC中利用多线程技术实现线程之间的通信.pdf   作者: 刘涛   当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。      一、 实现方法   1、理解线程   要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。   一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。   线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。   2、线程的管理和操作   (一)线程的启动   创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。   对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。   (二)线程的优先级   以下的CwinThread类的成员函数用于线程优先级的操作: int GetThreadPriority(); BOOL SetThradPriority()(int nPriority);   上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。   (三)线程的悬挂和恢复   CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。   (四)结束线程   终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码: //////////////////////////////////////////////////////////////// //////CtestView message handlers /////Set to True to end thread Bool bend=FALSE;//定义的全局变量,用于控制线程的运行; //The Thread Function; UINT ThreadFunction(LPVOID pParam)//线程函数 {  while(!bend)  {   Beep(100,100);   Sleep(1000);  }  return 0; } ///////////////////////////////////////////////////////////// CwinThread *pThread; HWND hWnd; Void CtestView::OninitialUpdate() {  hWnd=GetSafeHwnd();  pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程  pThread-m_bAutoDelete=FALSE;//线程为手动删除  Cview::OnInitialUpdate(); } //////////////////////////////////////////////////////////////// Void CtestView::OnDestroy() {  bend=TRUE;//改变变量,线程结束  WaitForSingleObject(pThread-m_hThread,INFINITE);//等待线程结束  delete pThread;//删除线程  Cview::OnDestroy(); }   3、线程之间的通信   通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。   (一) 利用用户定义的消息通信   在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束: UINT ThreadFunction(LPVOID pParam) {  while(!bend)  {   Beep(100,100);   Sleep(1000);  }  ::PostMessage(hWnd,WM_USERMSG,0,0);  return 0; } ////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam, LPARAM lParam) LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam) {  AfxMessageBox("Thread ended.");  Retrun 0; }   上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。   (二)用事件对象实现通信   在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下: //////////////////////////////////////////////////////////////////// Cevent threadStart ,threadEnd; UINT ThreadFunction(LPVOID pParam) {  ::WaitForSingleObject(threadStart.m_hObject,INFINITE);  AfxMessageBox("Thread start.");  while(!bend)  {   Beep(100,100);   Sleep(1000);   Int result=::WaitforSingleObject(threadEnd.m_hObject,0);   //等待threadEnd事件有信号,无信号时线程在这里悬停   If(result==Wait_OBJECT_0)    Bend=TRUE;  }  ::PostMessage(hWnd,WM_USERMSG,0,0);  return 0; } ///////////////////////////////////////////////////////////// Void CtestView::OninitialUpdate() {  hWnd=GetSafeHwnd();  threadStart.SetEvent();//threadStart事件有信号  pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程  pThread-m_bAutoDelete=FALSE;  Cview::OnInitialUpdate(); } //////////////////////////////////////////////////////////////// Void CtestView::OnDestroy() {  threadEnd.SetEvent();  WaitForSingleObject(pThread-m_hThread,INFINITE);  delete pThread;  Cview::OnDestroy(); }   运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"。   4、线程之间的同步   前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。   (一) 临界区   临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array ,destarray ; CCriticalSection Section; UINT WriteThread(LPVOID param) {  Section.Lock();  for(int x=0;x10;x++)   array =x;  Section.Unlock(); } UINT ReadThread(LPVOID param) {  Section.Lock();  For(int x=0;x10;x++)   Destarray =array ;   Section.Unlock(); }   上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。   (二)互斥   互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下: #include "afxmt.h" int array ,destarray ; CMutex Section; UINT WriteThread(LPVOID param) {  CsingleLock singlelock;  singlelock (Section);  singlelock.Lock();  for(int x=0;x10;x++)   array =x;  singlelock.Unlock(); } UINT ReadThread(LPVOID param) {  CsingleLock singlelock;  singlelock (Section);  singlelock.Lock();  For(int x=0;x10;x++)   Destarray =array ;   singlelock.Unlock(); }   (三)信号量   信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。 ///////////////////////////////////////////////////////////////////////// Csemaphore *semaphore; Semaphore=new Csemaphore(2,2); HWND hWnd=GetSafeHwnd(); AfxBeginThread(threadProc1,hWnd); AfxBeginThread(threadProc2,hWnd); AfxBeginThread(threadProc3,hWnd); UINT ThreadProc1(LPVOID param) {  CsingleLock singelLock(semaphore);  singleLock.Lock();  Sleep(10000);  ::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);  return 0; } UINT ThreadProc2(LPVOID param) {  CSingleLock singelLock(semaphore);  singleLock.Lock();  Sleep(10000);  ::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);  return 0; } UINT ThreadProc3(LPVOID param) {  CsingleLock singelLock(semaphore);  singleLock.Lock();  Sleep(10000);  ::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);  return 0; }    二、 编程步骤   1、 启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为"sequence"   2、 输入要排续的数字,声明四个子线程;   3、 输入代码,编译运行程序。    三、 程序代码 ////////////////////////////////////////////////////////////////////////////////////// // sequence.cpp : Defines the entry point for the console application. /* 主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN; 线程建立函数: HANDLE CreateThread(  LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;  DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;  LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针;  LPVOID lpParameter, // 传递给线程函数的参数,可以保存一个指针值;  DWORD dwCreationFlags, // 线程建立是的初始标记,运行或挂起;  LPDWORD lpThreadId // 指向接收线程号的DWORD变量; ); 对临界资源控制的多线程控制的信号函数: HANDLE CreateEvent(  LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;  BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。若为 FALSE则在WaitForSingleObject  //后,系统自动清除事件信号;  BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号;  LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH;  //如果遇到同名的其他信号量函数就会失败,如果遇  //到同类信号同名也要注意变化; ); HANDLE CreateMutex(  LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性结构指针,可为NULL  BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,  //这样其他线程就不能获得此互斥量也就无法进入由  //该互斥量控制的临界区。FALSE表示不占有该互斥量  LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH如果  //遇到同名的其他信号量函数就会失败,  //如果遇到同类信号同名也要注意变化; ); //初始化临界区信号,使用前必须先初始化 VOID InitializeCriticalSection(  LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针 ); //阻塞函数 //如果等待的信号量不可用,那么线程就会挂起,直到信号可用 //线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后 //Event信号会变得无信号,Mutex、Semaphore等也会变。 DWORD WaitForSingleObject(  HANDLE hHandle, // 等待对象的句柄  DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待 ); //如果要等待多个信号可以使用WaitForMutipleObject函数 */ #include "stdafx.h" #include "stdlib.h" #include "memory.h" HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完 /* 下面使用了三种控制方法,你可以注释其中两种,使用其中一种。 注意修改时要连带修改临界区PrintResult里的相应控制语句 */ HANDLE evtPrint; //事件信号,标记事件是否已发生 //CRITICAL_SECTION csPrint; //临界区 //HANDLE mtxPrint; //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号 static long ThreadCompleted = 0; /*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)进行加减操作*/ //下面的结构是用于传送排序的数据给各个排序子线程 struct MySafeArray {  long* data;  int iLength; }; //打印每一个线程的排序结果 void PrintResult(long* Array, int iLength, const char* HeadStr = "sort"); //排序函数 unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序 unsigned long __stdcall SelectSort(void* theArray); //选择排序 unsigned long __stdcall HeapSort(void* theArray); //堆排序 unsigned long __stdcall InsertSort(void* theArray); //插入排序 /*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread 建立一个线程。 (1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责 栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall (2)返回值必须是unsigned long (3)参数必须是一个32位值,如一个指针值或long类型 (4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中): static unsigned long _stdcall MyThreadFun(void* pParam); handleRet = CreateThread(NULL, 0, CThreadTestDlg::MyThreadFun, NULL, 0, ThreadID); 之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/ int QuickSort(long* Array, int iLow, int iHigh); //快速排序 int main(int argc, char* argv = {123,34,546,754,34,74,3,56};  int iDataLen = 8;  //为了对各个子线程分别对原始数据进行排序和保存排序结果  //分别分配内存对data数组的数据进行复制  long *data1, *data2, *data3, *data4, *data5;  MySafeArray StructData1, StructData2, StructData3, StructData4;  data1 = new long ;  memcpy(data1, data, iDataLen 2); //把data中的数据复制到data1中  //内存复制 memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度  //为4字节,所以复制的字节数为iDataLen 2, 即等于iDataLen*4  StructData1.data = data1;  StructData1.iLength = iDataLen;  data2 = new long ;  memcpy(data2, data, iDataLen 2);  StructData2.data = data2;  StructData2.iLength = iDataLen;  data3 = new long ;  memcpy(data3, data, iDataLen 2);  StructData3.data = data3;  StructData3.iLength = iDataLen;  data4 = new long ;  memcpy(data4, data, iDataLen 2);  StructData4.data = data4;  StructData4.iLength = iDataLen;  data5 = new long ;  memcpy(data5, data, iDataLen 2);  unsigned long TID1, TID2, TID3, TID4;  //对信号量进行初始化  evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");  evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");  //分别建立各个子线程  CreateThread(NULL, 0, BubbleSort, StructData1, NULL, TID1);  CreateThread(NULL, 0, SelectSort, StructData2, NULL, TID2);  CreateThread(NULL, 0, HeapSort, StructData3, NULL, TID3);  CreateThread(NULL, 0, InsertSort, StructData4, NULL, TID4);  //在主线程中执行行快速排序,其他排序在子线程中执行  QuickSort(data5, 0, iDataLen - 1);  PrintResult(data5, iDataLen, "Quick Sort");  WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子线程结束  //所有的子线程结束后,主线程才可以结束  delete data2;  delete data4;  CloseHandle(evtPrint);  return 0; } /* 冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次 循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/ unsigned long __stdcall BubbleSort(void* theArray) {  long* Array = ((MySafeArray*)theArray)-data;  int iLength = ((MySafeArray*)theArray)-iLength;  int i, j=0;  long swap;  for (i = iLength-1; i 0; i--)  {   for(j = 0; j i; j++)   {    if(Array Array ) //前比后大,交换    {     swap = Array ;     Array = Array ;     Array = swap;    }   }  }  PrintResult(Array, iLength, "Bubble Sort"); //向控制台打印排序结果  InterlockedIncrement(ThreadCompleted); //返回前使线程完成数标记加1  if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完  //若都执行完则设置程序结束信号量  return 0; } /*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。*/ unsigned long __stdcall SelectSort(void* theArray) {  long* Array = ((MySafeArray*)theArray)-data;  int iLength = ((MySafeArray*)theArray)-iLength;  long lMin, lSwap;  int i, j, iMinPos;  for(i=0; i iLength-1; i++)  {   lMin = Array ;   iMinPos = i;   for(j=i + 1; j = iLength-1; j++) //从无序的元素中找出最小的元素   {    if(Array lMin)    {     iMinPos = j;     lMin = Array ;    }   }   //把选出的元素交换拼接到有序序列的最后   lSwap = Array ;   Array = Array ;   Array = lSwap;  }  PrintResult(Array, iLength, "Select Sort"); //向控制台打印排序结果  InterlockedIncrement(ThreadCompleted); //返回前使线程完成数标记加1  if(ThreadCompleted == 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完  //若都执行完则设置程序结束信号量  return 0; } /*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/ unsigned long __stdcall HeapSort(void* theArray) {  long* Array = ((MySafeArray*)theArray)-data;  int iLength = ((MySafeArray*)theArray)-iLength;  int i, j, p;  long swap;  for(i=0; iiLength-1; i++)  {   for(j = iLength - 1; ji; j--) //从最后倒数上去比较字节点和父节点   {    p = (j - i - 1)/2 + i; //计算父节点数组下标    //注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减    if(Array Array ) //如果父节点数值大则交换父节点和字节点    {     swap = Array ;     Array = Array ;     Array = swap;    }   }  }  PrintResult(Array, iLength, "Heap Sort"); //向控制台打印排序结果  InterlockedIncrement(ThreadCompleted); //返回前使线程完成数标记加1  if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完  //若都执行完则设置程序结束信号量  return 0; } /*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/ unsigned long __stdcall InsertSort(void* theArray) {  long* Array = ((MySafeArray*)theArray)-data;  int iLength = ((MySafeArray*)theArray)-iLength;  int i=1, j=0;  long temp;  for(i=1; iiLength; i++)  {   temp = Array ; //取出序列后面无序数据的第一个元素值   for(j=i; j0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置   {    if(Array temp) //如果该元素比插入值大则后移     Array = Array ;    else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置     break;   }   Array = temp;  }  PrintResult(Array, iLength, "Insert Sort"); //向控制台打印排序结果  InterlockedIncrement(ThreadCompleted); //返回前使线程完成数标记加1  if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完   //若都执行完则设置程序结束信号量  return 0; } /*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右 边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归 建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。 这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/ int QuickSort(long* Array, int iLow, int iHigh) {  if(iLow = iHigh) return 1; //递归结束条件  long pivot = Array ;  int iLowSaved = iLow, iHighSaved = iHigh; //保未改变的iLow,iHigh值保存起来  while (iLow iHigh)  {   while (Array = pivot iHigh iLow) //寻找比支点大的元素    iHigh -- ;   Array = Array ; //把找到的元素放置到空置的位置   while (Array pivot iLow iHigh) //寻找比支点小的元素    iLow ++ ;   Array = Array ; //把找到的元素放置到空置的位置  }  Array = pivot; //把支点值放置到支点位置,这时支点位置是空置的  //对左右部分分别进行递归处理  QuickSort(Array, iLowSaved, iHigh-1);  QuickSort(Array, iLow+1, iHighSaved);  return 0; } //每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程 //竞争对控制台的使用权。 void PrintResult(long* Array, int iLength, const char* HeadStr) {  WaitForSingleObject(evtPrint, INFINITE); //等待事件有信号  //EnterCriticalSection(csPrint); //标记有线程进入临界区  //WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(没有线程拥有它)  int i;  printf("%s: ", HeadStr);  for (i=0; iiLength-1; i++)  {   printf("%d,", Array );   Sleep(100); //延时(可以去掉) /*只是使得多线程对临界区访问的问题比较容易看得到 如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会 分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果 */  }  printf("%d\n", Array );  SetEvent(evtPrint); //把事件信号量恢复,变为有信号 }    四、 小结   对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用。
个人分类: C++|1 次阅读|0 个评论
[转载]C++多线程(一)
热度 1 malin 2011-7-23 10:08
WIN 多线程API 一 简单实例 比较简单的代码,创建10个线程,其中使第4个线程在一创建就挂起,等到其他的线程执行的差不多的时候再使第4个线程恢复执行。 #include stdio.h #include stdlib.h #include windows.h #define THREAD_NUM10 DWORDWINAPIPrintThreads(LPVOID); int main() { HANDLEhThread ; DWORDdwThreadID ; for ( int i = 0 ;i THREAD_NUM; ++ i) { int isStartImmediate = 0 ; if ( 3 == i) isStartImmediate = CREATE_SUSPENDED; hThread = CreateThread(NULL, // securityattributesthatshouldbeappliedtothenewthread, // thisisforNT.UseNULLtogetthedefaultsecurityattributes.UseNULLforwin95 0 , // defaultsizeof1MBcanbepassedbypassingzero. PrintThreads, // functionname:addressofthefunctionwherethenewthreadstarts. (LPVOID)i, // parameter(voidpointer):pointertothe32bitparameterthatwillbepassedintothethread isStartImmediate, // flagstocontrolthecreationofthethread.Passingzerostartsthethreadimmediately. // PassingCREATE_SUSPENDEDsuspendsthethreaduntiltheResumeThread()functioniscalled. dwThreadID // pointertoa32-bitvariablethatreceivesthethreadidentifier. ); if (hThread ) { printf( " Threadlaunchedsuccessfully\n " ); } } printf( " Startsleep100,andletotherthreadexcute\n " ); Sleep( 100 ); printf( " Startsleep100,andthread3excute\n " ); ResumeThread(hThread ); Sleep( 100 ); for ( int i = 0 ;i THREAD_NUM; ++ i) { if (hThread ) { CloseHandle(hThread ); // Youneedtousethistoreleasekernelobjectswhenyouaredoneusingthem. // Ifaprocessexitswithoutclosingthethreadhandle, // theoperatingsystemdropsthereferencecountsforthoseobjects. // Butifaprocessfrequentlycreatesthreadswithoutclosingthehandles, // therecouldbehundredsofthreadkernelobjectslyingaroundandtheseresourceleakscanhaveabighitonperformance. } } return ( 0 ); } // functionPrintThreads DWORDWINAPIPrintThreads(LPVOIDnum) { for ( int i = 0 ;i 10 ;i ++ ) printf( " ThreadNumberis%d%d%d\n " ,num,num,num); return 0 ; } 二 相关基本API的说明 CreateThread() 调用成功返回句柄和一个id。 CloseHandle() 关闭一个打开的对象句柄,该对象句柄可以是线程句柄,也可以是进程、信号量等其他内核对象的句柄. SuspendThread(HANDLE) 允许开发人员将HANDLE指定的线程挂起,如果要挂起的线程占有共享资源,则可能导致死锁。 ResumeThread(HANDLE) 恢复指定的线程。 TerminateThread() 立即终止线程的工作,不做任何清理工作。 ExitThread() 线程函数返回时回调用此函数,所以一般我们不去显示的调用。 ExitThread是推荐使用的结束一个线程的方法,当调用该函数时,当前线程的栈被释放,然后线程终止,相对于TerminateThread函数来说,这样做能够更好地完成附加在该线程上的DLL的清除工作. 但是ExitThread()会导致线程在清处构造器/自动变量之前就终止,所以我们最好不要显示的调用ExitThread()。
个人分类: C++|41 次阅读|1 个评论
[转载]多线程中使用CheckForIllegalCrossThreadCalls = false访问窗口
dolphinzhu 2011-7-2 14:39
如下: 在窗口构造函数中将CheckForIllegalCrossThreadCalls设置为 false public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false; } 【msdn】 如何:对 Windows 窗体控件进行线程安全调用 使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。 示例 访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。 .NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException ,并提示消息:“从不是创建控件 control name 的线程访问它。” 此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。 注意 可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。 转自: http://www.cnblogs.com/lanru/archive/2010/07/15/1777892.html
个人分类: CSHARP|2843 次阅读|0 个评论
新的软件危机(110419)
热度 8 ymin 2011-4-19 17:43
新的软件危机(110419) 闵应骅 在上世纪60-80年代,计算机面临软件危机。主要表现在硬件生产出来以后,软件跟不上,开发经费很高,而正确性得不到保证,甚至引发安全事故。典型的例子是IBM360的操作系统OS/360,经历了10年,用1000人才完成了这一个复杂的系统。慢慢地,由于结构化程序设计、面向对象程序设计、CASE工具的出现、形式化方法的引入,终于使这个问题有所缓解,虽然至今软件项目失败的几率仍然很高。 近年来,多核处理器的出现是多线程的程序并行化成为急需解决的问题。上世纪80年代,美国成立专门的中心做串行程序并行化的工作。我们知道,Fortran程序库是人类文明的宝库,是几十年程序工作的积累。但都是串行程序,需要把它们并行化。我的一个朋友被请去当主任。可惜,几年下来,不了了之。据说他现在在某大公司做高管。因为要自动并行化太困难了。可现在,有了多核处理器,如果程序不能并行化,多核就等于没有用。现在,我国自己的多核芯片已经出来了,超级计算机也已经国际领先了。硬件有了,软件呢?全世界的情况也是如此。所以,我认为,并行编程技术应该是计算机领域第一重要的事情。 所谓并行编程就是要让多个处理器同时分担任务,较少相互通信,以解决同一个大的计算问题。本月的IEEE Computer上一篇“回避下一次软件危机”中提到,三种软件工程机制可以帮助我们把串行程序并行化。 1.代码重构 代码重构是指为改进软件内部结构并保持其外部行为而修改软件的过程。它可以半自动地把串行程序并行化,也可以改进已有的多线程程序。这种改变可以由人逐步地增量式的进行。例如用局部变量代替全局变量;修改数据结构及其布置;修改循环索引表达式;由于循环是不好并行的,可以将循环分裂、合并、延后或关闭。这些想法还是要人来提供的。自动并行编译就是因为碰到同样的困难,而无法做到完全地优化和自动化。代码重构多线程程序就更加困难一些,因为很难跟踪整个程序,而不光是跟踪一个线程。例如增加一个变量对于单线程是很容易的。但是,对于多线程的全局协同就可能受损,别的线程可能不可达该变量,甚至产生竞争。 2.模型驱动工程 模型驱动工程的目的是用规范和处理虚拟模型提高程序的抽象程度,去掉一些非本质的东西,以降低复杂性。然后根据模型用代码产生器产生代码。这在串行程序里已经用上了,对并行程序也可以用。由于各层的模型是一种抽象,所以,考虑模型的并行要比考虑代码的并行容易一些,从而使得有些分析和变换比较容易自动化。 3.重组再设计 重组再设计包括从传统软件抽出模型的逆向工程,并用另一种较好的方式重新实现该模型。如果要并行化,就需要在该模型中重新引进并行性。通过模型变换和代码产生来引进并行性可以累积程序的修改,提高程序质量。 这些工程方法对并行编程会很有帮助,而且相应的软件工具也会不断出现。但是,说老实话,不能从理论上说明什么叫并行计算,这些工程方法只能是摸着石头过河。
个人分类: 计算机|7136 次阅读|22 个评论
[转载]基于VC++6.0 编译器使用多线程
machinelearn 2010-8-21 19:22
POSIX是Portable Operating System Interface of Unix的缩写。由IEEE(Institute of Electrical and Electronic Engineering)开发,由ANSI和ISO标准化。POSIX的诞生和Unix的发展是密不可分的。Windows本身没有提供对POSIX的支持,但有一个叫 POSIX Threads for Win32 ( http://sourceware.org/pthreads-win32/ )的开源项目给出了一个功能比较完善的Windows下pthreads API的实现。目前的最新版本( pthreads-w32-2-8-0-release.exe )可以从 http://sources-redhat.mirrors.airband.net/pthreads-win32/ 上下载得到。 pthreads-w32-2-8-0-release.exe 是一个自解压文件,解压后,Pre-built.2目录中有编译所需要的头文件(include子目录)和库文件(lib子目录)。 测试程序如下: #include stdio.h #include pthread.h #include assert.h void * Function_t ( void * Param) { printf( Iamathread! ); pthread_tmyid = pthread_self(); printf( threadID=%d ,myid); return NULL; } int main() { pthread_tpid; pthread_attr_tattr; pthread_attr_init( attr); pthread_attr_setscope( attr,PTHREAD_SCOPE_PROCESS); pthread_attr_setdetachstate( attr,PTHREAD_CREATE_DETACHED); pthread_create( pid, attr,Function_t,NULL); printf( ======================================== ); getchar(); pthread_attr_destroy( attr); return 1 ; } 具体包括三步: 1 将 头文件目录(C:\pthreads\Pre-built.2\include)和库文件目录(C:\pthreads\Pre-built.2\lib) 分别添加到VC++ 6.0开发环境对应的include和lib目录下。 (点击:tools-options-Directories) 2. 添加link时要连接的库( pthreadVC2.lib )。 (点击:project-Settings-Link-Objects/library modules) 3. 将文件 pthreadVC2.dll 放到当前工程的目录下或放到system32目录下 参考链接: http://read.newbooks.com.cn/info/179364.html http://www.360doc.com/content/08/1215/09/59579_2126633.shtml
个人分类: C++|4230 次阅读|0 个评论
[转载]Matlab的Distributed Computing Toolbox初探
热度 1 w52191114 2010-8-13 01:13
Matlab的Distributed Computing Toolbox初探 之前讲过,实验室来了五台双核 Pentium D 925 计算机,这正适合用来做分布式或并行式计算。我打算只调用那些计算机中的一个核参与计算,留下一个核可以让其他人正常地使用该计算机。我们在这里将会介绍 Matlab 中 Distributed Computing Toolbox 的基本使用方法,目标是实现简单的分布式计算。 Distributed Computing Toolbox 就是分布式计算工具箱,简称 DCT ,其可以在多台计算机组成的 Cluster 中实现分布式或并行式计算。简单来说,我们是把一个很繁重的工作,分解成许多小任务,然后分给不同的计算机去处理,最后把计算结果汇总,以达到提高计算效率的目的。 Matlab 的做法是这样的:在每台参与计算的计算机中启动一个叫 Matlab Distributed Computing Engine 的服务,该服务能启动参与计算的 worker 的 Matlab session 和管理各台计算机 workers 的 job manager 。 Job manager 对 workers 进行管理,给 workers 分配计算任务,接收 workers 计算后的结果。而你本人就是 client ,你要把你的工作分解为多个任务,然后把任务给 job manager 。 job manager 就会根据 workers 的多少和空闲情况,适当地把任务分配给 workers 去做。 workers 完成任务后,会把结果返回给 job manager 。当所有 workers 都完成任务后,你,即是 client ,便可以从 job manager 里取回结果。 具体的概念可以参考 Matlab 的帮助,我们也不能说得很准确。我们在这里只想给出使用 Matlab 实现分布式计算的简单步骤,以便初学者快速入门。 1、首先第一步要做的,就是令每台要参与计算的计算机组成局域网。比如我有三台计算机,其 IP 地址分别为 192.168.1.101 - 192.168.1.103 ,以下简称计算机名为 101 , 102 和 103 。 2、在三台计算机中安装 Matlab Distributed Computing Engine ( mdce )服务。安装方法为:如 Matlab 的安装地址为 C:\Program Files\MATLAB\R2006b ,则 Start-Run-cmd 到命令行窗口,进入 C:\Program Files\MATLAB\R2006b\toolbox\distcomp\bin 目录,运行 mdce install 命令安装 mdce 服务。接着去控制台 - 管理工具 - 服务,查看 Matlab Distributed Computing Engine 的属性。进入登录页,选择此帐户,输入 NT AUTHORITY\NetworkService ,删除下面的密码,让该服务以 NetworkService 的形式登入,以便该服务存取共享的映射网络驱动器中的原程序文件。接着便可以启动该服务了。注意以后重新开机,该服务都会启动,当然你可以设置让它手动启动。 3、启动 job manager 。任一台计算机都可以启动 job manager ,只要 mdce 服务启动了即可。比如使用计算机 101 ,在 C:\Program Files\MATLAB\R2006b\toolbox\distcomp\bin 目录下,运行以下命令: startjobmanager -name frenseljobm 该命令启动 jobmanager ,其名字叫 frenseljobm ,启动地点为计算机 101 。 4、启动 workers 。 任一台计算机都可以启动 workers ,只要 mdce 服务启动了即可。比如使用计算机 101 ,在 C:\Program Files\MATLAB\R2006b\toolbox\distcomp\bin 目录下,运行以下命令: startworker -jobmanagerhost 192.168.1.101 -jobmanager frenseljobm -name worker1 此命令指明在计算机 192.168.1.101 中,启动名为 worker1 的 worker ,而该 worker 受名为 frenseljobm 的 jobmanager 管理。就是说来自乡下 101 的可怜工人 worker1 ,成为万恶的监工 frenseljobm 的马仔了。接着,监工 frenseljobm 要在不同村 102 和 103 中雇用更多的工人 worker2 、 worker3 。运行如下的命令: startworker -jobmanagerhost 192.168.1.101 -jobmanager frenseljobm -name worker2 -remotehost 192.168.1.102 即可在 102 计算机中启动一个新的,名为 worker2 的 worker ,如此类推启动 103 计算机的 worker3 。使用 nodestatus 命令可以查看节点的状态,加上 -remotehost 可以查看其他节点的状态。 5、如令计算机 101 为 client ,即我们的程序在这里编写的。设程序文件位于 D:\Matlab_code\testDCT 中。共享出文件夹 Matlab_code ,在文件夹中按工具 - 映射网络驱动器 - 令盘符为 Z:- 文件夹里填 \\192.168.1.101\Matlab_code 。于是 Z:\testDCT 便成为放置你程序的地方了。以同样的方法,让计算机 102 和 103 都建立映射网络驱动器,令盘符为 Z: ,文件夹里填 \\192.168.1.101\Matlab_code 。这时三台机都可以通过 Z:\testDCT 访问原程序文件。 6、现在便可以进行计算了。这里给出测试的代码。首先写一个函数,模拟我们实际的工作。 % hp.m function f = hp(m, n) H1 = zeros(n); H2 = zeros(n); for i = 1 : m H = H1 + H2; end f = H; end 将此程序 hp.m 放在 D:\Matlab_code\testDCT 中。此函数计算 n 维随机矩阵的加法 m 次。接着建立另一个 m 文件,做具体的分布式计算。 % runDCT.m tic % 寻找资源,比如 jobmanager 在什么地方,叫什么名字。 jm = findResource( 'scheduler' , 'type' , 'jobmanager' , 'name' , ... 'frenseljobm' , 'LookupURL' , '192.168.1.101' ); % 使用刚才找到的资源建立一个工作 job = createJob(jm); % 设置该工作的文件关联,让所有 workers 都可以找到原程序文件。 set(job, 'PathDependencies' , { 'Z:\testDCT' }) % 另一种方法,把用到的原程序文件传给所有 workers 。 % set(job, 'FileDependencies', {'hp.m'}) N = 100; M = 1000000; % 建立三个任务,每任务都是算 hp(M, N) 。 createTask(job, @hp, 1, {M, N}); createTask(job, @hp, 1, {M, N}); createTask(job, @hp, 1, {M, N}); % 提交工作给 jobmanager 。 submit(job) % 等待所有 workers 都把任务做完。 waitForState(job, 'finished' ) % 取出计算结果。 results = getAllOutputArguments(job); toc 同样地,该程序 runDCT.m 也是放在 D:\Matlab_code\testDCT 中。该程序计算了三次 100 维矩阵的加法 1000000 次,即算了 100 维矩阵的加法 3000000 次。如果在单机上运行: tic, a = hp(3000000, 100); toc Elapsed time is 63.096369 seconds. 而使用三台机作分布式计算时: runDCT Elapsed time is 24.323556 seconds. 效率有明显的提升。但注意到,当第一次进行分布式计算时,其他几台机要从 Z:\testDCT 中读取原程序文件,会使得计算速度降低。 总结来说, Matlab 的 Distributed Computing Toolbox 为我们提供了一种简便的分布式或并行式计算的实现方法。以上所写的是为了对 DCT 具体做法的整个过程做一次简单的介绍,可能很粗糙和存在许多谬误,敬请指正。
个人分类: matlab|3606 次阅读|1 个评论
[转载]Matlab多线程运算的问题
w52191114 2010-8-13 01:12
Matlab多线程运算的问题 上次说到, Matlab R2006a 开始使用 Intel MKL ,只要设好 OMP_NUM_THREADS 这个环境变量,即可以 BLAS Level 3 的运算在任意线程数下。然而,尽管设定的线程数和 CPU 核的数目相同,但这样也并不能保证能提升计算效率。主要的原因在于建立线程也是需要时间的。如果你的任务只要 0.0001 秒就能算完,但建立线程也要用 0.0001 秒,那就根本没有必要把该任务多线程化。 麻烦的是,当我们设定好环境变量 OMP_NUM_THREADS 后启动 Matlab ,那么这个进程运行的线程数就定下来了,不能中途改变。于是,我们没有办法根据具体问题随时改变使用的线程数,使得在我们的程序中,能提升某部份的效率,但另一部份的效率却可能降低。 我们可以对矩阵乘法做一点测试,在不同的线性数下,看看不同的矩阵大小,其乘法的效率如何。首先编写程序如下: THREADS = 2; % 线程数 N = 2000000; % N / 矩阵大小 = 每种维度的矩阵要做乘法的次数 % 设定测试的矩阵大小 MN = 50; step = 2; x = 10 : step : MN; n = max(size(x)); T = zeros(2, n); % 用作存放结果 for i = 1 : n M1 = zeros(x(i)); M2 = zeros(x(i)); M1 = rand(x(i)); M2 = rand(x(i)); t = cputime; % 准备计时 for j = 1 : N / x(i) % 令计算次数随矩阵增大而减少 tmp = M1 * M2; end T(:, i) = ; % 存放结果 end 可以在不同线程下计算,把 T 存起来。接着可以比较单线程下的计算结果 T1 和双线程下计算的结果 T2 ,有: T = T1(2, :) ./ T2(2, :); plot(T1(1, :), T, 'k') 可得出下图。说明当矩阵大小约为 20 到 30 时,双线程反而令矩阵乘法效率降低。当矩阵更小时,是否多线程效率更好,我不能保证,应该要作更仔细的验算。
个人分类: matlab|4374 次阅读|0 个评论
[转载]MEX文件中使用OpenMP
w52191114 2010-8-12 23:58
MEX文件中使用OpenMP Matlab 本身是不可以做多线程计算的,即不可像 Fortran/C++ 使用 OpenMP 那样,可以指明程序的那个部份应该用多少个线程去执行,但却可以使用间接地调用已经多线程化的程序,如 Intel MKL 中的 BLAS Level 3 去实现多线程运算。于是我们想到,是否可以把用 Fortran/C++ 写的 OpenMP 多线程程序,编译成 Matlab 可以调用的动态链接库 MEX 文件。通过调用拥有多线程运算能力的 MEX 文件,提高 Matlab 在某些应用中的效率。这种想法是可以实现的,这里简述具体做法。 我们这里使用 Fortran 做例子, C++ 中的实现方法和 Fortran 是类似的。我们使用的是 Widnows XP 、 Microsoft Visual Studio .NET 2005 、 Intel Visual Fortran Compiler 9.1 、 Matlab R2006b 、 CPU 是 Intel Core 2 Duo E6400 。首先设置好 MEX 要用到的一些路径,如 include 和 lib 的位置,具体可参考 Matlab 帮助中的 MATLAB - External Interfaces - Calling C and Fortran programs from Matlab - Custum Building on Windows 中的 Compiling MEX-Files with the Microsoft Visual C++ IDE 。只要按着要求的步骤做即可,要注意编写 def 文件时,如用 IVF 编译器的,要写成 EXPORTS MEXFUNCTION 。 现在可以做个小测试。先从 Matlab 帮助中,找一个最简单的 Fortran MEX 文件的例子,我使用的是 A First Example Passing a Scalar (只要搜寻 Passing a Scalar ,即可找出此例子)。这个例子是通过接口子程序 mexFunction ,把输入参数从 Matlab 里调入,同时给定 Matlab 的输出变量,再把该输入参数通过另一个子程序 timestwo 乘以 2 ,将结果放入参数 y 中,最后把 y 返回到 Matlab 的输出变量中。就是说,子程序 timestwo 是该 MEX 文件的功能部份,只要将该功能部份并行化,即可提高运算效率。但子程序 timestwo 太简单了,我们没必要做什么。但我们却可以人为地加上一个很艰巨的任务,以此测试 OpenMP 的使用。 我们考虑做两个 1000 X 1000 矩阵的加法,要执行 2000 次。这任务看来并不轻松,足够 CPU 忙一阵子。既然这事情要做 2000 次,不如让每个线程做 1000 次,这可以使用 OpenMP 的 SECTIONS Directive 实现。现把子程序 timestwo 改动如下: subroutine timestwo(y, x) implicit none real(8) :: x, y integer , parameter :: N = 1000 integer :: i real(8) , allocatable :: a(:, :), b(:, :), c(:, :), d(:, :) ! dynamically creates storage allocate (a(N, N), b(N, N), c(N, N), d(N, N)) ! initialization a = 1.23456789d0 b = 9.87654321d0 !$OMP PARALLEL !$OMP SECTIONS !$OMP SECTION do i = 1, 1000 c = a + b end do !$OMP SECTION do i = 1, 1000 d = a + b end do !$OMP END SECTIONS !$OMP END PARALLEL y = 2.0 * x return end subroutine timestwo 这程序相当简单,它并没有影响原本 timestwo 的功能,而是在其中加上一些无关的计算,测试 OpenMP 。我们使用了 OpenMP 的 SECTIONS directive ,将矩阵加法分为两个部份,每个部份都是只有一个线程执行,这样便可以使 CPU 的两个核同时工作。 接着设定好 OpenMP 的编译命令,即可生成 MEX 文件。把该 MEX 文件放在 Matlab 的相关目录下,假设文件名为 timestwo.mexw32 ,在 Matlab 里运行: tic, a = timestwo(2), toc a = 4 Elapsed time is 22.742711 seconds. clear timestwo 可见,做 2000 次 1000 X 1000 矩阵的加法,在 Intel Core 2 Duo E6400 上,要约 22.74 秒。在 Release 模式下,需时约 12.50 秒(在 Pentium D 925 中,要 29.57 秒)。要注意使用 timestwo 后,最好先把它 clear 了,否则该 MEX 文件不能更删。 这里还有一点是必须提一下的。我们在子程序 timestwo 中,矩阵使用了动态分配内存,而不是静态地预先分配好。这是由于动态链接库是不会分配 stack 空间的,而是调用该 dll ( MEX )的程序 Matlab 分配。由于要分配 1000 X 1000 的 double 矩阵,而且每个线程都要分配一份,使得 Matlab 调用该 MEX 文件时,很可能出现 stack overflow 错误。要改动 Matlab 所分配的 stack 的大小看来不是个好主意,我也不清楚该如何做,那就只好动态地分配内存,使其数据放在 heap 中,那就可以避免 stack overflow 。
个人分类: matlab|5322 次阅读|0 个评论
[转载]Matlab:多线程?我也行。
w52191114 2010-8-12 23:54
Matlab:多线程?我也行。 Matlab从R14开始使用Intel MKL的BLAS,在Windows平台下,设定环境变量BLAS_VERSION=mkl.dll即可使用(默认还是使用ATLAS BLAS,在R2006a版本这个环境变量不用设了,都用MKL)。而且通过MKL,可实现BLAS Level 3的多线程运行。设定环境变量OMP_NUM_THREADS=2,重新启动Matlab,运行昨天的测试程序: function test(l) N = 1000; a = zeros(N, N); b = zeros(N, N); a(:) = 1.23456789; b(:) = 9.87654321; tic for i = 1 : l c = a * b; end toc end 在Matlab的Command Window运行: test(30) Elapsed time is 6.300522 seconds. 结果不比在Fortran直接调用MKL的要差。用双核的朋友们,别忘了设定环境变量OMP_NUM_THREADS啊!
个人分类: matlab|4534 次阅读|0 个评论
论多线程
wanglicuc 2010-4-26 13:23
应燕儿姐的鼓励,冲个咖啡,继续记录点滴。这次我想谈谈多线程。多线程是计算机里的术语,它的百科解释为: http://baike.baidu.com/view/65706.htm 。不过我可不想以那种教条的方式来谈,那多没劲啊,是吧。谈谈我的理解吧,Here we go! Follow my thinking!既然我们都年轻人,那我们就从和我们密切相关的谈恋爱开始吧。我从一个man的角度来讲。每个男人都希望追到自己梦寐以求的女孩子,从 而开启自己感情的第一个线程,再在线程的运行过程中慢慢的磨合感情,单线程必然是单任务的,没有管理的开销,直接执行就可以了,直到遇到最后的 return两人幸福的度过一生或者中间过程中遇到异常而夭折。可见在每个人感情的这个任务中只开启一个线程效率是高的,但风险也是大的,一旦经营不好, 就会夭折,接下来的就是痛苦,释怀之后又要积攒勇气和魄力投入到下一段感情之中。所以每一个单线程的过程都是深刻的。 可是, 生物学赋予了每个男人花心的本性,社会道德规范又要求男人专一,当社会道德难以和生物原理抗衡时,总会出现那么一些人,在自己的感情任务中开启了多个线程 (为 这些男同胞找个借口哈)。暂且不论道德,从评价单线程的角度看多线程,多线程的性能是高的,它充分调动起了身边的资源,机会多了,风险也相应的小了;一个 夭折,还有其他的,痛苦也会小得多,或者没有痛苦,更谈不到积攒勇气和魄力来投入到下一段感情中。在他们的感情任务线程池中总是有线程在运行着,也随 时等着接纳新加进来的线程。 我上面说了,不讨论道德,只为映射来理解多线程及相关概念。每个人都是一个CPU,我们知 道CPU是基于时间片地来计算的,即将时间分片,然后将每个时间片分给各个线程,如第0 ~ 2秒CPU执行第一个任务、第2 ~ 6秒CPU执行第二个任务等等,好理解吧。好了,现在你开了多线程,总不能让这些线程撞车吧(你和第i个亲热时,让第j个看见,这不是找死么!),那接下 来你就得学会如何调度这些线程了,哪个优先级高?你当前的任务完成的怎么样了?这些都是要考虑的因素,不能两个线程都说了要你明天陪她们逛街,你又不 会分身术,当然得找个借口,错开一下咯。 这就是互斥访问的概念,即:同一时刻你的资源只能由一个线程来用。 至 于把你的时间分给每个线程多少(所谓的优先级),也是需要权衡的,优先级高的分的时间自然就多点呗。这些应该比较清楚了吧。好,那我思维转一下。每个人都 有很多事情要处理,当然在一个时刻只能处理一个。女孩子都喜欢有责任感、有上进心的等等,得有自己的工作和事业吧,而且得表现出很忙的样子对不,所以呢很 多时候在陪这些线程之前就要 等 你工作的事情完成(线程重大的急事除外啊), 这就是同步的概念了,即:等待而使得你的各项任务有序的完成罢了。 但也有时候你的任务不必你亲自去做的,你可以委托给别人,这种情况很多吧,这时你让别人去做,你呢就去陪线程吧,你的两项任务同时进行, 这就是所谓的异步,即:没有等待,事情同时进行。 好了,我们看到了多线程会涉及到的问题及解决方式。我们应该会得到一个结论吧: 多线程不是谁都玩的起的 。 首先得有魅力有资本 。女生怎么会和一个看不进她法眼的你执行一个线程呢。对于一个男人来说,这些资本涉及的东西真的就很多了,相貌得看得下来吧,出身、背景、学历、经济状况、性格等等(这里就不说人品了啊,或者说的话,应该是让女生觉得你人品好)。 其次要聪明、能力强 。我们看到了上面说的那些问题,哪个处理不好行呢?这个情商和智商都会涉及到,而且会有极高的要求。 最后心理素质一定要好,抗压性要强 。虽然在自己的心中是生物原理战胜了社会道德,但是从整体来看,这个毕竟是少数的,舆论的压力是很大的,即使你保密工作做的好,处事低调,也会承受说漏嘴的压力呀,所以心理素质和抗压性一定要好。
个人分类: 未分类|2053 次阅读|0 个评论
MFC中的多线程
bluewind23 2009-9-11 15:55
Microsoft 基础类 (MFC) 库提供多线程应用程序支持。本主题描述进程、线程和 MFC 多线程编程方法。 进程是应用程序的执行实例。例如,双击记事本图标时,将启动运行记事本的进程。 线程是进程内的执行路径。启动记事本时,操作系统创建进程并开始执行该进程的主线程。此线程终止时,进程也终止。启动代码以函数地址的形式将此主线程提供给操作系统。通常是所提供的 main 函数或 WinMain 函数的地址。 如果愿意,可以在应用程序中创建其他线程。如果在处理后台任务或维护任务时不希望用户等待这些任务完成,则可能需要创建其他线程。MFC 应用程序中的所有线程都由 CWinThread 对象表示。大多数情况下,甚至不必显式创建这些对象,而只需调用框架 Helper 函数 AfxBeginThread ,该函数将为您创建 CWinThread 对象。 MFC 区分两种类型的线程:用户界面线程和辅助线程。用户界面线程通常用于处理用户输入及响应用户生成的事件和消息。辅助线程通常用于完成不需要用户输入的任务(如重新计算)。Win32 API 不区分线程类型;它只需要了解线程的起始地址以开始执行线程。MFC 为用户界面中的事件提供消息泵,从而对用户界面线程进行专门处理。 CWinApp 是用户界面线程对象的一个示例,因为它从 CWinThread 派生并对用户生成的事件和消息进行处理。 以上文字摘自:http://msdn.microsoft.com/zh-cn/library/975t8ks0(VS.80).aspx 在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下: (1) CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, nPriority=THREAD_PRIORITY_NORMAL, UINT nStackSize=0, DWORD dwCreateFlags=0, LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL ); PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam); 请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。 pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略; nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级; nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小; dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起; lpSecurityAttrs:线程的安全属性指针,一般为NULL; (2) CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority=THREAD_PRIORITY_NORMAL, UINT nStackSize=0, DWORD dwCreateFlags=0, LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL ); pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,此消息机制同主线程的机制几乎一样。 在工作线程中使用的函数指针一般是指向全局函数的而不是类成员函数,因为这牵扯到对象的生命周期,如果一个对象在线程执行时被销毁了,那么这个线程的行为就成为不确定的了。
个人分类: 学习研究|9957 次阅读|0 个评论

Archiver|手机版|科学网 ( 京ICP备07017567号-12 )

GMT+8, 2024-6-3 15:33

Powered by ScienceNet.cn

Copyright © 2007- 中国科学报社

返回顶部