首页 > 代码库 > perl多线程

perl多线程

2000 年 5 月发布的 Perl v5.6.0 中开始引入了一个全新的线程模型,即 interpreter threads, 或称为 ithreads,也正是在这个版本的发布申明中第一次提出了 5005threads 线程模型将来可能会被禁用的问题。

perl线程的生命周期

创建线程

线程作为Perl中的一种实体,其周期包括创建,运行和退出。

Perl里创建一个新的线程非常简单,主要有两种方法,他们分别是:

1、使用threads包的create方法

use threads; 
 sub say_hello 
 { 
    printf("Hello thread! @_.\n"); 
    return( rand(10) ); 
 } 

 my $t1 = threads->create( \&say_hello, "param1", "param2" ); 
 my $t2 = threads->create( "say_hello", "param3", "param4" ); 
 my $t3 = threads->create( 
 sub { 
        printf("Hello thread! @_\n"); 
        return( rand(10) ); 
 },  "param5", "param6" );



2、通过async{}块创建匿名线程

#!/usr/bin/perl 
# 
use threads; 
my $t4 = async{ 
    printf("Hello thread!\n"); 
};



  join方法和detach方法

线程一旦被成功创建,它就立刻开始运行,面临两种选择join或者detach方法。

从字面上,join就是把新创建的线程结合到当前的主线程中来,把它当成是主线程的一部分,使他们合二为一。join会出发两个动作,首先,主线程会索取新建线程执行结束以后的返回值,其次,新建线程在执行完毕后并返回结果以后会自动释放它自己所占用的系统资源。

#!/usr/bin/perl 
 # 
 use threads; 

 sub func { 
 sleep(1); 
 return(rand(10)); 
 } 

 my $t1 = threads->create( \&func ); 
 my $t2 = threads->create( \&func ); 

 printf("do something in the main thread\n"); 

 my $t1_res = $t1->join(); 
 my $t2_res = $t2->join(); 
 printf("t1_res = $t1_res\nt2_res = $t2_res\n");



如果调用 join 方法太早,新建线程尚未执行完毕,自然就无法返回任何结果,那么这个时候,主线程就不得不被阻塞,直到新建线程执行完毕之后,才能获得返回值,然后资源会被释放join 才能结束,这在很大程度上破话了线程之间的并行性。相反,如果调用 join 方法太晚,新建线程早已执行完毕,由于一直没有机会返回结果,它所占用的资源就一直无法得到释放,直到被 join 为止,这在很大程度上浪费了宝贵的系统资源。因此,join 新建线程的最好时机应该是在它刚刚执行完毕的时候,这样既不会阻塞当前线程的执行,又可以及时释放新建线程所占用的系统资源。??????

我们再来看看 detach 方法,这也许是最省心省力的处理方法了。从字面上来理解,detach 就是把新创建的线程与当前的主线程剥离开来,让它从此和主线程无关。当你使用 detach 方法的时候,表明主线程并不关心新建线程执行以后返回的结果,新建线程执行完毕后 Perl 会自动释放它所占用的资源。

#!/usr/bin/perl 
 # 
 use threads; 
 use Config; 

 sub say_hello { 
 my ( $name ) = @_; 
 printf("Hello World! I am $name.\n"); 
 } 

 my $t1 = threads->create( \&say_hello, "Alex" ); 
 $t1->detach(); 

 printf("doing something in main thread\n"); 
 sleep(1);



本节的开始我们提到,新线程被创建以后,如果既不 join,也不 detach 不是一个好习惯,这是因为除非明确地调用 detach 方法剥离线程,Perl 会认为你也许要在将来的某一个时间点调用 join,所以新建线程的返回值会一直被保存在内存中以备不时之需,它所占用的系统资源也一直不会得到释放。然而实际上,你打算什么也不做,因此宝贵的系统资源直到整个 Perl 应用结束时才被释放。同时,由于你即没有调用 join 有没有调用 detach,应用结束时 Perl 还会返回给你一个线程非正常结束的警告。

线程的消亡

大多数情况下,你希望你创建的线程正常退出,这就意味着线程所对应的函数体在执行完毕后返回并释放资源。例如在清单 5 的示例中,新建线程被 join 以后的退出过程。可是,如果由于 detach 不当或者由于主线因某些意外的异常提前结束了,尽管它所创建的线程可能尚未执行完毕,但是他们还是会被强制中止,正所谓皮之不存,毛将焉附。这时你也许会得到一个类似于“Perl exited with active threads”的警告。

当然,你也可以显示地调用 exit() 方法来结束一个线程,不过值得注意的是,默认情况下,如果你在一个线程中调用了 exit() 方法, 其他线程都会随之一起结束,在很多情况下,这也许不是你想要的,如果你希望 exit() 方法只在调用它的线程内生效,那么你在创建该线程的时候就需要设置’ exit ’ => ’ thread_only ’。例如

#!/usr/bin/perl 
 # 
 use threads; 

 sub say_hello { 
 printf("Hello thread! @_.\n"); 
 sleep(10); 
 printf("Bye\n"); 
 } 

 sub quick_exit { 
 printf("I will be exit in no time\n"); 
 exit(1); 
 } 

 my $t1 = threads->create( \&say_hello, "param1", "param2" ); 
 my $t2 = threads->create( {‘exit‘=>‘thread_only‘}, \&quick_exit ); 

 $t1->join(); 
 $t2->join();



如果你希望每个线程的 exit 方法都只对自己有效,那么在每次创建一个新线程的时候都去要显式设置’ exit ’ => ’ thread_only ’属性显然有些麻烦,你也可以在引入 threads 包的时候设置这个属性在全局范围内有效,例如

use threads (‘exit‘ => ‘threads_only‘); 

 sub func { 
 ... 
 if( $condition ) { 
 exit(1); 
 } 
 } 

 my $t1 = threads->create( \&func ); 
 my $t2 = threads->create( \&func ); 

 $t1->join(); 
 $t2->join();



共享与同步

threads::shared

和现在大多数线程模型不同,在 Perl ithreads 线程模型中,默认情况下任何数据结构都不是共享的。当一个新线程被创建以后,它就已经包含了当前所有数据结构的一份私有拷贝,新建线程中对这份拷贝的数据结构的任何操作都不会在其他线程中有效。因此,如果需要使用任何共享的数据,都必须显式地申明。threads::shared 包可以用来实现线程间共享数据的目的。

#!/usr/bin/perl 
# 

 use threads; 
 use threads::shared; 
 use strict; 

 my $var   :shared  = 0;       # use :share tag to define 
 my @array :shared = (); # use :share tag to define 
 my %hash = (); 
 share(%hash);                  # use share() funtion to define 


 sub start { 
 $var = 100; 

 @array[0] = 200; 
 @array[1] = 201; 

 $hash{‘1‘} = 301; 
 $hash{‘2‘} = 302; 
 } 

 sub verify { 
    sleep(1);                      # make sure thread t1 execute firstly 
    printf("var = $var\n");     # var=100 

 for(my $i = 0; $i < scalar(@array); $i++) { 
        printf("array[$i] = $array[$i]\n");    # array[0]=200; array[1]=201 
 } 
 foreach my $key ( sort( keys(%hash) ) ) { 
     printf("hash{$key} = $hash{$key}\n"); # hash{1}=301; hash{2}=302 
 } 
 } 

 my $t1 = threads->create( \&start ); 
 my $t2 = threads->create( \&verify ); 

 $t1->join(); 
 $t2->join();



锁:

多线程既然有了共享数据,那么就必须对共享数据进行小心地访问

Perl创建线程有两种方式,正常通过threads->create创建线程,用async创建一个调用匿名过程的线程;

线程共享变量需要使用threads::shared,共享变量只能存储scalar,共享变量的引用,如果存储List Hash引用需要shared_clone([@list])shared_clone({%hash})。

线程创建后最好使用join或者detach,否则在退出时会有warning。

线程的join方式,使用threads中提供的函数接口,可以做到及时释放资源。