跳至主要內容

Perl语言多线程的简单实现

Haopeng Yu大约 6 分钟

最近要批量整理一大堆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模块,感觉这个模块更像个线程池,终于实现了需求。

博主简介
博主简介