📜 ⬆️ ⬇️

Perl and GUI. Work with threads

I will touch on a very painful topic, Perl + GUI + streams.
Painful, because trying to get your application to work with threads can fail. The program "hangs", "segolit", you refer to the documentation, you will see there that the library is not thread-safe. Was the time spent down the drain?

Hint: create threads before calling Tkx :: MainLoop, since MainLoop () starts its event loop and blocks code execution. It would be so simple! You rewrote the code with this condition, but it still hangs ...

What to do? There is an exit.
You need to use the Boss / Workers model (Head and Workers) and message queues (Queue).
')
The goal: to write an application with a GUI and use multithreading.
Let's look at the problem "on the fingers", we will present everything in the form of an abstract model.

There is a warehouse. You come to the boss (boss)
“Hello, collect this list for me ...”
- Okay, now I’ll scatter the task in parts, the workers (workers) will do everything.

Storekeepers assign tasks from the stack (and take in the order of their receipt).

This queue is implemented by the package Thread::Queue .

We will use several methods
- enqueue - put the task
- dequeue, dequeue_nb - take the task

The difference between dequeue and dequeue_nb is that the latter is non-blocking.

In other words, when we call dequeue, we wait until the task arrives, and only then we get it. And in the second case, if there is no task, then undef is returned.

 while (defined (my $ item = $ queue-> dequeue ())) {
   # perform any actions.
 }


Storekeepers have collected all the necessary goods, now the loader will take it, and you will bring it.
...

Now we will start implementation (simplified version).

Task -> Tk -> Boss -> Worker -> Result

image

 #! / usr / bin / perl
 use strict;

 use Tkx;  # tulkit

 use threads;  # work with threads
 use Thread :: Queue;  # implements the queue

 # create queues
 my $ queue_tk = Thread :: Queue-> new ();  # we get tasks from Tk
 my $ queue_job = Thread :: Queue-> new ();  # send to employees
 my $ queue_box = Thread :: Queue-> new ();  # results

 # boss
 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";
        
         # send the task for processing to the employee
         $ queue_job-> enqueue ($ item);
     }
    
     $ queue_job-> enqueue (undef);
 }

 # worker (s)
 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";
        
         # doing some work ...
         print STDERR "Worker ($ tid) has finished the task \ n";
        
         # we throw everything in one box;)
         $ queue_box-> enqueue ("processed: $ job");
     }

     $ queue_box-> enqueue (undef);    
 }
    
 # create threads
 my $ boss = threads-> new (\ & thread_boss);
 my $ worker = threads-> new (\ & thread_worker);


 # Create 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);

 # input field
 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);

 # handler for WM_DELETE_WINDOW
 sub on_destroy {
     my $ mw = shift;

     # send queues undef that will terminate streams
     $ queue_tk-> enqueue (undef);
     $ queue_box-> enqueue ('finish');

     # Destroy
     # or Tkx :: destroy ('.')
     $ mw-> g_destroy ();
 }

 $ main_window-> g_wm_protocol ('WM_DELETE_WINDOW', [\ & on_destroy, $ main_window]);

 # process the result
 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]);
     }
    
 }

 # run monitoring
 Tkx :: after (100, [\ & monitor, $ label]);

 # detach streams
 # otherwise at the end of the program, we will have warnings
 # Perl exited with active threads:
 # 2 running and unjoined
 # 0 finished and unjoined
 # 0 running and detached
 $ boss-> detach ();
 $ worker-> detach ();

 Tkx :: MainLoop ();


If you plan to write a multi-threaded program for working with networks, databases, then I think that instead of standard streams, it would be much more appropriate to use POE (event-based, non-blocking sockets).

While this is a draft, will be supplemented.

Source: https://habr.com/ru/post/65592/


All Articles