Search code examples
multithreadingperlglobal-variables

Use of global arrays in different threads in perl


Use of global arrays in different threads

I'm going to use Dancer2 and File::Tail to use Tail on the web. So when the Websocket is opened, it stores the $conn in an array, and when File::Tail is detected, it tries to send data to the socket stored in the array. But it doesn't work as expected.

The array that is saved when a websocket connection occurs is probably not a global variable.

                 # it doesn't works.
                 foreach (@webs) {
                     $_->send_utf8("test2!!!!!!!!");
                 }

I tried to use threads::shared and Cache:::Memcached etc, but I failed.

I don't know perl very well. I tried to solve it myself, but I couldn't solve it for too long, so I leave a question.

This is the whole code.

use File::Tail ();
use threads;
use threads::shared;
use Net::WebSocket::Server;
use strict;
use Dancer2;

my @webs = ();
# my %clients :shared = ();

my $conns :shared = 4;
threads->create(sub {
    print "start-end:", "$conns", "\n";
    my @files = glob( $ARGV[0] . '/*' );
    my @fs = ();
    foreach my $fileName(@files) {
        my $file = File::Tail->new(name=>"$fileName",
                                   tail => 1000,
                                   maxinterval=>1,
                                   interval=>1,
                                   adjustafter=>5,resetafter=>1,
                                   ignore_nonexistant=>1,
                                   maxbuf=>32768);
        push(@fs, $file);
    }
    do {
        my $timeout = 1;
        (my $nfound,my $timeleft,my @pending)=
            File::Tail::select(undef,undef,undef,$timeout,@fs);
        unless ($nfound) {

        } else {
            foreach (@pending) {
                my $str = $_->read;
                print $_->{"input"} . " ||||||||| ".localtime(time)." ||||||||| ".$str;

                # it doesn't works.
                foreach (@webs) {
                    $_->send_utf8("test!!!!!!!!");
                }
            }
        }
    } until(0);
})->detach();

threads->create(sub {
    Net::WebSocket::Server->new(
        listen => 8080,
        on_connect => sub {
            my ($serv, $conn) = @_;
            push(@webs, $conn);
            $conn->on(
                utf8 => sub {
                    my ($conn, $msg) = @_;
                    $conn->send_utf8($msg);
                    # it works.
                    foreach (@webs) {
                        $_->send_utf8("test!!!!!!!!");
                    }
                },
                );
        },
        )->start;
                })->detach();


get '/' => sub {
    my $ws_url = "ws://127.0.0.1:8080/";
    return <<"END";
    <html>
      <head><script>
          var urlMySocket = "$ws_url";

          var mySocket = new WebSocket(urlMySocket);

          mySocket.onmessage = function (evt) {
            console.log( "Got message " + evt.data );
          };

          mySocket.onopen = function(evt) {
            console.log("opening");
            setTimeout( function() {
              mySocket.send('hello'); }, 2000 );
          };

    </script></head>
    <body><h1>WebSocket client</h1></body>
  </html>
END
};

dance;

Solution

  • Threads in perl are not lightweight. They're separate instances of the program.

    The only thing that threads have in common, are things that exist prior to the threads instantating.

    You can - with declaring shared variables - allow data structures to share between threads, however I'd warn you to be cautious here - without some manner of locking, you potentially create yourself a race condition.

    In your case, you could declare @webs as : shared. This will mean values inserted into it will be visible to all your threads. But you still need a degree of caution there, because 'when stuff is added' is still nondeterministic.

    But anyway, this basically works:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    use threads;
    use threads::shared; 
    
    use Data::Dumper;
    
    my @shared_struct  : shared;
    
    sub reader  {
       print "Starting reader\n";
       for ( 1..10 ) {
           print threads -> self() -> tid(), ":", join (",", @shared_struct ), "\n";
           sleep 1;
       } 
    } 
    
    
    sub writer {
       print "starting writer\n";
       for ( 1..10 ) {
         push @shared_struct, rand(10); 
         print Dumper \@shared_struct;
         sleep 1; 
      }
       
    } 
    
    
    ## start the threads;
    
    my $reader = threads -> create ( \&reader );
    my $writer = threads -> create ( \&writer ); 
    
    while ( 1 ) {
       print @shared_struct;
       sleep 1;
    }
    

    More generally, I'd suggest you almost never actually want to detach a thread in perl - in doing so, what you're saying is 'I don't care about your execution'. And clearly that's not the case in your code - you're trying to talk to the threads.

    Just creating the thread accomplishes what you want - parallel execution and you can have:

    for my $thread ( threads -> list ) { 
    
       $thread -> join; 
    }
    

    As and when you're ready for the thread to terminate.