threads - 基于 Perl 解释器的线程
本文档描述了 threads 版本 2.36
Perl 提供的“基于解释器的线程”并非人们期望或希望的快速、轻量级多任务处理系统。线程的实现方式使其易于误用。很少有人知道如何正确使用它们,或者能够提供帮助。
在 perl 中使用基于解释器的线程已被正式不建议。
use threads ('yield',
'stack_size' => 64*4096,
'exit' => 'threads_only',
'stringify');
sub start_thread {
my @args = @_;
print('Thread started: ', join(' ', @args), "\n");
}
my $thr = threads->create('start_thread', 'argument');
$thr->join();
threads->create(sub { print("I am a thread\n"); })->join();
my $thr2 = async { foreach (@files) { ... } };
$thr2->join();
if (my $err = $thr2->error()) {
warn("Thread error: $err\n");
}
# Invoke thread in list context (implicit) so it can return a list
my ($thr) = threads->create(sub { return (qw/a b c/); });
# or specify list context explicitly
my $thr = threads->create({'context' => 'list'},
sub { return (qw/a b c/); });
my @results = $thr->join();
$thr->detach();
# Get a thread's object
$thr = threads->self();
$thr = threads->object($tid);
# Get a thread's ID
$tid = threads->tid();
$tid = $thr->tid();
$tid = "$thr";
# Give other threads a chance to run
threads->yield();
yield();
# Lists of non-detached threads
my @threads = threads->list();
my $thread_count = threads->list();
my @running = threads->list(threads::running);
my @joinable = threads->list(threads::joinable);
# Test thread objects
if ($thr1 == $thr2) {
...
}
# Manage thread stack size
$stack_size = threads->get_stack_size();
$old_size = threads->set_stack_size(32*4096);
# Create a thread with a specific context and stack size
my $thr = threads->create({ 'context' => 'list',
'stack_size' => 32*4096,
'exit' => 'thread_only' },
\&foo);
# Get thread's context
my $wantarray = $thr->wantarray();
# Check thread's state
if ($thr->is_running()) {
sleep(1);
}
if ($thr->is_joinable()) {
$thr->join();
}
# Send a signal to a thread
$thr->kill('SIGUSR1');
# Exit a thread
threads->exit();
从 Perl 5.8 开始,可以使用名为 *解释器线程* 的模型进行线程编程,该模型为每个线程提供一个新的 Perl 解释器,并且默认情况下,线程之间不会共享任何数据或状态信息。
(在 Perl 5.8 之前,*5005threads* 可通过 Thread.pm
API 使用。此线程模型已被弃用,并从 Perl 5.10.0 开始被移除。)
如上所述,默认情况下,所有变量都是线程本地变量。要使用共享变量,您还需要加载 threads::shared
use threads;
use threads::shared;
加载 threads::shared 时,您必须在 use threads::shared
之前 use threads
。(如果您反过来操作,threads
会发出警告。)
强烈建议您尽早使用 use threads
在脚本中启用线程。
如果需要,可以编写脚本以便在支持线程和不支持线程的 Perl 上运行。
my $can_use_threads = eval 'use threads; 1';
if ($can_use_threads) {
# Do processing using threads
...
} else {
# Do it without using threads
...
}
这将创建一个新的线程,该线程将使用指定的入口点函数开始执行,并将其提供给 *ARGS* 列表作为参数。它将返回相应的线程对象,如果线程创建失败,则返回 undef
。
*FUNCTION* 可以是函数名称、匿名子例程或代码引用。
my $thr = threads->create('func_name', ...);
# or
my $thr = threads->create(sub { ... }, ...);
# or
my $thr = threads->create(\&func, ...);
->new()
方法是 ->create()
的别名。
这将等待相应的线程完成其执行。当线程完成时,->join()
将返回入口点函数的返回值。
->join()
返回值的上下文(空、标量或列表)是在线程创建时确定的。
# Create thread in list context (implicit)
my ($thr1) = threads->create(sub {
my @results = qw(a b c);
return (@results);
});
# or (explicit)
my $thr1 = threads->create({'context' => 'list'},
sub {
my @results = qw(a b c);
return (@results);
});
# Retrieve list results from thread
my @res1 = $thr1->join();
# Create thread in scalar context (implicit)
my $thr2 = threads->create(sub {
my $result = 42;
return ($result);
});
# Retrieve scalar result from thread
my $res2 = $thr2->join();
# Create a thread in void context (explicit)
my $thr3 = threads->create({'void' => 1},
sub { print("Hello, world\n"); });
# Join the thread in void context (i.e., no return value)
$thr3->join();
有关更多详细信息,请参阅 "线程上下文"。
如果程序退出而没有所有线程都已加入或分离,则会发出警告。
对已加入的线程调用 ->join()
或 ->detach()
将导致抛出错误。
使线程不可加入,并导致任何最终返回值被丢弃。当程序退出时,任何仍在运行的分离线程将被静默终止。
如果程序退出而没有所有线程都已加入或分离,则会发出警告。
对已分离的线程调用 ->join()
或 ->detach()
将导致抛出错误。
允许线程分离自身的类方法。
允许线程获取其自身 *threads* 对象的类方法。
返回线程的 ID。线程 ID 是唯一的整数,程序中的主线程为 0,每个创建的线程递增 1。
类方法,允许线程获取自己的 ID。
如果将 stringify
导入选项添加到 use threads
声明中,那么在字符串或字符串上下文中使用线程对象(例如,作为哈希键)将导致其 ID 用作值。
use threads qw(stringify);
my $thr = threads->create(...);
print("Thread $thr started\n"); # Prints: Thread 1 started
这将返回与指定线程 ID 关联的活动线程的线程对象。如果 $tid
是当前线程的值,则此调用与 ->self()
相同。否则,如果与 TID 关联的线程不存在,或者线程已加入或分离,或者未指定 TID,或者指定的 TID 为 undef,则返回 undef
。
这是对操作系统的建议,让此线程将 CPU 时间让给其他线程。实际发生的情况高度依赖于底层线程实现。
您可以执行 use threads qw(yield)
,然后在代码中使用 yield()
。
在列表上下文中,不带参数(或使用 threads::all
)时,返回所有未加入、未分离的线程对象的列表。在标量上下文中,返回相同对象的计数。
使用true参数(使用 threads::running
)时,返回所有未加入、未分离且仍在运行的线程对象的列表。
使用false参数(使用 threads::joinable
)时,返回所有未加入、未分离且已完成运行的线程对象的列表(即,对于 ->join()
不会阻塞的线程)。
测试两个线程对象是否为同一个线程。这被重载为更自然的形式
if ($thr1 == $thr2) {
print("Threads are the same\n");
}
# or
if ($thr1 != $thr2) {
print("Threads differ\n");
}
(线程比较基于线程 ID。)
async
创建一个线程来立即执行紧随其后的代码块。此代码块被视为匿名子例程,因此在闭合大括号后必须有分号。与 threads->create()
一样,async
返回一个 threads 对象。
线程在 eval
上下文中执行。如果线程正常终止,此方法将返回 undef
。否则,它将返回与线程在 eval
上下文中的执行状态相关的 $@
的值。
此私有方法返回指向与线程对象关联的内部线程结构的指针(即以无符号整数表示的内存位置)。对于 Win32,这是一个指向 CreateThread
返回的 HANDLE
值的指针(即 HANDLE *
);对于其他平台,它是一个指向 pthread_create
调用中使用的 pthread_t
结构的指针(即 pthread_t *
)。
此方法对一般的 Perl 线程编程没有用。它的目的是为其他(基于 XS 的)线程模块提供访问和可能操作与 Perl 线程关联的底层线程结构的能力。
允许线程获取自身句柄的类方法。
终止线程的常用方法是从入口点函数返回适当的返回值。
如果需要,可以通过调用 threads->exit()
在任何时候退出线程。这将导致线程在标量上下文中返回 undef
,或在列表上下文中返回空列表。
从主线程调用时,这与 exit(0)
行为相同。
从线程调用时,这与 threads->exit()
行为相同(即退出状态代码被忽略)。
从主线程调用时,这与 exit(status)
行为相同。
在线程中调用 die()
表示线程异常退出。线程中的任何 $SIG{__DIE__}
处理程序将首先被调用,然后线程将退出并显示一条警告消息,其中包含 die()
调用中传递的任何参数。
在线程内部调用 exit() 会导致整个应用程序终止。因此,强烈建议不要在多线程代码或可能在多线程应用程序中使用的模块中使用 exit()
。
如果确实需要 exit()
,请考虑使用以下方法
threads->exit() if threads->can('exit'); # Thread friendly
exit(status);
这将全局覆盖在线程内部调用 exit()
的默认行为,并有效地使此类调用与 threads->exit()
的行为相同。换句话说,使用此设置,调用 exit()
仅会导致线程终止。
由于其全局影响,此设置不应在模块或类似环境中使用。
此设置不会影响主线程。
这将仅覆盖新创建线程内部 exit()
的默认行为。
这可用于在创建线程后更改线程的仅退出线程行为。使用true参数,exit()
仅会导致线程退出。使用false参数,exit()
将终止应用程序。
此调用不会影响主线程。
用于在线程内部更改其自身 exit()
行为的类方法。
此调用不会影响主线程。
以下布尔方法可用于确定线程的状态。
如果线程仍在运行(即,其入口点函数尚未完成或退出),则返回 true。
如果线程已完成运行,未分离且尚未加入,则返回 true。换句话说,线程已准备好加入,调用 $thr->join()
不会阻塞。
如果线程已分离,则返回 true。
类方法,允许线程确定它是否已分离。
与子例程一样,从线程的入口点函数返回的值类型可以通过线程的上下文来确定:列表、标量或 void。线程的上下文在创建线程时确定。这是必要的,以便上下文通过 wantarray() 可用于入口点函数。然后,线程可以指定要从 ->join()
返回的适当类型的值。
由于线程创建和线程加入可能发生在不同的上下文中,因此可能需要明确地将上下文声明给线程的入口点函数。这可以通过使用哈希引用作为第一个参数调用 ->create()
来完成
my $thr = threads->create({'context' => 'list'}, \&foo);
...
my @results = $thr->join();
在上面,线程对象以标量上下文返回给父线程,并且线程的入口点函数 foo
将在列表(数组)上下文中调用,以便父线程可以从 ->join()
调用中接收列表(数组)。('array'
与 'list'
同义。)
类似地,如果您需要线程对象,但您的线程不会返回值(即void 上下文),则您将执行以下操作
my $thr = threads->create({'context' => 'void'}, \&foo);
...
$thr->join();
上下文类型也可以用作哈希引用中的键,后跟一个true 值
threads->create({'scalar' => 1}, \&foo);
...
my ($thr) = threads->list();
my $result = $thr->join();
如果没有明确说明,线程的上下文将从 ->create()
调用的上下文中隐含。
# Create thread in list context
my ($thr) = threads->create(...);
# Create thread in scalar context
my $thr = threads->create(...);
# Create thread in void context
threads->create(...);
这以与 wantarray() 相同的方式返回线程的上下文。
类方法,用于返回当前线程的上下文。这将返回与在当前线程的入口点函数中运行 wantarray() 相同的值。
不同平台的默认每线程堆栈大小差异很大,并且几乎总是远远超过大多数应用程序所需的堆栈大小。在 Win32 上,Perl 的 Makefile 明确地将默认堆栈设置为 16 MB;在大多数其他平台上,使用系统默认值,这可能比实际需要的大得多。
通过将堆栈大小调整为更准确地反映应用程序的需求,您可以显著减少应用程序的内存使用量,并增加同时运行的线程数量。
请注意,在 Windows 上,地址空间分配粒度为 64 KB,因此,在 Win32 Perl 上将堆栈设置为小于该值不会节省更多内存。
返回当前默认的每线程堆栈大小。默认值为零,这意味着当前正在使用系统默认堆栈大小。
返回特定线程的堆栈大小。返回值为零表示该线程使用了系统默认堆栈大小。
设置新的默认每线程堆栈大小,并返回之前的设置。
某些平台具有最小线程堆栈大小。尝试将堆栈大小设置为低于此值将导致警告,并将使用最小堆栈大小。
某些 Linux 平台具有最大堆栈大小。设置过大的堆栈大小会导致线程创建失败。
如果需要,$new_size
将向上舍入到下一个内存页面大小的倍数(通常为 4096 或 8192)。
在设置堆栈大小后创建的线程将调用 pthread_attr_setstacksize()
(对于 pthreads 平台),或将堆栈大小提供给 CreateThread()
(对于 Win32 Perl)。
(显然,此调用不会影响任何当前存在的线程。)
这在应用程序启动时设置默认的每线程堆栈大小。
默认的每线程堆栈大小可以在应用程序启动时通过使用环境变量 PERL5_ITHREADS_STACK_SIZE
来设置。
PERL5_ITHREADS_STACK_SIZE=1048576
export PERL5_ITHREADS_STACK_SIZE
perl -e'use threads; print(threads->get_stack_size(), "\n")'
此值将覆盖提供给 use threads
的任何 stack_size
参数。它的主要目的是允许为遗留线程应用程序设置每线程堆栈大小。
要为任何单个线程指定特定的堆栈大小,请使用哈希引用作为第一个参数调用 ->create()
my $thr = threads->create({'stack_size' => 32*4096},
\&foo, @args);
这将创建一个新的线程 ($thr2
),它从现有线程 ($thr1
) 继承堆栈大小。这是以下内容的简写
my $stack_size = $thr1->get_stack_size();
my $thr2 = threads->create({'stack_size' => $stack_size},
FUNCTION, ARGS);
当安全信号生效时(默认行为 - 有关更多详细信息,请参阅 "不安全信号"),则信号可以由各个线程发送和处理。
将指定的信号发送到线程。信号名称和(正)信号编号与 kill() 支持的信号名称和编号相同。例如,'SIGTERM'、'TERM' 和(取决于操作系统)15 都是 ->kill()
的有效参数。
返回线程对象以允许方法链接
$thr->kill('SIG...')->join();
需要在线程中为它们预期要处理的信号设置信号处理程序。以下是一个用于 *取消* 线程的示例
use threads;
sub thr_func
{
# Thread 'cancellation' signal handler
$SIG{'KILL'} = sub { threads->exit(); };
...
}
# Create a thread
my $thr = threads->create('thr_func');
...
# Signal the thread to terminate, and then detach
# it so that it will get cleaned up automatically
$thr->kill('KILL')->detach();
以下是一个更简单的示例,它说明了在结合使用信号量的情况下使用线程信号来提供基本的 *挂起* 和 *恢复* 功能
use threads;
use Thread::Semaphore;
sub thr_func
{
my $sema = shift;
# Thread 'suspend/resume' signal handler
$SIG{'STOP'} = sub {
$sema->down(); # Thread suspended
$sema->up(); # Thread resumes
};
...
}
# Create a semaphore and pass it to a thread
my $sema = Thread::Semaphore->new();
my $thr = threads->create('thr_func', $sema);
# Suspend the thread
$sema->down();
$thr->kill('STOP');
...
# Allow the thread to continue
$sema->up();
警告:此模块提供的线程信号功能实际上不会通过操作系统发送信号。它在 Perl 级 *模拟* 信号,以便在适当的线程中调用信号处理程序。例如,发送 $thr->kill('STOP')
实际上不会挂起线程(或整个进程),但会导致在该线程中调用 $SIG{'STOP'}
处理程序(如上所示)。
因此,通常不适合在 kill()
命令中使用的信号(例如,kill('KILL', $$)
)可以使用 ->kill()
方法(如上所示)。
相应地,向线程发送信号不会中断线程当前正在执行的操作:信号将在当前操作完成后处理。例如,如果线程 *卡住* 在 I/O 调用上,向其发送信号不会导致 I/O 调用被中断,从而使信号立即被处理。
向已终止/完成的线程发送信号将被忽略。
如果程序退出时并非所有线程都已加入或分离,则会发出此警告。
注意:如果主线程退出,则无法使用以下建议的no warnings 'threads';
来抑制此警告。
请参阅pthread_create
的相应手册页以确定失败的实际原因。
线程以某种方式终止,而不是仅仅从其入口点函数返回,或使用threads->exit()
。例如,线程可能因错误而终止,或使用die
。
某些平台具有最小线程堆栈大小。尝试将堆栈大小设置为低于此值会导致上述警告,并且堆栈大小将设置为最小值。
指定的SIZE超过了系统的最大堆栈大小。使用较小的堆栈大小值。
如果需要,可以使用以下方法抑制线程警告
no warnings 'threads';
在适当的范围内。
您尝试使用的特定 Perl 副本不是使用useithreads
配置选项构建的。
要支持线程,需要重新构建 Perl 安装中的所有 Perl 和所有 XS 模块;这不仅仅是添加threads模块的问题(即,线程化和非线程化 Perl 在二进制上不兼容)。
无法更改当前存在的线程的堆栈大小,因此,以下操作会导致上述错误
$thr->set_stack_size($size);
必须启用安全信号才能使用 ->kill()
信号方法。有关更多详细信息,请参阅 "不安全信号"。
您尝试使用的特定 Perl 副本不支持在 ->kill()
调用中使用指定的信号。
在您考虑发布错误报告之前,请咨询并可能在讨论论坛上发布消息,以查看您遇到的问题是否已知问题。
在创建可能在多线程应用程序中使用的模块时,请参阅 "使您的模块线程安全" in perlmod,尤其是在这些模块使用非 Perl 数据或 XS 代码时。
不幸的是,您可能会遇到不是线程安全的 Perl 模块。例如,它们可能会在执行期间使 Perl 解释器崩溃,或者在终止时转储核心。根据模块和应用程序的要求,可能可以解决此类问题。
如果模块仅在线程内部使用,您可以尝试使用 require
(如果需要,还可以使用 import
)从线程入口点函数内部加载模块。
sub thr_func
{
require Unsafe::Module
# Unsafe::Module->import(...);
....
}
如果模块在主线程内部需要,请尝试修改您的应用程序,以便在启动任何线程后加载模块(再次使用 require
和 ->import()
),并且以这样一种方式加载,即之后不会启动其他线程。
如果上述方法不起作用,或者不适合您的应用程序,请在 https://rt.cpan.org/Public/ 上针对有问题的模块提交错误报告。
在大多数系统上,频繁且持续地创建和销毁线程会导致 Perl 解释器内存占用不断增长。虽然简单地启动线程然后使用 ->join()
或 ->detach()
来处理它们,但对于长期运行的应用程序,最好维护一个线程池,并使用 队列 来通知线程待处理的工作,以便重复使用它们来完成所需的工作。此模块的 CPAN 发行版包含一个简单的示例(examples/pool_reuse.pl),说明了创建、使用和监控一个 可重用 线程池。
在除 MSWin32 之外的所有平台上,当前工作目录的设置在所有线程之间共享,因此在一个线程中更改它(例如,使用 chdir()
)将影响应用程序中的所有线程。
在 MSWin32 上,每个线程都维护自己的当前工作目录设置。
在 Perl 5.28 之前,由于各种竞争条件,区域设置无法与线程一起使用。从该版本开始,在实现线程安全区域设置函数的系统上,线程可以使用,但有一些注意事项。这包括从 Visual Studio 2005 开始的 Windows,以及与 POSIX 2008 兼容的系统。请参阅 "perllocale 中的多线程操作"。
每个线程(主线程除外)都是使用 C 区域设置启动的。主线程像所有其他 Perl 程序一样启动;请参阅 "perllocale 中的 ENVIRONMENT"。您可以在任何线程中根据需要切换区域设置。
如果您想继承父线程的区域设置,您可以在父线程中设置一个变量,如下所示
$foo = POSIX::setlocale(LC_ALL, NULL);
然后将一个闭包包含 $foo
的子程序传递给 threads->create()。然后,在子线程中,您说
POSIX::setlocale(LC_ALL, $foo);
或者您可以使用 threads::shared 中的工具来传递 $foo
;或者如果环境没有改变,在子线程中,执行
POSIX::setlocale(LC_ALL, "");
目前,在除 MSWin32 之外的所有平台上,从线程发出的所有 系统 调用(例如,使用 system()
或反引号)都使用 主 线程的环境变量设置。换句话说,对线程中的 %ENV
所做的更改在该线程发出的 系统 调用中将不可见。
要解决此问题,请将环境变量设置为 系统 调用的部分。例如
my $msg = 'hello';
system("FOO=$msg; echo \$FOO"); # Outputs 'hello' to STDOUT
在 MSWin32 上,每个线程都维护自己的环境变量集。
信号由脚本的 主 线程(线程 ID = 0)捕获。因此,在线程中为除 "线程信号" 之外的目的设置信号处理程序将无法实现预期目的。
这在尝试在线程中捕获SIGALRM
时尤其如此。要在线程中处理警报,请在主线程中设置信号处理程序,然后使用"线程信号"将信号传递给线程。
# Create thread with a task that may time out
my $thr = threads->create(sub {
threads->yield();
eval {
$SIG{ALRM} = sub { die("Timeout\n"); };
alarm(10);
... # Do work here
alarm(0);
};
if ($@ =~ /Timeout/) {
warn("Task in thread timed out\n");
}
};
# Set signal handler to relay SIGALRM to thread
$SIG{ALRM} = sub { $thr->kill('ALRM') };
... # Main thread continues working
在某些平台上,可能无法在仍存在子线程的情况下销毁父线程。
从 Perl 5.8.0 开始,Perl 中的信号变得更加安全,因为它们的操作被推迟到解释器处于安全状态时。有关更多详细信息,请参阅"perl58delta 中的安全信号"和"perlipc 中的延迟信号(安全信号)"。
安全信号是默认行为,旧的、立即的、不安全的信号行为仅在以下情况下有效
Perl 使用PERL_OLD_SIGNALS
构建(请参阅 perl -V
)。
环境变量PERL_SIGNALS
设置为unsafe
(请参阅"perlrun 中的 PERL_SIGNALS")。
如果使用不安全的信号,则信号处理不是线程安全的,并且无法使用->kill()
信号方法。
当通过join
操作从线程返回一个值时,该值及其引用的所有内容都会被复制到连接的线程中,这与线程创建时复制值的方式非常相似。这对于大多数类型的值(包括数组、哈希和子例程)都很好用。复制会递归遍历数组元素、引用标量、子例程封闭的变量以及其他类型的引用。
但是,返回的值引用的所有内容在连接的线程中都是新的副本,即使返回的对象在子线程中是先前存在于父线程中的内容的副本。因此,在连接后,父线程将拥有每个此类对象的副本。这有时很重要,尤其是在对象被修改时;这对于返回的子例程提供访问权限的私有数据尤其重要。
从线程返回祝福对象不起作用。根据所涉及的类,您可以通过返回对象的序列化版本来解决此问题(例如,使用 Data::Dumper 或 Storable),然后在连接线程中重新构建它。如果您使用的是 Perl 5.10.0 或更高版本,并且该类支持 共享对象,则可以通过 共享队列 传递它们。
可以使用 END 块 通过使用 require 或 eval 添加线程,并使用适当的代码。这些 END
块将在线程的解释器被销毁时执行(即,在 ->join()
调用期间或程序终止时)。
但是,在这样的 END
块中调用任何 线程 方法很可能会失败(例如,应用程序可能会挂起或生成错误),因为需要互斥锁来控制 线程 模块中的功能。
因此,强烈建议不要在线程中使用 END
块。
在 perl 5.14 及更高版本中,在不支持 fchdir
C 函数的非 Windows 系统上,目录句柄(请参阅 opendir)不会被复制到新线程。您可以使用 Config.pm 中的 d_fchdir
变量来确定您的系统是否支持它。
在之前的 perl 版本中,使用打开的目录句柄生成线程会导致解释器崩溃。 [perl #75154]
如果主线程在仍有正在运行的分离线程的情况下退出,则 Perl 的全局销毁阶段不会执行,因为否则某些控制线程操作的全局结构(在主线程的内存中分配)可能会在分离线程被销毁之前被销毁。
如果您正在使用任何需要执行全局销毁阶段进行清理的代码(例如,删除临时文件),则不要使用分离线程,而是在退出程序之前加入所有线程。
对线程的支持超出了本模块中的代码(即,threads.pm 和 threads.xs),并扩展到了 Perl 解释器本身。较旧版本的 Perl 包含可能在使用来自 CPAN 的最新版本的 threads 时仍然出现的错误。除了升级到最新版本的 Perl 之外,没有其他解决方法。
即使使用最新版本的 Perl,也已知某些使用线程的结构可能会导致有关泄漏的标量或未引用的标量的警告消息。但是,此类警告是无害的,可以安全地忽略。
您可以在 https://rt.cpan.org/Public/ 上搜索与 threads 相关的错误报告。如有必要,请将任何新的错误、问题、补丁等提交到:https://rt.cpan.org/Public/Dist/Display.html?Name=threads
Perl 5.8.0 或更高版本
MetaCPAN 上的 threads:https://metacpan.org/release/threads
CPAN 发行版的代码库:https://github.com/Dual-Life/threads
https://perldotcom.perl5.cn/pub/a/2002/06/11/threads.html 和 https://perldotcom.perl5.cn/pub/a/2002/09/04/threads.html
Perl 线程邮件列表:https://lists.perl.org/list/ithreads.html
堆栈大小讨论:https://www.perlmonks.org/?node_id=532956
此发行版在 CPAN 上的 examples 目录中的示例代码。
Artur Bergman <sky AT crucially DOT net>
Jerry D. Hedden <jdhedden AT cpan DOT org> 制作的 CPAN 版本
threads 在与 Perl 相同的许可证下发布。
Richard Soderberg <perl AT crystalflame DOT net> - 帮助我解决了很多问题,试图找到竞态条件和其他奇怪错误的原因!
Simon Cozens <simon AT brecon DOT co DOT uk> - 随时解答无数烦人的问题
Rocco Caputo <troc AT netrus DOT net>
Vipul Ved Prakash <mail AT vipul DOT net> - 帮助调试
Dean Arnold <darnold AT presicient DOT com> - 栈大小 API