Perl语言多线程的简单实现
最近要批量整理一大堆BAM数据,批量Map到转录组固定位点。写了一个单线程的程序,估计了一下,跑完估计得2天,完全等不了。所以开始写准备写perl语言的多线程,经过半个下午的折腾,终于找到了靠谱的方法。感叹一下,perl是不是已经被抛弃了,现在真心是python的天下。
- 项目的多线程需求
- 多线程模块Parallel::ForkManager
- 血泪史
- 我对Perl语言的理解
项目的多线程需求
我现在把文件压缩成bam格式并且index,现在需要把reads的位点定位在转录本的固定位点上。也就是这是一个单线程不怎么耗时的任务,需要用大量多线程并发来提高效率。
所以单线程任务为:
- 调用用samtools提取转录本所在的位置的所有reads
- 用perl处理后在数组固定位置加1
- 将数组输出到文件
每个单线程耗时很短,最耗时的可能是调用samtools,不过因为bam我都进行index了,几乎秒查。
如果需要多线程,那么需求为:
- 同时提交多个单线程任务,线程之间数据不共享
- 数据输出到文件的时候,多线程不能冲突(比如多个线程同时往一个文件中数据,那结果就乱了)
- 最好能控制一下线程数量,不能一次提交全部线程。
多线程模块Parallel::ForkManager
人生苦短,我用Parallel::ForkManager,不用threads:)
这个模块优点就是,CPAN上有详细的API,作者也给出了多个示例供参考,几行代码轻松实现多线程=-=,这就是我需要的模块。
安装Parallel::ForkManager
我用CPAN安装
cpan Parallel::ForkManager
对于国内的朋友需要改一下源:
#如果你知道Config.pm或者MyConfig.pm在哪,那就直接打开,把下面的这行修改成163的源
'urllist' => [q[http://mirrors.163.com/cpan/]]
#如果你不知道配置文件在哪,可以命令行:cpan
$ cpan
Loading internal logger. Log::Log4perl recommended for better logging
Terminal does not support AddHistory.
To fix that, maybe try> install Term::ReadLine::Perl
cpan shell -- CPAN exploration and modules installation (v2.27)
Enter 'h' for help.
#输入
cpan[1]> o conf
$CPAN::Config options from /home/xxx/.cpan/CPAN/MyConfig.pm:
commit [Commit changes to disk]
defaults [Reload defaults from disk]
help [Short help about 'o conf' usage]
#上面显示配置文件的路径,可以直接打开文件修改,或者
o conf urllist q[http://mirrors.163.com/cpan/]
装好之后,可以先use测试一下
use Parallel::ForkManager
如果报错的话,仔细看错误,我在装的时候提示还有一个包没有装(如果是路径不对就自行调整),Moo::Role。
cpan Moo::Role
之后就能运行啦~
语法
我对于多线程理解的语法应该类似于这样的:
#伪代码,伪代码
$最大线程数=20;
for my $i(@你的所有任务){
$开始多线程
单线程任务(一些参数)
$结束多线程
}
sub 单线程任务{
...
}
这里先把所有线程安排到一个“等待队列”里面,同时有一个有限长度的“工作队列”。完成一个,出“工作队列”,同时从“等待队列”取出一个加入“工作队列”保持最大“工作队列长度”。
或者是没有“等待队列”,"工作队列"有空缺了,再生成一个任务补进去。这种听起来似乎更节约内存。
虽然不知道FolkManager究竟的内部逻辑是什么,但是语法逻辑和上面的一样,很便于理解。
#最简单的ForkManager
use strict;use warnings;
use Parallel::ForkManager;
# 这里设置最大线程数为20
my $pm=Parallel::ForkManager->new(20)
my @jobs;
for my $i(@jobs){
#在循环中写上开始、结束和你想要做的事情
$pm ->start and next ;
这里写上你想让多线程做的事情
$pm ->finish;
}
$pm->wait_all_children;
完美!
解决输出问题
刚才提到了还有一个问题,就是多线程会一起输出。如果几个线程同时写文件就会出现错乱,如果写到不同的文件倒是一个方案,但是最后cat的压力就比较大,我这个共几千万个转录本,最后就是几千万个文件的cat,实在崩溃。
我发现添加下面的语句,就不会出现错乱问题。就是设置在子线程计算完成的时候,都要运行这个程序,统一输出,吧......我也不知道为什么,但是,it work!
my $pm=Parallel::ForkManager->new(20)
#定义完之后定义下面的,run_on_finish
$pm -> run_on_finish(
sub {
my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
if (defined($data_structure_reference)){
my $string = ${$data_structure_reference};
print $string,"\n";
}
}
);
#在循环中写finish的时候,改成以下写法:
$pm ->finish(0,\$report);
#这里的$report就是我期望子线程输出的内容,一定要记住这个反斜杠\,别忘加了
(我也不知道为什么,但是我这么写之后,确实不会出现输出文件错乱问题了。如果哪个大佬知道为什么,求告知,不胜感谢)
血泪史
也不算血泪史,毕竟才折腾了半个下午,还没有写这个博客耗时长=-=。
首先我当时用了threads,但是这个模块我知道怎么创建线程,怎么回收线程,但是怎么控制线程的数量啊?
之后我知道Thread::Pool模块,其中还有一个Thread::Pool::Simple模块,看起来就简单易行。然鹅,感觉bug的是,运行后直接运行了所有是threads,并没有控制数量,并且线程还没结束就join了-0-。
然后我觉得,要不还是用Thread::Pool好了。结果,我想要一个例子=-=,但是没有,作者没有在CPAN里面给,同时全网搜都木有。
最后用了FolkManager,舒服了。。。简单易行,作者给了详细的例子,我自己写了几个Demo后就直接上服务器了,感觉效率爆表。
我对Perl语言的理解
我的小骆驼啊~
从进实验室的第一天,师兄、老师就让我学Perl语言,当时实验室还在用的还有MatLab, C++, Java。不过当时做生物信息很多大佬用perl的很多,原因是处理文本比较快且容易学,代码写起来长度短,无视数据类型全靠上下文等。
然鹅,现在,实验室的小朋友们统一在学,Python =-=!
但是在服务器写代码,我还是喜欢用Perl,因为写起来快,运行起来快,并且最重要的是**不需要考虑缩进!**我对于Perl语言的理解就是,我觉得它是一个脚本语言,用Perl可以更好的代替cat, awk, grep等shell的命令完成更复杂的工作,所以以后如果需要管服务器或者写比较麻烦的shell的时候,我还是用Perl好了。
但是现在问题就是,如果你写perl程序遇到问题了,这时候就麻烦了。Perl的社区有限活跃度及其一般,经常搜不到你想要的,只能自己摸索。很多包没有现成的,需要自己写。
今天想研究一下多线程,基础模块threads的教程倒是很多但是无法满足我的需求,找了半天,终于找到了Parallel::ForkManager模块,感觉这个模块更像个线程池,终于实现了需求。
