从根本上说,您误解了线程的作用。它们被设计为并行和异步操作。
这意味着不同的线程在不同的时间访问程序的不同位,并可能在不同的处理器上运行。
这样做的一个缺点是——正如你所发现的——你不能保证操作的顺序或原子性。复合操作也是如此——你实际上不能保证即使 print 语句是原子操作——你最终可能会出现拆分行。
您应该始终假设任何操作都不是原子的,除非您确定不这样做,并相应地锁定。大多数时候你会侥幸逃脱,但你会发现自己被一些真正可怕且难以发现的错误绊倒,因为在一小部分情况下,你的非原子操作会相互干扰。即使像++ 这样的东西也可能不是。 (这不是线程本地变量的问题,只要您与共享资源交互,如文件、STDOUT、共享变量等)
这是并行编程中非常常见的问题,因此有多种解决方案:
使用lock 和一个共享变量:
##outside threads:
use threads::shared;
my $lock : shared;
在线程内部:
{
lock $lock;
### do atomic operation
}
当锁离开作用域时,它会被释放。一个线程将“阻塞”等待获得该锁,因此对于这一点,您不再并行运行。
使用Thread::Semaphore
很像一把锁 - 你有 Thread::Semaphore 模块,在你的情况下,它的工作原理是一样的。但它是围绕有限(但不止 1 个)资源构建的。我不会在您的场景中使用它,但如果您尝试使用它可能会很有用。限制并发磁盘 IO 和并发处理器使用 - 设置信号量:
use Thread::Semaphore;
my $limit = Thread::Semaphore -> new ( 8 );
在线程内部:
$limit -> down();
#do protected bit
$limit -> up();
当然,您可以将 8 设置为 1,但与 lock 相比,您不会获得太多。 (只是up() 删除它的能力,而不是让它超出范围)。
使用带有Thread::Queue 的“IO 处理程序”线程
(在分叉中,您可以在此处使用pipe)。
use Thread::Queue;
my $output = Thread::Queue -> new ();
sub print_output_thread {
while ( $output -> dequeue ) {
print;
}
}
threads -> create ( \&output_thread );
在你的线程中,你会使用print:
$output -> enqueue ( "Print this message \n" );
此线程序列化您的输出,并确保每条消息都是原子的 - 但请注意,如果您执行 两个 enqueue 操作,它们可能会再次交错,原因完全相同。
所以你需要;
$output -> enqueue ( "Print this message\n", "And this message too\n" );
(您也可以像第一个示例中那样锁定队列)。再说一遍,对于您的示例来说可能有点矫枉过正,但如果您尝试将结果整理到特定的顺序中,它可能会很有用。