Я затрону весьма наболевшую тему, Perl + GUI + потоки.
Наболевшую, потому что попытка заставить работать ваше приложение с потоками может закончиться неудачей. Программа «виснет», «сегфолитится», Вы обратитесь к документации, увидете там, что библиотека не thread-safe. Потраченное время было коту под хвост?
Хинт: создать потоки до вызова Tkx::MainLoop, так как MainLoop() запускает свой цикл событий и блокирует выполнение кода. Было бы все так просто! Переписали Вы код с этим условием, а она все равно виснет…
Что же делать? Выход есть.
Нужно использовать модель Boss/Workers (Начальник и Работники) и очереди сообщений (Queue).
Цель: написать приложение с GUI и использовать многопоточность.
Давайте, рассмотрим задачу «на пальцах», представим все в виде абстрактной модели.
Есть склад. Вы приходите к начальнику (boss),
— Привет, соберите мне вот этот списочек…
— Окей, сейчас раскидаю задание по частям, работники (workers) все сделают.
Кладовщики задания берут из стопки (причем берут по порядку их поступления).
Подобную очередь реализует пакет
Мы будем использовать несколько методов
— enqueue — положить задание
— dequeue, dequeue_nb — взять задание
Разница между dequeue и dequeue_nb в том, что последний неблокирующийся.
Другими словами, когда мы вызываем dequeue, мы ждем пока задание не появится, и только тогда его получаем. А во втором случае, если задания нет — то возвращается undef.
Кладовщики собрали весь необходимый товар, теперь его заберет грузчик, и Вам принесет.
…
Теперь приступим к реализации (упрощенный вариант).
Task -> Tk -> Boss -> Worker -> Result
Если Вы планируете писать многопоточную программу для работы с сетью, базами данных, то я думаю что вместо стандартных потоков, гораздо правильней будет использовать POE (событийная машина, non-blocking sockets).
Пока это черновой вариант, будет дополняться.
Наболевшую, потому что попытка заставить работать ваше приложение с потоками может закончиться неудачей. Программа «виснет», «сегфолитится», Вы обратитесь к документации, увидете там, что библиотека не thread-safe. Потраченное время было коту под хвост?
Хинт: создать потоки до вызова Tkx::MainLoop, так как MainLoop() запускает свой цикл событий и блокирует выполнение кода. Было бы все так просто! Переписали Вы код с этим условием, а она все равно виснет…
Что же делать? Выход есть.
Нужно использовать модель Boss/Workers (Начальник и Работники) и очереди сообщений (Queue).
Цель: написать приложение с GUI и использовать многопоточность.
Давайте, рассмотрим задачу «на пальцах», представим все в виде абстрактной модели.
Есть склад. Вы приходите к начальнику (boss),
— Привет, соберите мне вот этот списочек…
— Окей, сейчас раскидаю задание по частям, работники (workers) все сделают.
Кладовщики задания берут из стопки (причем берут по порядку их поступления).
Подобную очередь реализует пакет
Thread::Queue
.Мы будем использовать несколько методов
— enqueue — положить задание
— dequeue, dequeue_nb — взять задание
Разница между dequeue и dequeue_nb в том, что последний неблокирующийся.
Другими словами, когда мы вызываем dequeue, мы ждем пока задание не появится, и только тогда его получаем. А во втором случае, если задания нет — то возвращается undef.
while( defined( my $item = $queue->dequeue() ) ) { # выполняем какие-либо действия. }
Кладовщики собрали весь необходимый товар, теперь его заберет грузчик, и Вам принесет.
…
Теперь приступим к реализации (упрощенный вариант).
Task -> Tk -> Boss -> Worker -> Result
#!/usr/bin/perl use strict; use Tkx; # тулкит use threads; # работа с потоками use Thread::Queue; # реализует очередь # создаем очереди my $queue_tk = Thread::Queue->new(); # получаем задания из Tk my $queue_job = Thread::Queue->new(); # отправляем работникам my $queue_box = Thread::Queue->new(); # результаты # босс sub thread_boss { my $self = threads->self(); my $tid = $self->tid(); while( defined( my $item = $queue_tk->dequeue() ) ) { print STDERR "Boss($tid) has received the task from Tk: $item\n"; # отправляем задание на обработку работнику $queue_job->enqueue( $item ); } $queue_job->enqueue( undef ); } # работник(и) sub thread_worker { my $self = threads->self(); my $tid = $self->tid(); while( defined( my $job = $queue_job->dequeue() ) ) { print STDERR "Worker($tid) has received task from Boss: $job\n"; # выполняем какую-нибудь работу... print STDERR "Worker($tid) has finished the task\n"; # скидываем все в одну коробку ;) $queue_box->enqueue( "processed: $job" ); } $queue_box->enqueue( undef ); } # создаем потоки my $boss = threads->new( \&thread_boss ); my $worker = threads->new( \&thread_worker ); # Создаем UI my $main_window = Tkx::widget->new( '.' ); my $frame = $main_window->new_ttk__frame( -padding => q/10 10 10 10/ ); $frame->g_grid(); my $label = $frame->new_ttk__label( -text => 'waiting' ); $label->g_grid( -row => 0, -column => 0, -columnspan => 2 ); # поле для ввода my $entry_data = 'enter data here'; my $entry = $frame->new_ttk__entry( -textvariable => \$entry_data ); my $button = $frame->new_ttk__button( -text => 'Send to Boss', -command => sub { $queue_tk->enqueue( $entry_data ); }, ); $entry->g_grid( -row => 1, -column => 0 ); $button->g_grid( -row => 1, -column => 1 ); # обработчик события WM_DELETE_WINDOW sub on_destroy { my $mw = shift; # отсылаем очереди undef, что завершит потоки $queue_tk->enqueue( undef ); $queue_box->enqueue( 'finish' ); # Destroy # или Tkx::destroy( '.' ) $mw->g_destroy(); } $main_window->g_wm_protocol( 'WM_DELETE_WINDOW', [\&on_destroy, $main_window] ); # обрабатываем результат sub monitor { my $status_lbl = shift; my $result = $queue_box->dequeue_nb; if( $result ne 'finish' ) { if( defined $result ) { $label->configure( -text => "job completed: ".scalar(localtime)); } Tkx::after( 1000, [\&monitor, $label]); } } # запускаем мониторинг Tkx::after( 100, [\&monitor, $label] ); # открепляем потоки # иначе при завершении программы, у нас будут предупреждения # Perl exited with active threads: # 2 running and unjoined # 0 finished and unjoined # 0 running and detached $boss->detach(); $worker->detach(); Tkx::MainLoop();
Если Вы планируете писать многопоточную программу для работы с сетью, базами данных, то я думаю что вместо стандартных потоков, гораздо правильней будет использовать POE (событийная машина, non-blocking sockets).
Пока это черновой вариант, будет дополняться.