Проблема с обратными ссылками в многопоточном скрипте Perl в Windows

#windows #multithreading #perl #backticks

#Windows #многопоточность #perl #обратные ссылки

Вопрос:

У меня проблема со следующим очень простым и небольшим скриптом Perl на платформе Windows.

 use strict;
use warnings;
use threads;
use threads::shared;

my $print_mut : shared;
my $run_mut : shared;
my $counter : shared;

$counter = 30;

###############################################################

sub _print($)
{
lock($print_mut);
my $str = shift;
my $id  = threads->tid();
print "[Thread_$id] $str";
return;
}

###############################################################

sub _get_number()
{
lock($counter);
return $counter--;
}

###############################################################

sub _get_cmd($)
{
my $i = shift;
if ($^O eq 'MSWin32')
  {
    return qq{cmd /c "echo $i"};
  }
return "echo $i";
}

###############################################################

sub thread_func()
{
while ((my $i = _get_number()) > 0)
  {
    my $str = 'NONE';
    {
    lock($run_mut);
    my $cmd = _get_cmd($i);
    $str = `$cmd`;
    }
    chomp $str;
    _print "Got string: '$str'.n";
  }
return;
}

###############################################################

# Start all threads
my @threads;
for (1 .. 8)
  {
my $thr = threads->create('thread_func');
push @threads, $thr;
  }

# Wait for completion of the threads
foreach (@threads)
  {
$_->join;
  }

###############################################################
  

На моем компьютере Linux (Perl v5.10.0) Я получаю правильные (ожидаемые) результаты:

$ perl ~/tmp/thr2.pl 
[Thread_1] Получена строка: '30'.
[Thread_1] Получена строка: '29'.
[Thread_2] Получена строка: '28'.
[Thread_1] Получена строка: '27'.
[Thread_2] Получена строка: '26'.
[Thread_1] Получена строка: '25'.
[Thread_1] Получена строка: '23'.
[Thread_2] Получена строка: '24'.
[Thread_2] Получена строка: '20'.
[Thread_2] Получена строка: '19'.
[Thread_1] Получена строка: '22'.
[Thread_4] Получена строка: '18'.
[Thread_5] Получена строка: '15'.
[Thread_2] Получена строка: '17'.
[Thread_2] Получена строка: '12'.
[Thread_3] Получена строка: '21'.
[Thread_4] Получена строка: '14'.
[Thread_4] Получена строка: '7'.
[Thread_1] Получена строка: '16'.
[Thread_6] Получена строка: '11'.
[Thread_2] Получена строка: '10'.
[Thread_2] Получена строка: '2'.
[Thread_3] Получена строка: '8'.
[Thread_5] Получена строка: '13'.
[Thread_8] Получена строка: '6'.
[Thread_4] Получена строка: '5'.
[Thread_1] Получена строка: '4'.
[Thread_6] Получена строка: '3'.
[Thread_7] Получена строка: '9'.
[Thread_2] Получена строка: '1'. 
$

Однако в Windows (Perl v5.10.1) я получаю беспорядок:

C:>perl Z:tmpthr2.pl
[Thread_1] Получена строка: '30'.
[Thread_2] Получена строка: '29'.
[Thread_2] Получена строка: '21'.
[Thread_6] Получена строка: '26'.
[Thread_5] Получена строка: '25'.
[Thread_5] Получена строка: '17'.
[Thread_8] Получена строка: '23'.
[Thread_1] Получена строка: '22'.
[Thread_1] Получена строка: '14'.
[Thread_2] Получена строка: '20'.
[Thread_6] Получена строка: '18'.
[Thread_7] Получена строка: '24'.
[Thread_7] Получена строка: '9'.
[Thread_8] Получена строка: '15'.
[Thread_3] Получена строка: '28'.
[Thread_3] Получена строка: '6'.
[Thread_4] Получена строка: '12'.
[Thread_2] Получил строку: '[Thread_4] Получил строку: '27'.
19'.
[Thread_6] Получена строка: '10'.
[Thread_5] Получена строка: '16'.
[Thread_7] Получена строка: '8'.
[Thread_8] Получена строка: '7'.
[Thread_1] Получена строка: '13'.
[Thread_3] Получена строка: '5'.
[Thread_4] Получена строка: '4'.
[Thread_2] Получена строка: '11'.
[Thread_6] Получил строку: '[Thread_2] Получил строку: '3'.
[Thread_5] Получена строка: '2'.
1'.

C:>

Проблема возникает, когда я запускаю команду (не имеет значения, какую команду) из функции thread через backtick для сбора ее выходных данных.

У меня очень ограниченный опыт работы с потоками в Perl и с Perl в Windows. Я всегда старался вообще избегать использования потоков в Perl, но на этот раз я должен их использовать.

Мне не удалось найти ответ в perldoc и Google. Не мог бы кто-нибудь, пожалуйста, объяснить, что не так с моим скриптом?

Заранее спасибо!

Комментарии:

1. Один из найденных мной обходных путей — изменить «print» на «print STDERR», а затем запустить скрипт как «perl Z:tmpthr2.pl 2>amp;1»

2. Однако я понятия не имею, почему это устраняет проблему. Я знаю, что STDERR не буферизован, но я попробовал стандартный вывод с автозапуском, и это не сработало.

3. Ошибка Perl 77672, исправлена в версии 5.19.8.

Ответ №1:

Я могу воссоздать эту проблему на своем WinXP с идентичными результатами. Однако, похоже, это влияет только на стандартный вывод.

Проблема не появляется, если я печатаю в файл, и она не появляется, когда я использую STDERR, как предложил Дмитрий. Однако это появляется, если я записываю в стандартный вывод и файл. Что является подсказкой.

Добавление другой переменной обратного указателя к печати приводит к появлению проблемы в двух местах, перед каждой конкатенацией.

Во время тестирования я решил, что chomp () недостаточно, поэтому я добавил

 $str =~ s/[^w] //g;
  

С этим интересным результатом:

 [Thread_6] Got string: 'Thread_4Gotstring1925'.
  

Что, по-видимому, подразумевает, что $str фактически содержит весь буфер печати из другого потока. Что, мягко говоря, странно.

Если только это…

Выполняются два потока в одно и то же время:

 print "[Thread_4] Got string: '19'.n"
$str = `echo 25`
  

Вероятно, Print и echo используют один и тот же буфер стандартного вывода, и поэтому все это переходит в $str с результирующей печатью:

 chomp "[Thread_4] Got string: '19'.n25n"
print "[Thread_6] Got string: [Thread_4] Got string: ''19'n25'.n"
  

Короче говоря, проблема Windows. Если вы хотите «исправить» проблему, убедитесь, что echo и print оба покрыты заблокированными значениями. Перемещение } в thread_func вниз под _print должно обеспечить чистую печать. Т.е.:

 {
    lock($run_mut);
    my $cmd = _get_cmd($i);
    $str = `$cmd`;
    chomp $str;
    _print "Got string: '$str'.n";
}
  

Забавным способом проверить это было бы заменить echo на какую-нибудь команду Windows, которая записывает в STDERR, и посмотреть, не конфликтует ли это с print в STDERR в perl.