Perl Многопоточный апплет GTK+

Я хотел бы написать приложение Perl GTK+, которое будет:

0.1) Нажмите кнопку A
0.2) Отключите A
0.3) запустите потоки 1 и 2
0.4) запустите поток 3

Поток 3 делает следующее:

3.1) присоединиться к потоку 1
3.2) присоединиться к потоку 2
3.3) включить A

По завершении потока 3 кнопка A должна снова стать доступной.

Такой подход отлично работает в C/C++ под Win32, Linux с использованием родных библиотек GUI и/или GTK+, KDE. Проблема с GTK+ и Perl заключается в том, что вы не можете совместно использовать переменную кнопки в потоках (например, пункт 3.3 не может быть выполнен потоком 3).

Проблема в том, что threads::shared работает только на базовых типах, а не на ссылках типа Gtk2::Button.

Я снова попытался bless объект Gtk2::Button (как показано в документации), но получил ошибку:

my $thread_button = shared_clone(Gtk2::Button->new('_Threads'));
bless $thread_button => 'Gtk2::Button';
$hbox->pack_start($thread_button, FALSE, FALSE, 0);
my ($jobA, $jobB);
$thread_button->signal_connect( clicked => sub {
        $thread_button->set_sensitive(0);
        if (defined($jobA)) {
            $jobA->join();
        }
        if (defined($jobB)) {
            $jobB->join();
        }
        # spawn jobs
        $jobA = threads->create(\&async_func, 10);
        $jobB = threads->create(\&async_func, 10);
        threads->create(sub { 
            $jobA->join();
            $jobB->join();
            bless $thread_button => 'Gtk2::Button';
            $thread_button->set_sensitive(1);
            });
    });

В порядке ли мой код?
Я спрашиваю, потому что при его запуске графический интерфейс не отображает кнопку Thread и сообщает о следующей ошибке:

Gtk-CRITICAL **: gtk_box_pack: assertion `GTK_IS_WIDGET (child)' failed at vbox.pl line 48. (Where I use pack_start)
GLib-GObject-WARNING **: invalid (NULL) pointer instance at vbox.pl line 67.
GLib-GObject-CRITICAL **: g_signal_connect_closure: assertion `G_TYPE_CHECK_INSTANCE (instance)' failed at vbox.pl line 67. (the signal_connect doesn't work)

По-видимому, это не работает со сложными объектами.
Я пробовал другое решение, опрашивая запущенные потоки внутри функции обратного вызова, вызываемой в основном (GTK) потоке:

my $thread_button = Gtk2::Button->new('_Threads');
$hbox->pack_start($thread_button, FALSE, FALSE, 0);
my ($jobA, $jobB);
$thread_button->signal_connect( clicked => sub {
        $thread_button->set_sensitive(0);
        # spawn jobs
        $jobA = threads->create(\&async_func, 10);
        $jobB = threads->create(\&async_func, 10);
        Glib::Timeout->add(3000, sub { 
                print "TIMER\n";
                if (defined($jobA)) {
                    if (! $jobA->is_running()) {
                        print "jobA is not running!\n";
                        $jobA->join();
                        undef $jobA;
                    }
                }
                if (defined($jobB)) {
                    if (! $jobB->is_running()) {
                        print "jobB is not running!\n";
                        #$jobB->join();
                        undef $jobB;
                    }
                }
                if (!defined($jobA) && !defined($jobB)) {
                    print "Both jobs have terminated!\n";
                    $thread_button->set_sensitive(1);
                    return 0;
                }
                return 1;
                });
    });

Обратите внимание на следующее:
1) я должен прокомментировать присоединение во второй теме.

#$jobB->join();

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

Это очень нестабильно. Я думал, что Perl больше основан на C, но эта огромная нестабильность полностью отсутствует в C/C++. Я немного разочарован.
У кого-нибудь есть еще предложения? Является ли многопоточный API настолько нестабильным в Perl?

Последнее обновление. Этот код работает:

my $thread_button = Gtk2::Button->new('_Threads');
$hbox->pack_start($thread_button, FALSE, FALSE, 0);
my ($jobA, $jobB);
$thread_button->signal_connect( clicked => sub {
        $thread_button->set_sensitive(0);
        # spawn jobs
        $jobA = threads->create(\&async_func, 10);
        $jobB = threads->create(\&async_func, 10);
        Glib::Timeout->add(100, sub { 
                if (!$jobA->is_running() && !$jobB->is_running()) {
                    print "Both jobs have terminated!\n";
                    $thread_button->set_sensitive(1);
                    return 0;
                }
                return 1;
                });
    });

Но:
1) я должен опрашивать потоки (не очень ресурсоемкий для современных процессоров, но НЕ элегантный... следует полагаться только на примитивы синхронизации ОС)
2) я могу не присоединяйтесь к тредам, иначе апплет вылетит
3) Учитывая (2) огромные утечки памяти каждый раз, когда я нажимаю кнопку

Честно говоря, чем больше я это вижу, тем больше убеждаюсь, что для правильной разработки приложений вы не можете полагаться на Perl... но даже с точки зрения прототипа это отстой.
Надеюсь, я делаю что-то не так... в этом случае кто-нибудь может мне помочь?

Ваше здоровье,


person Community    schedule 17.08.2009    source источник
comment
Почему вы присоединяетесь к $jobA и $jobB вне третьей темы?   -  person Sinan Ünür    schedule 17.08.2009
comment
Логика заключается в том, что jobA и jobB соединяются потоком 3, так что, когда jobA и jobB завершаются, третий поток может снова активировать кнопку непосредственно перед выходом.   -  person    schedule 18.08.2009


Ответы (2)


Как объясняется в threads::shared документах, вам необходимо повторно благословить общие объекты.

Обновление: попробуйте следующий вариант

#!/usr/bin/perl

package Button;

use strict;  use warnings;
# Trivial class because I do not have GTK2

sub new { bless \ my $self => $_[0] }
sub enable     { ${ $_[0] } = 1; return }
sub disable    { ${ $_[0] } = 0; return }
sub is_enabled { ${ $_[0] } ? 1 : 0 }

package main;

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

my $buttonA = shared_clone( Button->new );
my $button_class = ref $buttonA;

$buttonA->disable;

my @thr = map { threads->create(
    sub {
        print "thread $_ started\n";
        sleep rand 3;
        print "thread $_ finished\n";
        return; }
) } (1, 2);

my $thr3 = threads->create( sub {
        $_->join for @_ ;
        bless $buttonA => $button_class;
        $buttonA->enable;
    }, @thr,
);

$thr3->join;

printf "buttonA is %s\n", $buttonA->is_enabled ? 'enabled' : 'disabled';

Другой альтернативой является передача обратного вызова $thr3:

my $buttonA = Button->new;
share($buttonA);
$buttonA->disable;

# start the other threads

my $thr3 = threads->create( sub {
        my $callback = shift;
        $_->join for @_ ;
        $callback->();
    }, sub { $buttonA->enable }, @thr,
);

Обе версии кода производят вывод:

thread 1 started
thread 2 started
thread 1 finished
thread 2 finished
buttonA is enabled
person Sinan Ünür    schedule 17.08.2009
comment
Это не работает с правильными элементами графического интерфейса GTK+. Или кнопка не может быть передана, или она дает сбой (как и ожидалось). Поскольку это не C/C++, я думаю, что должен быть представлен правильный пример для виджетов GTK+, а не для простых в обращении макетных классов. Ваше здоровье, - person ; 18.08.2009

Я читал пару примеров о потоках и GTK в Perl, но все они инициализируют рабочие потоки, а затем переключают свой статус на run/halt...
Очень плохой пример параллельного развития.

Есть еще предложения?

Ваше здоровье,

person Emanuele    schedule 19.08.2009