1、并行系列一
线程是一个单一的执行流程,它是所有程序执行过程中最小的控制单位,即能被 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($i<scalar(@names))
{ while(scalar(threads->list())<10)
{ threads->new(\&orgfile,$names[$i]);
$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";
}
------------------------------------------------
END;
2、并行开发系列二
在当今多核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($j>10)
{ 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的使用率。
-----------------------------------------------------------转载完----------------------------------------------------------------------
关于多线程还可以参考:
http://perldoc.perl.org/threads.html ------------------(perl官方文档介绍)
http://www.cnblogs.com/zhangchaoyang/articles/2059168.html ------------------(多线程实现爬虫)
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!