Perl线程开发概述
在官方的5.38版本的手册中,threads、threads::shared模块在'Pragmatic Modules'中;Thread、Thread::Queue、Thread::Semaphore位于'Standard Modules'中。

线程模型
分为两类:5005threads与ithreads,5005threads与ithreads是v5.005版本中引入的线程模型,问题多多,已经在5.10后从Perl被ithreads所取代。ithreads系'interpreter threads'的简写。因此Thread系的用户前端在5.10之后与threads同由基于ithreads模型,因此只要使用了相应的新版本就可以确定使用了那种线程模型。当然,不管那种模型,官方都不鼓励使用线程。
关于Perl官方不鼓励使用线程
从《处理器(CPU)芯片曝重大缺陷》中可以得知,OpenBSD开发人员已经证实了CPU的乱序执行会导致安全问题,超线程在技术上是不可靠的。
上面从大的方面(硬件)说了线程安全,现回到Perl,相信大家在不少的参考资料中看到函数的介绍中有这样的说明:是否线程安全;就是说该函数在线程环境中是否能安全地执行,结果是否与预期一致,否则就不能算是线程安全的了。
Perl基于解释器的线程并不是人们所预期的高效、轻量级的多任务系统。不幸的是,这些线程的设计使它们容易被滥用,只有少数人拥有正确使用它们或提供帮助的专业知识。因此,官方建议避免在Perl中使用基于解释器的线程。
那如果需要多任务并发执行,有没有好的推荐呢。
其实官方更加鼓励使用多进程方式,这也是Unix/Linux所推荐的方式(Windows下推荐使用线程),因其上进程的管理是比较轻量的,开销并没有想像中那么大。另外还有事件、协程框架的助力多任务并发的场景。但线程有其存在的意义且历史悠久(存在即合理),还是有其不少的场景,它毕竟内置于Perl之中。
Perl线程,至少在perl5中,并不是真正的线程——它们可能更像是由Perl运行时控制的通道,而不是POSIX或其他线程。一般来说,使用fork而不是perl线程可以获得更好的性能。
不少Perl的参考书籍中没有对线程进行提及,或有所提及,但篇幅甚小。
Instead of using threads, you might want to look into AnyEvent::Socket, or Coro::Socket, or POE, or Parallel::ForkManager.
Perl线程的参考资料
最好的介绍当属其官方手册页。
官方的模块主页中,可见线程模块的链接位置。
《Perl多线程的编写资料》就是来源于《Perl语言编程》第三版中的第17章节,而该书的第四版中已经没有对线程的介绍了。
《Perl中的线程》系IBM的高工所写,本站幸有收录,与上互有补充。
《Perl线程开发过程中的经验》介绍了在使用线程的过程中的经验谈。
《Perl多线程示例》中引入了相当的实例,直接可以上手简单的场景。
《Perl Thread 中文参考》同样转自高工所加工的文章,主要对Perl所涉及的相关模块的介绍。
鉴于上述的现状,线程在Perl中的使用还是比较有限,建议使用其最核心的功能;当然,包括多进程、事件驱动、协程在内的多任务并发体系都涉及到对资源(硬件、内外部)的过量使用,都不可避免的需要协调对资源的使用,在共享资源的情况下尽可能的完成并发任务(节省时间或充分利用计算资源)。
资源共享就必须要加锁,加锁就会制约并发,并发受限就会影响执行效果,坏的情况可能导致不如串行执行的效果或死锁导致应用停摆(这是包括线程在内所有并发执行编程方式来带来的副作用:对内、外资源协调所带来的开销)。当然也有不需要考虑资源加锁的情况,各个子线程并发执行且相互间相对独立的情况(如并行的ping主机或主机上的各个端口存活),但这种情形比较少见。那有没有将加锁的这部分'外包'出来呢,当然是可以的,可以将加锁这部分放在redis中,其中的List(列表)类型就是一种(安全)队列,可助线程间实现弱相关关系。
只需要少量的这个外部资源,可解决相当部分的共享问题,还是非常值得的。
关于线程在Linux的情况可以参考《Linux线程技术发展概念》
Thread::Queue真是一个不错的模块,将队列机制用到了极致,也为固定线程编写提供了很好的帮助。
部分关键示例
use threads;
use Thread::Queue qw(); # Version 3.01+ required
my $NUM_WORKERS = 5;
sub worker {
my ($job) = @_;
...
}
my $q = Thread::Queue->new();
my @workers;
for (1..$NUM_WORKERS) {
push @workers, async {
while (defined(my $job = $q->dequeue())) {
worker($job);
}
};
}
$q->enqueue($_) for @jobs; # Send work
$q->end(); # Tell workers they're done.
$_->join() for @workers; # Wait for the workers to finish.
#start some threads
for (1 .. $nthreads){
threads->create(\&worker);
}
#wait for threads to all finish processing.
foreach my $thr (threads->list()){
$thr->join();
}
lock and cond_wait的用法示例
use threads;
use threads::shared;
my $flag :shared;
$flag = 0;
my $t1 = threads->create(sub {
{ lock($flag); cond_wait($flag) until $flag == 1; }
print "Thread received signal!\n";
});
my $t2=threads->create(sub {
{ print "threads 2 for flag setting.\n"; lock($flag); $flag = 1; cond_signal($flag); } # Signal the thread
print "threads 2 setting over,value of flag is:$flag.\n";
});
$t1->join();
$t2->join();
>
$ time perl thrd.share2.pl
threads 2 for flag setting.
Thread received signal!
threads 2 setting over,value of flag is:1.
real 0m0.008s
线程中的错误处理(Error Handling In Threads)
就像在单线程程序中一样,错误处理是用Perl编写健壮的多线程应用程序不可或缺的一部分。当多个线程同时运行时,确保每个线程正确处理其错误变得至关重要。
Propagating Errors To Main Thread
Using Thread-Specific Error Handlers
线程中的错误处理方式类似于典型Perl脚本中的处理方式。eval函数通常用于捕获异常并防止它们终止程序。
use threads;
my $t = threads->create(sub {
eval {
# Potential error-prone code
};
if ($@) {
print "Caught an error: $@\n";
}
});
$t->join();
在本例中,eval块内易出错代码中的错误将被捕获,并打印错误消息。
Propagating Errors To Main Thread
有时将错误从子线程传播到主线程是很有用的。为了实现这一点,可以从子线程返回错误,然后将其抛出到主线程中:
use v5.20;
use Redis;
use threads;
my $t0 = threads->create(sub { print "someone will connect to redis...\n"; });
my $t1 = threads->create(sub {
my $error;
eval{
# Potential error-prone code
my $rds=Redis->new(server => '192.168.0.3:6379',password=>'errorPwd');
};
$error = $@ if $@;
#print "Error:$@" if $@;
return $error;
});
my $error = $t1->join();
$t0->join();
die $error if $error;
在这段代码中,如果子线程中发生错误,就会捕获并返回到主线程。然后主线程使用die重新抛出错误。
Using Thread-Specific Error Handlers
Perl also supports installing thread-specific error handlers using $SIG{__DIE__}. This allows different threads to handle errors in different ways:
use v5.20;
use Redis;
use threads;
my $t0 = threads->create(sub { print "someone will connect to redis...\n"; });
my $t1 = threads->create(sub {
$SIG{__DIE__} = sub { print "Thread-specific error: $_[0]\n"; };
my $rds=Redis->new(server => '192.168.0.3:6379',password=>'errorPwd');
#die "Connect2Redis Error!" unless $rds;
# Some code
});
$t0->join();
$t1->join();
在这段代码中,子线程使用了一个特定于线程的错误处理程序,用于打印自定义错误消息。
多线程Perl程序中正确的错误处理可以确保一个线程的问题不会干扰其他线程的执行。它还有助于使程序更加健壮,更易于调试。
首先要明白创建线程会带来开销,每个线程都携带一定量的系统和内存开销。创建大量线程可能会消耗大量的系统资源,从而导致整体性能降低。其次,共享变量的过度锁定和解锁会降低性能。每次获取或释放锁时,都会产生一些相关开销。过多的锁定可能会导致线程争用,即多个线程争夺共享资源,从而导致延迟。
一行式线程测试本机线程性能
time perl -Mthreads -e'threads->new(sub{threads->exit})->detach for 1 .. 5E3'
Perl的最新版本提供了线程支持,通过执行perl -V:usethreads检查阁下所使用系统是否支持它。
$ perl -V:usethreads
usethreads='define'
在终端下使用命令:perldoc threads 可以在命令行中查看到相关的参考说明。
Perl多进程和多线程技术一览
在IT产品系统测试的自动化项目中,经常有并行处理多个子任务的需求,为了提高测试效率,就需要用到Perl多进程或者多线程编程。
Perl语言是一种非常强大的脚本语言,其广泛应用于系统维护,CGI(Common Gateway Interface)编程,数据库编程和自动化测试中。多任务和并发处理一度被看作是判定优异操作系统的一个特性;同样任何优秀从而流行的编程语言都会有并发的应用,而且都有各自的实现方法。Perl最开始在并发方面的应用就是其多进程方式(这也是Unix平台推荐的方式:进程开销略大但稳定易用)。
Perl多进程的用户接口是fork()函数以及对系统fork函数封装的一些module。在使用Perl语言编程时,如果一个任务的某一个段可以或者需要并发很多执行,那么就会使用Perl多进程编程,例如同时向数据库递交多个记录的查询,同时完成多个系统信息的收集等等。
Perl多进程是这样实现的:主进程每fork一个子进程,会把当前(主进程的)内存空间的所有变量都复制一份传到新的进程里面,达到数据共享的目的。此外,主进程和子进程可以通过信号、管道等来通信。在处理并发的方案中,Perl多进程依靠内存空间独享提供了优秀的容错性和健壮性。一个Perl多进程的系统不会由于其中一个进程的状态不良而崩溃,每个进程都相对独立地运作,很少会相互影响。
内存空间独享也造就了Perl多进程不可避免的劣势:资源负荷以及通信复杂,对于Perl来说,每个子进程都可以看作主进程的拷贝,这多少有些内存浪费,而且主进程的关键变量如果是“浅复制”到子进程的话,将会带来一些意想不到的错误。另外,进程的创建和回收会带来许多额外的负载,因此应当尽量避免频繁地创建进程。多进程之间的通信方式有socket,管道,信号量等。在Linux平台上,对于进程间大量信息的交互情况,最常用的是文件;这在用户空间进程和系统内核空间进程之间的通信中的最为常用。
在谈起Perl多进程的时候,不可避免的要说说它与Perl多线程的关系。
Perl多进程和多线程的关系
如果需要执行一些各异的小任务,可能数量较多但生存周期都比较短,或者它们可能共享大量数据,只有小部分的变量是自身的。这个时候,很自然就想到了多线程。
从Perl v5.005开始,多线程的概念被引入Perl语言了;不过仅仅是概念而已。在Perl v5.005中,线程模型默认共享所有的数据,而且数据的共享访问需要明确的同步操作。这个模型被称为“5005threads”,其实就是Perl多进程,只不过换了个“多线程”的概念。
到了Perl5.6,一个新的线程模型引入了,各个线程的数据默认是私有的,而且共享数据的访问需要显式地调用相关的函数来保证同步。这个模型被称为“ithreads”。在Perl5.6中的ithreads模型并没有提供一些API供用户调用,仅作为一个内部的API供扩展使用。到了Perl v5.8,ithreads模型通过Perl的threads模块供用户使用,但是Perl v5.8同时也保留了5005threads模型。用户可以通过配置来选择使用哪一种模型。
在Perl5.10中,5005threads模型就不能再使用了,已从Perl解释器中移除了。所以要使用真正意义的、稳定的多线程模块,建议使用Perl v5.10以上的解释器。至此,Perl多进程和多线程应该是区别开来了。
Perl的threads模块提供的多线程,应用于经典的多线程编程例如Socket-Server编程、硬件驱动或者适配层编程十分有效,然而对于非线程安全的一些函数或者模块调用就无能为力了,会导致不可预知的错误或者主进程的崩溃。

线程模型
分为两类:5005threads与ithreads,5005threads与ithreads是v5.005版本中引入的线程模型,问题多多,已经在5.10后从Perl被ithreads所取代。ithreads系'interpreter threads'的简写。因此Thread系的用户前端在5.10之后与threads同由基于ithreads模型,因此只要使用了相应的新版本就可以确定使用了那种线程模型。当然,不管那种模型,官方都不鼓励使用线程。
关于Perl官方不鼓励使用线程
从《处理器(CPU)芯片曝重大缺陷》中可以得知,OpenBSD开发人员已经证实了CPU的乱序执行会导致安全问题,超线程在技术上是不可靠的。
上面从大的方面(硬件)说了线程安全,现回到Perl,相信大家在不少的参考资料中看到函数的介绍中有这样的说明:是否线程安全;就是说该函数在线程环境中是否能安全地执行,结果是否与预期一致,否则就不能算是线程安全的了。
Perl基于解释器的线程并不是人们所预期的高效、轻量级的多任务系统。不幸的是,这些线程的设计使它们容易被滥用,只有少数人拥有正确使用它们或提供帮助的专业知识。因此,官方建议避免在Perl中使用基于解释器的线程。
那如果需要多任务并发执行,有没有好的推荐呢。
其实官方更加鼓励使用多进程方式,这也是Unix/Linux所推荐的方式(Windows下推荐使用线程),因其上进程的管理是比较轻量的,开销并没有想像中那么大。另外还有事件、协程框架的助力多任务并发的场景。但线程有其存在的意义且历史悠久(存在即合理),还是有其不少的场景,它毕竟内置于Perl之中。
Perl线程,至少在perl5中,并不是真正的线程——它们可能更像是由Perl运行时控制的通道,而不是POSIX或其他线程。一般来说,使用fork而不是perl线程可以获得更好的性能。
不少Perl的参考书籍中没有对线程进行提及,或有所提及,但篇幅甚小。
Instead of using threads, you might want to look into AnyEvent::Socket, or Coro::Socket, or POE, or Parallel::ForkManager.
Perl线程的参考资料
最好的介绍当属其官方手册页。
官方的模块主页中,可见线程模块的链接位置。
《Perl多线程的编写资料》就是来源于《Perl语言编程》第三版中的第17章节,而该书的第四版中已经没有对线程的介绍了。
《Perl中的线程》系IBM的高工所写,本站幸有收录,与上互有补充。
《Perl线程开发过程中的经验》介绍了在使用线程的过程中的经验谈。
《Perl多线程示例》中引入了相当的实例,直接可以上手简单的场景。
《Perl Thread 中文参考》同样转自高工所加工的文章,主要对Perl所涉及的相关模块的介绍。
鉴于上述的现状,线程在Perl中的使用还是比较有限,建议使用其最核心的功能;当然,包括多进程、事件驱动、协程在内的多任务并发体系都涉及到对资源(硬件、内外部)的过量使用,都不可避免的需要协调对资源的使用,在共享资源的情况下尽可能的完成并发任务(节省时间或充分利用计算资源)。
资源共享就必须要加锁,加锁就会制约并发,并发受限就会影响执行效果,坏的情况可能导致不如串行执行的效果或死锁导致应用停摆(这是包括线程在内所有并发执行编程方式来带来的副作用:对内、外资源协调所带来的开销)。当然也有不需要考虑资源加锁的情况,各个子线程并发执行且相互间相对独立的情况(如并行的ping主机或主机上的各个端口存活),但这种情形比较少见。那有没有将加锁的这部分'外包'出来呢,当然是可以的,可以将加锁这部分放在redis中,其中的List(列表)类型就是一种(安全)队列,可助线程间实现弱相关关系。
只需要少量的这个外部资源,可解决相当部分的共享问题,还是非常值得的。
关于线程在Linux的情况可以参考《Linux线程技术发展概念》
Thread::Queue真是一个不错的模块,将队列机制用到了极致,也为固定线程编写提供了很好的帮助。
部分关键示例
use threads;
use Thread::Queue qw(); # Version 3.01+ required
my $NUM_WORKERS = 5;
sub worker {
my ($job) = @_;
...
}
my $q = Thread::Queue->new();
my @workers;
for (1..$NUM_WORKERS) {
push @workers, async {
while (defined(my $job = $q->dequeue())) {
worker($job);
}
};
}
$q->enqueue($_) for @jobs; # Send work
$q->end(); # Tell workers they're done.
$_->join() for @workers; # Wait for the workers to finish.
#start some threads
for (1 .. $nthreads){
threads->create(\&worker);
}
#wait for threads to all finish processing.
foreach my $thr (threads->list()){
$thr->join();
}
lock and cond_wait的用法示例
use threads;
use threads::shared;
my $flag :shared;
$flag = 0;
my $t1 = threads->create(sub {
{ lock($flag); cond_wait($flag) until $flag == 1; }
print "Thread received signal!\n";
});
my $t2=threads->create(sub {
{ print "threads 2 for flag setting.\n"; lock($flag); $flag = 1; cond_signal($flag); } # Signal the thread
print "threads 2 setting over,value of flag is:$flag.\n";
});
$t1->join();
$t2->join();
>
$ time perl thrd.share2.pl
threads 2 for flag setting.
Thread received signal!
threads 2 setting over,value of flag is:1.
real 0m0.008s
线程中的错误处理(Error Handling In Threads)
就像在单线程程序中一样,错误处理是用Perl编写健壮的多线程应用程序不可或缺的一部分。当多个线程同时运行时,确保每个线程正确处理其错误变得至关重要。
Propagating Errors To Main Thread
Using Thread-Specific Error Handlers
线程中的错误处理方式类似于典型Perl脚本中的处理方式。eval函数通常用于捕获异常并防止它们终止程序。
use threads;
my $t = threads->create(sub {
eval {
# Potential error-prone code
};
if ($@) {
print "Caught an error: $@\n";
}
});
$t->join();
在本例中,eval块内易出错代码中的错误将被捕获,并打印错误消息。
Propagating Errors To Main Thread
有时将错误从子线程传播到主线程是很有用的。为了实现这一点,可以从子线程返回错误,然后将其抛出到主线程中:
use v5.20;
use Redis;
use threads;
my $t0 = threads->create(sub { print "someone will connect to redis...\n"; });
my $t1 = threads->create(sub {
my $error;
eval{
# Potential error-prone code
my $rds=Redis->new(server => '192.168.0.3:6379',password=>'errorPwd');
};
$error = $@ if $@;
#print "Error:$@" if $@;
return $error;
});
my $error = $t1->join();
$t0->join();
die $error if $error;
在这段代码中,如果子线程中发生错误,就会捕获并返回到主线程。然后主线程使用die重新抛出错误。
Using Thread-Specific Error Handlers
Perl also supports installing thread-specific error handlers using $SIG{__DIE__}. This allows different threads to handle errors in different ways:
use v5.20;
use Redis;
use threads;
my $t0 = threads->create(sub { print "someone will connect to redis...\n"; });
my $t1 = threads->create(sub {
$SIG{__DIE__} = sub { print "Thread-specific error: $_[0]\n"; };
my $rds=Redis->new(server => '192.168.0.3:6379',password=>'errorPwd');
#die "Connect2Redis Error!" unless $rds;
# Some code
});
$t0->join();
$t1->join();
在这段代码中,子线程使用了一个特定于线程的错误处理程序,用于打印自定义错误消息。
多线程Perl程序中正确的错误处理可以确保一个线程的问题不会干扰其他线程的执行。它还有助于使程序更加健壮,更易于调试。
首先要明白创建线程会带来开销,每个线程都携带一定量的系统和内存开销。创建大量线程可能会消耗大量的系统资源,从而导致整体性能降低。其次,共享变量的过度锁定和解锁会降低性能。每次获取或释放锁时,都会产生一些相关开销。过多的锁定可能会导致线程争用,即多个线程争夺共享资源,从而导致延迟。
一行式线程测试本机线程性能
time perl -Mthreads -e'threads->new(sub{threads->exit})->detach for 1 .. 5E3'
Perl的最新版本提供了线程支持,通过执行perl -V:usethreads检查阁下所使用系统是否支持它。
$ perl -V:usethreads
usethreads='define'
在终端下使用命令:perldoc threads 可以在命令行中查看到相关的参考说明。
Perl多进程和多线程技术一览
在IT产品系统测试的自动化项目中,经常有并行处理多个子任务的需求,为了提高测试效率,就需要用到Perl多进程或者多线程编程。
Perl语言是一种非常强大的脚本语言,其广泛应用于系统维护,CGI(Common Gateway Interface)编程,数据库编程和自动化测试中。多任务和并发处理一度被看作是判定优异操作系统的一个特性;同样任何优秀从而流行的编程语言都会有并发的应用,而且都有各自的实现方法。Perl最开始在并发方面的应用就是其多进程方式(这也是Unix平台推荐的方式:进程开销略大但稳定易用)。
Perl多进程的用户接口是fork()函数以及对系统fork函数封装的一些module。在使用Perl语言编程时,如果一个任务的某一个段可以或者需要并发很多执行,那么就会使用Perl多进程编程,例如同时向数据库递交多个记录的查询,同时完成多个系统信息的收集等等。
Perl多进程是这样实现的:主进程每fork一个子进程,会把当前(主进程的)内存空间的所有变量都复制一份传到新的进程里面,达到数据共享的目的。此外,主进程和子进程可以通过信号、管道等来通信。在处理并发的方案中,Perl多进程依靠内存空间独享提供了优秀的容错性和健壮性。一个Perl多进程的系统不会由于其中一个进程的状态不良而崩溃,每个进程都相对独立地运作,很少会相互影响。
内存空间独享也造就了Perl多进程不可避免的劣势:资源负荷以及通信复杂,对于Perl来说,每个子进程都可以看作主进程的拷贝,这多少有些内存浪费,而且主进程的关键变量如果是“浅复制”到子进程的话,将会带来一些意想不到的错误。另外,进程的创建和回收会带来许多额外的负载,因此应当尽量避免频繁地创建进程。多进程之间的通信方式有socket,管道,信号量等。在Linux平台上,对于进程间大量信息的交互情况,最常用的是文件;这在用户空间进程和系统内核空间进程之间的通信中的最为常用。
在谈起Perl多进程的时候,不可避免的要说说它与Perl多线程的关系。
Perl多进程和多线程的关系
如果需要执行一些各异的小任务,可能数量较多但生存周期都比较短,或者它们可能共享大量数据,只有小部分的变量是自身的。这个时候,很自然就想到了多线程。
从Perl v5.005开始,多线程的概念被引入Perl语言了;不过仅仅是概念而已。在Perl v5.005中,线程模型默认共享所有的数据,而且数据的共享访问需要明确的同步操作。这个模型被称为“5005threads”,其实就是Perl多进程,只不过换了个“多线程”的概念。
到了Perl5.6,一个新的线程模型引入了,各个线程的数据默认是私有的,而且共享数据的访问需要显式地调用相关的函数来保证同步。这个模型被称为“ithreads”。在Perl5.6中的ithreads模型并没有提供一些API供用户调用,仅作为一个内部的API供扩展使用。到了Perl v5.8,ithreads模型通过Perl的threads模块供用户使用,但是Perl v5.8同时也保留了5005threads模型。用户可以通过配置来选择使用哪一种模型。
在Perl5.10中,5005threads模型就不能再使用了,已从Perl解释器中移除了。所以要使用真正意义的、稳定的多线程模块,建议使用Perl v5.10以上的解释器。至此,Perl多进程和多线程应该是区别开来了。
Perl的threads模块提供的多线程,应用于经典的多线程编程例如Socket-Server编程、硬件驱动或者适配层编程十分有效,然而对于非线程安全的一些函数或者模块调用就无能为力了,会导致不可预知的错误或者主进程的崩溃。