Re: Shared memory and asynchronous access
by 1nickt (Canon) on Apr 05, 2017 at 14:26 UTC
|
Hello Guillaume, welcome to the Monastery and to Perl, the One True Religion.
See MCE::Shared which works on Windows as well as Unix-like OSes, and is in my experience fast and powerful, so I would assume could handle your use case ( See MCE::Shared::Condvar ). Its author marioroy visits the Monastery frequently, which can make it easier to get help with the modules.
Hope this helps!
The way forward always starts with a minimal test.
| [reply] |
|
|
Oh, thanks for the quick reply!
I think I have tried that one few days ago without success, but my syntax may have been wrong (I'm really new to this and learning on my own). I've done some progress since :) so I'll try again.
Edit
I actually remember now: I have looked at this solution but I don't understand how variable are shared between scripts with MCE::Shared I don't see any ID or anything that would make the link and when I try it I have to declare the variable in both script (e.g. using $var = MCE::Shared->scalar; ) and both end up being totally independent.
I might be missing something :(
| [reply] |
|
|
Generally, you would declare the shared variable in a script then fork a worker or workers to do the separate task, where they would have access to the shared var.
Update: Here's a very simple demo:
use strict; use warnings; use feature 'say';
use Time::HiRes qw/ time usleep /;
use MCE::Shared;
say sprintf '%-5s %-17s %-s', 'PID', 'Time', 'Action';
my $shared = MCE::Shared->condvar(0);
say sprintf '%s %.06f Init val: %s', $$, time, $shared->get;
my $pid = fork;
if ( $pid ) {
for ( 0 .. 19 ) {
say sprintf '%s %.06f write: %s', $$, time, $shared->incr;
usleep 10000;
}
} else {
while (1) {
usleep 50000;
say sprintf '%s %.06f READ: %s', $$, time, $shared->get;
}
}
__END__
Output:
$ perl shared.pl
PID Time Action
16648 1491411785.712927 Init val: 0
16648 1491411785.713604 write: 1
16648 1491411785.724157 write: 2
16648 1491411785.734731 write: 3
16648 1491411785.745270 write: 4
16648 1491411785.755807 write: 5
16650 1491411785.763780 READ: 5
16648 1491411785.766309 write: 6
16648 1491411785.776740 write: 7
16648 1491411785.787272 write: 8
16648 1491411785.797800 write: 9
16648 1491411785.808273 write: 10
16650 1491411785.814272 READ: 10
16648 1491411785.818806 write: 11
16648 1491411785.829300 write: 12
16648 1491411785.839957 write: 13
16648 1491411785.850539 write: 14
16648 1491411785.861082 write: 15
16650 1491411785.864522 READ: 15
16648 1491411785.871574 write: 16
16648 1491411785.882031 write: 17
16648 1491411785.892612 write: 18
16648 1491411785.903151 write: 19
16648 1491411785.913714 write: 20
16650 1491411785.914756 READ: 20
Hope this helps!
The way forward always starts with a minimal test.
| [reply] [d/l] [select] |
|
|
|
|
|
|
| [reply] |
Re: Shared memory and asynchronous access
by huck (Prior) on Apr 05, 2017 at 14:52 UTC
|
I have approached this problem in a different manner
I have gui.pl start compute.pl via a start system call.
system("start perl compute.pl -arg1 arg2 arg3 arg4 sourcefile.txt > de
+stinationfile.txt" );
I then have compute.pl write its progress to a file(not at the ms level), and when done write a completion message to that file. I then use another gui (gui2.pl) program to print that file to the browser (this can have a auto refresh if you like). The first gui.pl then tells the browser what the new gui address is (i pass a parm to the gui2.pl that tells it what file to monitor http://../gui2.pl?file=tracker_file). Now i had programs that took days to finish, and that was way beyond the timeout limit of a cgi session, and you wouldnt want an auto refresh either. the user just refreshed the gui2.pl page whenever they wanted to check if its done.
Ive been doing it this way for over a decade. i have little modules that make it easy to do. gui2.pl takes a specially crafted file that contains elements for many table-sections. One section displays timestamps at various milestones (input done@time, first phase done @time...), another section often displays record counts or patient counts as the process continues, and a third section displays informational progress methods. It can have as many tables as you want, and they are displayed in order of the first reference to them. Often i have warning messages displayed first, with a button that can cause the task to be terminated if the user doesnt feel the progress is viable. This requires compute.pl to record its "task id" into the tracking file so gui2.pl knows what to cancel. For the lack of a better name i now call this module websee.pm
This is an example
a line is a ctime, <space> <cmd> <space> <rest>
cmd=$s gives the pid.
cmd=P prints <rest> in the bottom section
cmd=T is a table, <rest> is taken to be a <table-name> <space> <row-name> <space> <col-name> <space> <col-contents>
1483271066 $s 3880 1483271066
1483271066 P started
1483271066 T table step started 2017-01-01-05-44-26
1483271067 T table copied started 000:00:01 000:00:01
1483271067 P d-time:000:00:01 000:00:01->auto:started:copied
1483271067 T table copied ended 000:00:00 000:00:01
1483271067 P d-time:000:00:00 000:00:01->auto:ended:copied
1483271067 T table blind_scrape_4a-setup.pl started 000:00:00 000:00:0
+1
1483271067 P d-time:000:00:00 000:00:01->auto:started:blind_scrape_4a-
+setup.pl
1483271067 T table blind_scrape_4a-setup.pl ended 000:00:00 000:00:01
1483271067 P d-time:000:00:00 000:00:01->auto:ended:blind_scrape_4a-se
+tup.pl
1483271067 T table blind_scrape_4b-worksish.pl started 000:00:00 000:0
+0:01
1483271067 P d-time:000:00:00 000:00:01->auto:started:blind_scrape_4b-
+worksish.pl
1483271701 T table blind_scrape_4b-worksish.pl ended 000:10:34 000:10:
+35
1483272144 P Ended
1483272145 $e 3880 1483272145
That produced a simple page, the $s creates the "kill-button" at the top. it exists till it finds the $e line.
It has one table "table" with 4 rows and two columns (started/ended), and the contents of the cells are formatted time pairs. The first is the interstep time and the second is the cumulative time.
Under the tables, the cmd=P lines are listed
This may be extreme for your case, but it has served me well since the late 90's. Like i said, some jobs ran for days, one ran for a week, Ive used this on windows and unixen. The process of "spawning" the separate command on unixen has used "at" and other queue managers. On windows ive just used start. | [reply] [d/l] [select] |
|
|
Thanks for your message. I would really like to avoid using a browser or similar external tool for this small application, I'm just trying to display a progress bar / percentage and the computing is not in the scale of hours/days but seconds/minutes so I expect the user to wait here, looking at the UI. Besides, I'm not interested by displaying the whole history of compute.pl (I understand you were doing that), but only the last value that I'm "trying" to grab.
Just for information, I'm currently using "qx" to execute compute.pl and capture it's exit code, which works fine.
| [reply] |
|
|
I went with the browser as my gui because back in 99 xwindows over a v.34 modem was a dog. I was 200 miles from the site. A browser form created the parms for the program, the browser instructed the server to start the program, and the browser monitored it via cgi. At the end of the process i could download a zip via the browser with any output files to my pc for looking at. As time progressed we had users across the world using this process to setup/run/monitor their programs. Each set of parms had its own page, where you could edit and start the run and thats where the custom link to the websee page and the last zip was. Many of the runs created html pages as output and they were linked on that page as well. I could disconnect and reconnect at will. I could start the program at home and watch it from the GF's house. I had a web tree that i could drill down to find the run i was interested in if i didnt bookmark the start page itself. If i created a new page for each combination of parms it presented a history tree of the runs as well.
| [reply] |
|
|
Re: Shared memory and asynchronous access
by BrowserUk (Patriarch) on Apr 06, 2017 at 04:48 UTC
|
Personally, if it is important to keep the two parts of your application as separate processes, I'd use udp (which should work on any platform) for the IPC.
The monitoring script would have something like this:
#! perl -slw
use strict;
use Time::HiRes qw[ time sleep ];
use IO::Socket;
$|++;
my $port = 54321;
socket( SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('udp') ) or die "s
+ocket: $!";
setsockopt( SOCKET, SOL_SOCKET, SO_REUSEADDR, 1 );
bind( SOCKET, sockaddr_in( $port, inet_aton( 'localhost' ) ) ) or die
+$^E;
my $true = 1; ioctl( SOCKET, 0x8004667e, \$true );
my $pid = system 1, "/perl64/bin/perl.exe", "monitored.pl",$port;
my $time;
while( kill 0, $pid ) {
my $addr = recv( SOCKET, $time, 1024, 0 ) or select '','','',0.001
+;
printf "\rProgress:%s\t", $time;
}
print "All done.";
And the monitored script only needs minimal code, that will have little or no effect on its performance if it is being run stand alone: #! perl -slw
use strict; ## monitored.pl
use IO::Socket;
$|++;
my $port = shift;
socket( SOCKET, PF_INET, SOCK_DGRAM, getprotobyname('udp') ) or die "s
+ocket: $!";
my $runtime = rand( 120 );
for my $milli ( 0 .. $runtime * 1000 ) {
select '','','', 0.001;
send( SOCKET, sprintf( "\r%.f%%\t", $milli / ($runtime*10) ), 0, s
+ockaddr_in( $port, inet_aton( 'localhost' ) ) ) or die "send: $^E";
}
Simple, reliable and minimally invasive.
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] [select] |
|
|
The following is based on BrowserUk's++ demonstration.
I tried a cross-platform solution and tested on Windows, Linux, and Mac OS. To make this interesting, am using IO::Socket::INET.
On the Windows platform, the time resolution in Perl is 0.015 milliseconds. Meaning, 0.001 ends up taking 0.015 time. Therefore, am passing 0.015 to select to have Unix sleep just as long.
use strict;
use warnings;
use IO::Socket::INET qw( SOL_SOCKET SO_REUSEADDR );
use Fcntl qw( F_GETFL F_SETFL O_NONBLOCK );
my $port = 54321;
my $socket = IO::Socket::INET->new(
Proto => 'udp',
LocalPort => $port,
LocalAddr => '127.0.0.1'
);
die "Unable to bind to 127.0.0.1:$port: $!\n" unless $socket;
setsockopt( $socket, SOL_SOCKET, SO_REUSEADDR, 1 );
stop_blocking( $socket );
$SIG{CHLD} = 'IGNORE';
my $pid = fork; die "Could not fork: $!\n" unless defined $pid;
exec( $^X, 'mon2.pl', $port ) if ( $pid == 0 );
$| = 1;
while ( kill(0, $pid) ) {
my $addr = recv( $socket, my $time, 1024, 0 ) or select '','','',0
+.015;
printf "\rProgress: %s", $time;
}
print "\nAll done.\n";
sub stop_blocking {
my $socket = shift;
if ($^O eq 'MSWin32') {
my $flag = 1; ioctl($socket, 0x8004667e, \$flag);
}
else {
my $flags;
$flags = fcntl($socket, F_GETFL, 0)
or die "could not getfl: $!\n";
$flags = fcntl($socket, F_SETFL, $flags | O_NONBLOCK)
or die "could not setfl: $!\n";
}
}
The monitored.pl script.
use strict;
use warnings;
use IO::Socket::INET qw( pack_sockaddr_in inet_aton );
my $port = shift || 54321;
my $socket = IO::Socket::INET->new( Proto => 'udp' );
my $runtime = rand 10;
$| = 1;
for my $num ( 0 .. $runtime * 100 ) {
send ( $socket, sprintf("%.f%% ", $num / $runtime),
0, pack_sockaddr_in($port, inet_aton('127.0.0.1'))
) or die "send: $^E";
select '', '', '', 0.015;
}
| [reply] [d/l] [select] |
|
|
use POSIX ":sys_wait_h";
while ( kill(0, $pid) ) {
my $addr = recv( $socket, my $time, 1024, 0 ) or select '','','',0
+.015;
printf "\rProgress: %s", $time;
my $res = waitpid($pid, WNOHANG);
}
made the original not hang on
This is perl 5, version 20, subversion 1 (v5.20.1) built for MSWin32-x
+86-multi-thread-64int
(with 1 registered patch, see perl -V for more detail)
Copyright 1987-2014, Larry Wall
Binary build 2000 [298557] provided by ActiveState http://www.ActiveSt
+ate.com
Built Oct 15 2014 22:10:49
| [reply] [d/l] [select] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Thanks for the reply. IO::Socket::INET was actually one of the first packages I tried (and I tried a lot of them...), but my Mac would systematically deny opening a socket. Sure I could have configured it to go around that but I thought I could find another solution than a network protocol that can always be blocked by some firewall or similar program.
Just to add a little bit of context since it doesn't seem to be clear in my first post, this program I'm writing is meant to be used by anyone without special configuration (if possible).
| [reply] |
|
|
|
|
|
|
exec( $^X, 'monitored.pl', $port ) if ( $pid == 0 );
| [reply] [d/l] |
|
|
$SIG{CHLD} = 'IGNORE' if ( $^O ne 'MSWin32' );
| [reply] [d/l] |
|
|
|
|
|
|
When i first saw this i wondered what select was doing in there, with empty texts for RBITS,WBITS,EBITS even. Then i realized it was just a sub-second sleep. is that correct?
Second i wondered what empty text strings would do to select. Didnt try it, but http://perldoc.perl.org/functions/select.html suggests this select(undef, undef, undef, 0.001); instead.
| [reply] [d/l] |
|
|
| [reply] |
|
|
| [reply] [d/l] |
Re: Shared memory and asynchronous access
by jmlynesjr (Deacon) on Apr 05, 2017 at 16:18 UTC
|
Also take a look at SDR Scanner(aka Threaded wxPerl Example). It uses shared variables to communicate between the GUI and the processing code.
James
There's never enough time to do it right, but always enough time to do it over...
| [reply] |
|
|
Thanks James, but I think there is a confusion between variable in a same script and different, independent scripts. I have other threads running in this application and I have had no problem sharing variables (using Threads::shared it's very easy), generating events...etc.
Again, my main issue to share information between two processes that are completely independent. It seems that shared memory (there are different packages/solutions out there) is my solution but maybe you have better suggestion since I'm not able to put it in place (except on Mac/Linux).
| [reply] |
|
|
Ah... Currently, MCE and MCE::Shared do not work for two or more processes that are completely independent. It seems like the next step to tackle. All the parts are there. Basically, what's missing is for the shared-manager to support TCP/IP. That will be awesome if it could do that.
MCE now includes MCE::Mutex::Flock supporting threads and processes. That includes two or more completely independent processes. It's a start, but TCP/IP is needed regarding the shared-manager. Who knows, it might happen. MCE::Mutex::Flock was added recently.
Thank you, vef445++. Thank you, Perlmonks++.
| [reply] |
|
|
| A reply falls below the community's threshold of quality. You may see it by logging in. |