前些日子在写一些 Perl 脚本的时候为了追求处理速度或者时间上的平行,频繁地使用到了 Perl 的多线程功能。在类 UNIX 的 OS 上,Perl 的多线程还算可靠的,虽然 perldoc 也并不推荐使用多线程,但是应用场景实在太多,几乎不太可能完全不用。

Perl 内置有一个多线程模块 Thread ,提供了常规意义上的multithreads功能,可以满足我们的一般需求。下面以一个简单的收集Ping数据的脚本为例。

Perl多线程处理

要实现的目的是,收集本机 ping 多个远程 IP 产生的数据,写入到 csv 文件,然后上传的远程FTP上做后期处理。有关多线程的代码如下。其中 $cnip 是通过另一个 sub获取到的本机地址,@snip是配置文件中读取(或者参数中)读取到的目标地址。

my @threads;
    my @snips = split /,/,$opts{dest};
    #有多少个目标地址,就创建多少个线程。其中grabdata是收集数据的sub。
    for my $snip (@snips) {
        push @threads,threads->create(\&grabdata,$cnip,$snip);
    }
    #join所有线程,并写入到csv文件。
    for my $thread (@threads) {
        my $data = $thread->join();
        my $statics = join(",",@$data);
        print $OUT "$statics\n";
    }
    #关闭文件句柄,将buffer里的数据刷入文件
    close ($OUT);
    #获取所有本地csv文件并上传。其中getcsv和upload是脚本中定义的sub。
    my @csvfiles = getcsv( $reportdir );
    if ( $opts{ftp} ) {
        for my $csvfile ( @csvfiles ) {
            upload($opts{ftp},$opts{user},$opts{pass},"$csvfile");
        }
    }

基本原理很简单,为每一个 grabdata 操作都创建一个thread,遍历所有 thread,join到主线程并获取返回值,写入到本地文件,然后上传。

其中有几个需要注意的地方:

  1. thread的默认返回值是scalar 如果sub的返回值是array,可以返回array的ref(如上例),或者直接在 create 时传入hash ref指定: ``前些日子在写一些 Perl 脚本的时候为了追求处理速度或者时间上的平行,频繁地使用到了 Perl 的多线程功能。在类 UNIX 的 OS 上,Perl 的多线程还算可靠的,虽然 perldoc 也并不推荐使用多线程,但是应用场景实在太多,几乎不太可能完全不用。

Perl 内置有一个多线程模块 Thread ,提供了常规意义上的multithreads功能,可以满足我们的一般需求。下面以一个简单的收集Ping数据的脚本为例。

HUGOMORE42

Perl多线程处理

要实现的目的是,收集本机 ping 多个远程 IP 产生的数据,写入到 csv 文件,然后上传的远程FTP上做后期处理。有关多线程的代码如下。其中 $cnip 是通过另一个 sub获取到的本机地址,@snip是配置文件中读取(或者参数中)读取到的目标地址。

my @threads;
    my @snips = split /,/,$opts{dest};
    #有多少个目标地址,就创建多少个线程。其中grabdata是收集数据的sub。
    for my $snip (@snips) {
        push @threads,threads->create(\&grabdata,$cnip,$snip);
    }
    #join所有线程,并写入到csv文件。
    for my $thread (@threads) {
        my $data = $thread->join();
        my $statics = join(",",@$data);
        print $OUT "$statics\n";
    }
    #关闭文件句柄,将buffer里的数据刷入文件
    close ($OUT);
    #获取所有本地csv文件并上传。其中getcsv和upload是脚本中定义的sub。
    my @csvfiles = getcsv( $reportdir );
    if ( $opts{ftp} ) {
        for my $csvfile ( @csvfiles ) {
            upload($opts{ftp},$opts{user},$opts{pass},"$csvfile");
        }
    }

基本原理很简单,为每一个 grabdata 操作都创建一个thread,遍历所有 thread,join到主线程并获取返回值,写入到本地文件,然后上传。

其中有几个需要注意的地方:

  1. thread的默认返回值是scalar 如果sub的返回值是array,可以返回array的ref(如上例),或者直接在 create 时传入hash ref指定:`` 。

  2. join VS detach 上例用到join只是因为我需要每个 grabdata sub 的返回值,如果创建Thread只是为了达到并行处理的目的,线程的返回值不重要,可以创建后 ```前些日子在写一些 Perl 脚本的时候为了追求处理速度或者时间上的平行,频繁地使用到了 Perl 的多线程功能。在类 UNIX 的 OS 上,Perl 的多线程还算可靠的,虽然 perldoc 也并不推荐使用多线程,但是应用场景实在太多,几乎不太可能完全不用。

Perl 内置有一个多线程模块 Thread ,提供了常规意义上的multithreads功能,可以满足我们的一般需求。下面以一个简单的收集Ping数据的脚本为例。

HUGOMORE42

Perl多线程处理

要实现的目的是,收集本机 ping 多个远程 IP 产生的数据,写入到 csv 文件,然后上传的远程FTP上做后期处理。有关多线程的代码如下。其中 $cnip 是通过另一个 sub获取到的本机地址,@snip是配置文件中读取(或者参数中)读取到的目标地址。

my @threads;
    my @snips = split /,/,$opts{dest};
    #有多少个目标地址,就创建多少个线程。其中grabdata是收集数据的sub。
    for my $snip (@snips) {
        push @threads,threads->create(\&grabdata,$cnip,$snip);
    }
    #join所有线程,并写入到csv文件。
    for my $thread (@threads) {
        my $data = $thread->join();
        my $statics = join(",",@$data);
        print $OUT "$statics\n";
    }
    #关闭文件句柄,将buffer里的数据刷入文件
    close ($OUT);
    #获取所有本地csv文件并上传。其中getcsv和upload是脚本中定义的sub。
    my @csvfiles = getcsv( $reportdir );
    if ( $opts{ftp} ) {
        for my $csvfile ( @csvfiles ) {
            upload($opts{ftp},$opts{user},$opts{pass},"$csvfile");
        }
    }

基本原理很简单,为每一个 grabdata 操作都创建一个thread,遍历所有 thread,join到主线程并获取返回值,写入到本地文件,然后上传。

其中有几个需要注意的地方:

  1. thread的默认返回值是scalar 如果sub的返回值是array,可以返回array的ref(如上例),或者直接在 create 时传入hash ref指定: ``前些日子在写一些 Perl 脚本的时候为了追求处理速度或者时间上的平行,频繁地使用到了 Perl 的多线程功能。在类 UNIX 的 OS 上,Perl 的多线程还算可靠的,虽然 perldoc 也并不推荐使用多线程,但是应用场景实在太多,几乎不太可能完全不用。

Perl 内置有一个多线程模块 Thread ,提供了常规意义上的multithreads功能,可以满足我们的一般需求。下面以一个简单的收集Ping数据的脚本为例。

HUGOMORE42

Perl多线程处理

要实现的目的是,收集本机 ping 多个远程 IP 产生的数据,写入到 csv 文件,然后上传的远程FTP上做后期处理。有关多线程的代码如下。其中 $cnip 是通过另一个 sub获取到的本机地址,@snip是配置文件中读取(或者参数中)读取到的目标地址。

my @threads;
    my @snips = split /,/,$opts{dest};
    #有多少个目标地址,就创建多少个线程。其中grabdata是收集数据的sub。
    for my $snip (@snips) {
        push @threads,threads->create(\&grabdata,$cnip,$snip);
    }
    #join所有线程,并写入到csv文件。
    for my $thread (@threads) {
        my $data = $thread->join();
        my $statics = join(",",@$data);
        print $OUT "$statics\n";
    }
    #关闭文件句柄,将buffer里的数据刷入文件
    close ($OUT);
    #获取所有本地csv文件并上传。其中getcsv和upload是脚本中定义的sub。
    my @csvfiles = getcsv( $reportdir );
    if ( $opts{ftp} ) {
        for my $csvfile ( @csvfiles ) {
            upload($opts{ftp},$opts{user},$opts{pass},"$csvfile");
        }
    }

基本原理很简单,为每一个 grabdata 操作都创建一个thread,遍历所有 thread,join到主线程并获取返回值,写入到本地文件,然后上传。

其中有几个需要注意的地方:

  1. thread的默认返回值是scalar 如果sub的返回值是array,可以返回array的ref(如上例),或者直接在 create 时传入hash ref指定:`` 。

  2. join VS detach 上例用到join只是因为我需要每个 grabdata sub 的返回值,如果创建Thread只是为了达到并行处理的目的,线程的返回值不重要,可以创建后``` 。

  3. 危险的IO Buffer 这个虽然和多线程没有直接关系,但是令我印象深刻,所以还是记录一下。因为以前用到 file handler 从来不会手动 close,在这里就悲剧了。Perl在处理 file handler 的时候为了减少文件IO以提高性能,有一个 buffer 机制。比如上例在for循环中写入$OUT,perl并不会在每次循环都写入文件,而是暂时保存在 buffer 里。如果不手动 close 的话,最终上传的将是一个空文件。Perl 诡异的 Buffer 迷惑了很多人,于是就有了这坨长篇大论

内置的thread模块可以满足一般多线程的需求,但是没有解决一些我们经常会碰到的问题:

  1. 无法固定线程个数,排队处理 比如,我需要发送1000个http request,显然同时发送害人害己。比较自然的想法是,创建比如20个线程,某一个线程完毕后,程序自动补上一个,保证同时只有20个线程。事实上,thread 结合 thread::queue 模块可以实现,但是十分费解,手动要做的事情太多,而且容易侧漏。

  2. 线程之间变量共享比较困难 上例中,各个线程的变量是独立的。但是很多情况下我们需要所有线程共享一个数据结构,比如 push 数据到同一个array,thread 模块无法解决。thread::shared 模块号称能够共享变量,但是对于array和hash还是无能为力。

Perl的built-in功能虽然有限,但是大CPAN是万能的,有一个第三方模块可以很好的解决这个问题。下回再细说。