创建线程线程作为 Perl 中的一种实体,其一生可以粗略的分为创建,运行与退出这三个阶段。创建使得线程从无到有,运行则是线程完成其主要工作的阶段,退出自然就是指线程的消亡。线程的运行和普通函数的执行非常类似,有其入口参数,一段特定的代码流程以及执行完毕后返回的一个或一组结果,唯一与普通函数调用的不同之处就在于新建线程的执行与当前线程的执行是并行的。
Perl 里创建一个新的线程非常简单,主要有两种方法,他们分别是:
- 使用 threads 包的 create() 方法,例如
清单 3. 通过 create() 方法创建线程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| 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" );
|
清单 4. 通过 async{} 块创建线程1
2
3
4
5
6
7
| #!/usr/bin/perl
#
use threads;
my $t4 = async{
printf("Hello thread!\n");
};
|
join 方法和 detach 方法线程一旦被成功创建,它就立刻开始运行了,这个时候你面临两种选择,分别是 join 或者 detach 这个新建线程。当然你也可以什么都不做,不过这可不是一个好习惯,后面我们会解释这是为什么。
我们先来看看 join 方法, 这也许是大多数情况下你想要的。从字面上来理解,join 就是把新创建的线程结合到当前的主线程中来,把它当成是主线程的一部分,使他们合二为一。join 会触发两个动作,首先,主线程会索取新建线程执行结束以后的返回值;其次,新建线程在执行完毕并返回结果以后会自动释放它自己所占用的系统资源。例如
清单 5. 使用 join() 方法收割新建线程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| #!/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 为止,这在很大程度上浪费了宝贵的系统资源。因此,join 新建线程的最好时机应该是在它刚刚执行完毕的时候,这样既不会阻塞当前线程的执行,又可以及时释放新建线程所占用的系统资源。
我们再来看看 detach 方法,这也许是最省心省力的处理方法了。从字面上来理解,detach 就是把新创建的线程与当前的主线程剥离开来,让它从此和主线程无关。当你使用 detach 方法的时候,表明主线程并不关心新建线程执行以后返回的结果,新建线程执行完毕后 Perl 会自动释放它所占用的资源。例如
清单 6. 使用 detach() 方法剥离线程1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #!/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);
|
一个新建线程一旦被 detach 以后,就无法再 join 了。当你使用 detach 方法剥离线程的时候,有一点需要特别注意,那就是你需要保证被创建的线程先于主线程结束,否则你创建的线程会被迫结束,除非这种结果正是你想要的,否则这也许会造成异常情况的出现,并增加程序调试的难度。
本节的开始我们提到,新线程被创建以后,如果既不 join,也不 detach 不是一个好习惯,这是因为除非明确地调用 detach 方法剥离线程,Perl 会认为你也许要在将来的某一个时间点调用 join,所以新建线程的返回值会一直被保存在内存中以备不时之需,它所占用的系统资源也一直不会得到释放。然而实际上,你打算什么也不做,因此宝贵的系统资源直到整个 Perl 应用结束时才被释放。同时,由于你即没有调用 join 有没有调用 detach,应用结束时 Perl 还会返回给你一个线程非正常结束的警告。
线程的消亡大多数情况下,你希望你创建的线程正常退出,这就意味着线程所对应的函数体在执行完毕后返回并释放资源。例如在清单 5 的示例中,新建线程被 join 以后的退出过程。可是,如果由于 detach 不当或者由于主线因某些意外的异常提前结束了,尽管它所创建的线程可能尚未执行完毕,但是他们还是会被强制中止,正所谓皮之不存,毛将焉附。这时你也许会得到一个类似于“Perl exited with active threads”的警告。
当然,你也可以显示地调用 exit() 方法来结束一个线程,不过值得注意的是,默认情况下,如果你在一个线程中调用了 exit() 方法, 其他线程都会随之一起结束,在很多情况下,这也许不是你想要的,如果你希望 exit() 方法只在调用它的线程内生效,那么你在创建该线程的时候就需要设置’ exit ’ => ’ thread_only ’。例如
清单 7. 为某个线程设置’ exit ’ => ’ thread_only ’属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| #!/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 包的时候设置这个属性在全局范围内有效,例如
清单 8. 设置’ exit ’ => ’ thread_only ’为全局属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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();
|
|