in reply to Is a Net::CIDR::Lite object sharable within threads

I need to test single IPs against listed CIDRs (a small (<10) set)

Personally, on the basis of that description--ie.readonly use of the object once initally constructed--I wouldn't try to share the object.

Simply create the object before you create your threads and allow it to be cloned into the threads, either passed as an argument or through closure. Each thread will then have it's own (identical) copy of the object and can go ahead with the comparisons at full speed without any need for locking.

This seems to run perfectly:

#! perl -slw use strict; use Math::Random::MT qw[ rand ]; use Net::CIDR::Lite; use threads; our $WORKERS ||= 10; sub n2ip { join '.', unpack 'C4', pack 'V', $_[ 0 ] } sub worker { my $tid = threads->tid; my $CIDR = shift; for( 1 .. 1000 ) { my $ip = n2ip( int( rand 2**32 ) ); printf "[$tid] $ip was %s\n", $CIDR->find( $ip ) ? 'found' : 'not found'; } } my $CIDR = Net::CIDR::Lite->new( map{ n2ip( int( rand 2**32 ) ) . '/' . ( 1+int( rand 32 ) ) } 1 .. 10 ); print for $CIDR->list; printf "Enter to run threads"; <>; my @threads = map{ threads->create( \&worker, $CIDR ); } 1 .. $WORKERS; $_->join for @threads; __END__ C:\test>CIDR -WORKERS=100 16.0.0.0/4 133.105.224.0/20 155.64.0.0/10 191.83.33.64/26 192.0.0.0/3 224.0.0.0/4 Enter to run threads [1] 133.253.94.106 was not foun [1] 142.170.146.37 was not foun [1] 255.246.52.63 was not found [1] 223.1.163.7 was found [2] 93.38.214.136 was not found [3] 74.172.118.234 was not foun [3] 205.199.108.66 was found [3] 124.251.49.2 was not found [3] 230.87.252.108 was found [3] 195.216.205.202 was found [3] 240.11.9.134 was not found [3] 234.100.16.237 was found [3] 210.137.186.2 was found

If you needed to add new IPs or ranges to the object on the fly, then I'd use a client-server arrangement coordinated through a queue and a shared hash...but that doesn't seem to be necessary for this case.


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
"Too many [] have been sedated by an oppressive environment of political correctness and risk aversion."

Replies are listed 'Best First'.
Re^2: Is a Net::CIDR::Lite object sharable within threads
by Wiggins (Hermit) on Dec 03, 2008 at 12:56 UTC
    "basis of that description--ie.readonly use of the object once initially constructed-"
    I did not state that the list is created/updated dynamically during the run, with values supplied by cooperating processes.

    if I :

    • create the 'object' in the main thread before worker threads are created;
    • and use either 'our' or 'use vars' with that object;
    • and do the locking to single-thread access
    then might the object use main thread memory (during dynamic update), even when called from a worker.thread?

    I did like your reply though. I spent a couple of hours trying to figure out the 'pack'ing process to create 32-bit integers from string IPs and such. I thought I would simply create a shared hash with members containing the base network and the mask. Then I could do the comparisons myself and have a much more transparent solution. Unfortunately, the abstraction layer of Perl's implementation is still very opaque to me...

      if I : ... then might the object use main thread memory (during dynamic update), even when called from a worker.thread?

      Unfortunately no. I wish that shared variables had been implemented that way as it would be

      1. More intuative.
      2. Faster.
      3. Simpler.

      The correspondance between "shared" and "global" is very strong. Simple, direct access to Perl's global variables (with user locking) would be conceptually far simpler to grasp, faster (no cloning required; nor to be subverted), and would have less (no) performance impact upon lexical variables, as they would never be shared, so none of the internal locking would be required. And that would benefit everyone including those not using threads. But that horse bolted a long while ago.

      As I said above, for dynamic use, I'd use a client-server approach to sharing the CIDR object. (With some help from JadeNB & ikegami) I came up with this implementation which seems to work quite well and can sustain close to 30,000 queries per second from 10 concurrent threads. Which if there is any IO in your threads should be more than sufficient:

      #! perl -slw use strict; use Math::Random::MT qw[ srand rand ]; use Net::CIDR::Lite; use threads qw[ yield ]; use threads::shared; use Thread::Queue; our $WORKERS ||= 10; our $N ||= 1000; sub n2ip { join '.', unpack 'C4', pack 'V', $_[ 0 ] } sub worker { my $tid = threads->tid; srand( $tid ); my( $Q, $sem ) = @_; for( 1 .. $N ) { my $ip = n2ip( int( rand 2**32 ) ); $Q->enqueue( "$tid:$ip" ); ## 1 lock $$sem; ## 2 cond_wait $$sem; ## 3 printf "[$tid] ($_) $ip was %s\n", $$sem ? 'found' : 'not foun +d'; yield; ## 4 } } my $Q = new Thread::Queue; my @sems = map{ my $var :shared; \$var } 0 .. $WORKERS; my @threads = map{ threads->create( \&worker, $Q, $sems[ $_ ] ); } 1 .. $WORKERS; my $CIDR = Net::CIDR::Lite->new( map{ n2ip( int( rand 2**32 ) ) . '/' . ( 1+int( rand 32 ) ) } 1 .. 10 ); print for $CIDR->list; printf "Enter to run threads"; <>; while( threads->list(threads::running) > 1 ) { my( $tid, $ip ) = split ':', $Q->dequeue; print "Got $ip from $tid"; my $found = $CIDR->find( $ip ); $CIDR->add( "$ip/1" ) unless $found; ${ $sems[ $tid ] } = $found; lock ${ $sems[ $tid ] }; cond_signal ${ $sems[ $tid ] }; yield; } $_->join for @threads;

      This is not trivial code (and I haven't commented it), so please ask if there is anything you need clarifying. One thing I will point out is that both yields are necessary for reliable operations.

      The essential steps for the client threads are the four I've tagged. The server end is the while loop in the main thread which should be usable pretty much as is. You could also hive that off into its own thread if you have something better for your main thread to be doing.

      I spent a couple of hours trying to figure out the 'pack'ing process to create 32-bit integers from string IPs

      There are functions in one of the modules that do the conversion of ips to ints and vice versa, but I can never remember the name of the module or the (stupid) names of the functions, and find it quicker to role my own. For IP to int I use:

      sub ip2n{ unpack 'V', pack 'C4', split '\.', $_[ 0 ] }

      Of course, someone will point out that ips can contain hex or decimal components and that won't deal with them. But the only times I've ever encountered them, have been when some oik is trying to disguise them for nefarious purposes, so having it fail for them is a positive in my book.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
Re^2: Is a Net::CIDR::Lite object sharable within threads
by Wiggins (Hermit) on Dec 03, 2008 at 14:34 UTC
    My approach with the Net::CIDR::Lite doesn't seem to play with threads... So I am falling back to a simplier (non-object) approach.

    Just an FYI, I ran (3 times) your code in a VMware(6.0.4) RH9 on top of WinXP and ended up with 3 Segmentation Faults after 5850|5377|6055 lines of output.
    /usr/lib/libglib-2.0.so -> ../../lib/libglib-2.0.so.0.1600.6
    /lib/libglib-2.0.so.0 -> libglib-2.0.so.0.1600.6

    *** glibc detected *** perl: double free or corruption (!prev): 0x096d +f3a0 *** ======= Backtrace: ========= /lib/libc.so.6[0x18e874] /lib/libc.so.6(cfree+0x96)[0x1908d6] /usr/local/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi/auto/Mat +h/Random/MT/MT.so(mt_free+0x1d)[0x2d98fd] /usr/local/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi/auto/Mat +h/Random/MT/MT.so(XS_Math__Random__MT_DESTROY+0x1fb)[0x2d8c3b] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_pp_ +entersub+0xb29)[0x3e6819] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_cal +l_sv+0x743)[0x3dfd63] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_sv_ +clear+0x203)[0x40d3f3] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_sv_ +free2+0x77)[0x40e177] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_sv_ +free+0xb1)[0x40e2d1] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so[0x40ef5c +] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so[0x3f4ab9 +] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_sv_ +clean_objs+0x3d)[0x3f4b5d] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(perl_des +truct+0x1398)[0x3e2718] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/auto/threads/threads.so[ +0x2e2c66] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/auto/threads/threads.so( +XS_threads_join+0x630)[0x2e3c00] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_pp_ +entersub+0xb29)[0x3e6819] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(Perl_run +ops_debug+0x153)[0x3a8023] /usr/lib/perl5/5.10.0/i386-linux-thread-multi/CORE/libperl.so(perl_run ++0x4b9)[0x3e0ba9] perl(main+0x10e)[0x8048a2e] /lib/libc.so.6(__libc_start_main+0xe6)[0x1375d6] perl[0x8048881]
      My approach with the Net::CIDR::Lite doesn't seem to play with threads... So I am falling back to a simplier (non-object) approach.

      My code above works fine with threads--but do it the hard way if you must.

      Just an FYI, I ran (3 times) your code in a VMware(6.0.4) RH9 on top of WinXP and ended up with 3 Segmentation Faults after 5850|5377|6055 lines of output.

      RH linux on top of VMware on top of XP! Sorry, but you get what you ask for in this life :)

      As for the segfault, it is:

      1. In Math Random::MT, which is only used to generate IPs and so is not a part of the solution you would use.

        Specifically, it is the XS code in M::R::MT that is trying to free up a C-runtime allocated (malloc'd) buffer that has apparently already been freed.

        Traditionally in C, is was not an error to free a pointer more than once which may explain why the error doesn't crop up elsewhere.

        VMs have nasty hacks under the covers to map calls to the virtual OS they are running, through to the hosting OS, which places far more stringent requirements upon the code they run than any author writing for non-VM hosted code needs to consider.

        It is up to you the user of other peoples code that you wish to run in a virtual environment to choose wisely or fix it yourself.

      2. You could just substitute calls to Perl's in-built rand and my demo would probably run fine, although the range of IPs generated would be limited under native Win32 due to the PRNG only generating 15-bits. Under *nix it is generally a 48-bit PRNG.

      You'll probably find you have far more trouble getting your shared hash approach running than you would the Net::CIDR::Lite via client-server. And it will probably run far more slowly as with a shared hash, you have no option but to lock the entire has every time, which means that which ever thread locks the hash, all the other threads will block if they need access.

      Anyways, good luck.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        I'm just getting back to this thread. I posted the SEGVEC and have been off working a non-object approach using a shared list of "MaskedNet" and "NetMask" pairs. Mixed with some bit manipulations.

        Let me look this over... Thanks.