再谈谈perl多线程使用问题

本帖最后由 youzhengchuan 于 2011-05-31 09:10 编辑

最近有一个“十万条视频URL迁移”项目,算了一下,如果用单线程跑的话,大概要花费10天的时间,因此决定用多线程。

程序比较简单,先定义一个wget的子函数,该函数只负责2个事情,
  1;使用LWP模块,接受传递的URL,将该URL下载并存储在相关目录。
  2:在下载完毕后,对线程计数器减1,然后退出函数。

然后在程序主进程中,循环读取URL列表,每读取一个url,则生成一个线程去完成这个下载,同时每对线程计数器进行累加,当达到一定阀值的时候,则需要等待线程计数器下降到阀值以内才能生成新的线程。

代码块如下

#!/usr/bin/perl
use warnings;
use strict;
use threads;
use threads::shared;
use LWP;
use LWP::Simple;
use File::Basename;
use File::Path;

##使用这个变量存储线程数量
my $thread_num : shared = 0;

sub doWget {
    ###
    ###  获取传递过来的URL参数,然进行下载并存储在相关目录
    ### 下载完毕在退出线程之后,需要将 $thread_num 变量减1.
    $thread_num --;
    return 1;
}

###主程序负责读取URL列表并生成线程下载
for (my $i=0;$i < @urllist ;$i ++ ) {
   .....
   $maxthread = 20; ##允许开启线程最大数量
    if ( $thread_num <= $maxthread ){
          my $thread = threads->create(\&dowget,$url);
          $thread_num ++; ##生成线程后,需要把线程计数器加1
    }else{
          while( $thread_num > $maxthread ){
              sleep 5; ##进入while循环,判断线程数量是否已经小于线程阀值。
          }
          $i --;  #这里需要把 for循环中的参数 $i 减1,以保证不会漏掉url
    }

}


##最后,程序在退出之前,也要检查线程数量,以保证线程全部下载完毕才能退出。
while( $thread_num != 0) {
    sleep 5;
}

## 完毕。

作者: youzhengchuan   发布时间: 2011-05-31

本帖最后由 youzhengchuan 于 2011-05-31 09:12 编辑

通过一个一千条左右的url测试,发现并没有什么问题,正式使用后,发现当url下载到2万条左右的时候,perl程序莫名其妙崩溃并退出去了。
最后发现线程在使用之后,并没有join(),也没detach(),按照相关资料解释,这会导致资源被占用越来越多,通过观察,的确内存占用很厉害,当读取到2千条左右的url后,内存已经占用满了。
于是在线程生成之后,立刻调用detach(),让生成的线程和主进程分离,线程执行完毕后立即释放资源,代码如下:
          my $thread = threads->create(\&dowget,$url);
          $thread_num ++;
          $thread -> detach();

经过修改之后的脚本,占用内存一致处于15%左右,当线程运行到20个之后,cpu占用在20%左右,情况也较为乐观。
但是当程序跑到4个小时候,也大概读取了3万条左右的url之后,程序也崩溃掉了。
从nohup日志中看,错误信息如下:
Attempt to free non-existent shared string 'et', Perl interpreter: 0x2aaac58a2d80 during global destruction.
Unbalanced string table refcount: (1) for "[这里是一堆乱码]" during global destruction.

大意就是说,perl尝试释放一个不存在的共享变量,导致中断错误退出。
从我的使用经验来看,perl的多线程非常不稳定,不如使用fork,效率应该差不多。

作者: youzhengchuan   发布时间: 2011-05-31

看 perlthrtut 关于 线程安全module的描述

换多进程+异步非阻塞吧

作者: hitsubunnu   发布时间: 2011-05-31

回复 hitsubunnu


    有连接吗

作者: youzhengchuan   发布时间: 2011-05-31

那是 hash key 的缓存策略,用个hash 表保存所有的 key 不是共享变量。这个是内部错误,对用户来讲应该是不可见的。原因是线程结束释放解释器时出错了,算个bug吧。不过跑了4个小时才出现, 'et' 有什么特殊的难道

作者: zhlong8   发布时间: 2011-05-31

回复 youzhengchuan

http://perldoc.perl.org/5.8.9/perlthrtut.html

个人认为 perl的 threads是个死胡同 你无法掌握他会在什么时候出现什么状况。。

作者: hitsubunnu   发布时间: 2011-05-31