Perl - 如何制作特定于各个线程的库

Perl - How to make a library specific to individual threads

我正在用 perl 编写多线程脚本。我在其中使用了一个库 Net::Netconf::Manager,它又使用了 Net::SSH2。 Net::SSH2(libssh2) 在同时 'shared handles' 时似乎不是线程安全的。

我引用 libssh2 website

线程安全:只是不要同时共享句柄

  1. 我不确定 "sharing handles" 是什么意思。我也想知道如何 'not share handles'.

当我 运行 我的脚本时,偶尔我会看到带有回溯和内存映射的错误跟踪,表示 *** glibc detected *** perl: double free or corruption (out): 0x00007f0320012d70 *** 错误。这个错误是因为 Net::SSh2 库的线程安全性。

  1. 如何使这个 Net::Netconf::Manager 对每个线程都可用,而不是用 'use' 全局声明它。我希望所有线程都可以独立于其他线程访问该库。

请告诉我你的看法。

我是 Net::SSH2 的当前维护者。

我从未追求该模块的线程安全性,但对其代码的粗略检查表明,double free 错误可能是由 Net::SSH2 对象的 Perl 端在线程创建时被克隆引起的而C端则不是。这导致 libssh2 对象被销毁和释放两次,导致程序崩溃。

因此,如果您想在多线程应用程序中使用 Net::SSH2,您应该确保永远不会从存在此模块对象的线程创建线程。

即便如此,模块上也可能潜伏着其他错误。

显然正确的做法是修复模块。如果你想自己做,我会尽力帮助你。请与我联系,以便我们可以先讨论细节......否则,既然你已将这个问题提请我注意,好吧,也许在某个时候我会自己解决它......但这不会一夜之间发生。

'thread safety' 问题的一般解决方法是 'require' 和 'import'。这些必须在 发生任何类型的线程实例化之后调用,并且之后不能创建线程(从加载模块的任何地方 - 在 'main' 内没问题)。

所以 - 因为您没有给我们任何代码,所以我使用了模块外的示例。您需要相应地进行修改。

#!/usr/bin/env perl

use strict;
use warnings;
use threads;
use Thread::Queue;

my $num_workers = 10;

my %generalargs = (
   'access'              => 'ssh',
   'server'              => 'netconf',
   'command'             => 'junoscript netconf',
   'debug_level'         => 1,
   'client_capabilities' => [
      'urn:ietf:params:xml:ns:netconf:base:1.0',
      'urn:ietf:params:xml:ns:netconf:capability:candidate:1.0',
      'urn:ietf:params:xml:ns:netconf:capability:confirmed-commit:1.0',
      'urn:ietf:params:xml:ns:netconf:capability:validate:1.0',
      'urn:ietf:params:xml:ns:netconf:capability:url:1.0?protocol=http,ftp,file',
      'http://xml.juniper.net/netconf/junos/1.0',
   ]
);

my @host_list = (
   {  'hostname' => 'routername',
      'login'    => 'loginname',
      'password' => 'secret',
   },
   {  'hostname' => 'routername2',
      'login'    => 'differentname',
      'password' => 'anotherpassword',
   },
);

my $work_q = Thread::Queue->new;


sub some_helper_sub_that_isnt_a_thread {
   my ( $input, $process ) = @_;
   return "$input";
}

sub do_netconf_stuff {
   require 'Net::NetConf::Manager';
   Net::NetConf::Manager->import;

   while ( my $item = work_q->dequeue ) {
      my $device = Net::NetConf::Manager->new( %{$item} );
      print 'Could not create Netconf device' unless $device;
      some_helper_sub($device);
   }
}

threads->create( \&do_netconf_stuff ) for 1 .. $num_workers;

foreach my $host (@host_list) {
   $work_q->enqueue( { %$host, %generalargs } );
}
$work_q->end;

$_->join for threads->list;

这里发生的是每个线程独立地 - 并且在运行时 - 导入 Net::NetConf::Manager 这意味着它们分别被实例化。然后您可以从该线程中调用其他子程序,它们会正常工作 - 您已经加载到该线程的全局命名空间

您不能做的事情就是启动额外的线程,这些线程将 'inherit' 导入的环境。

注意 - 这并非 100% 肯定有效 - 线程可能会发生冲突还有其他原因(例如尝试侦听相同的端口号、锁定相同的文件等)。但是由于共享文件句柄等,您将避免模块内的问题。