Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

Re: a question on threads

by BrowserUk (Patriarch)
on May 11, 2005 at 03:40 UTC ( [id://455849]=note: print w/replies, xml ) Need Help??


in reply to a question on threads

Rather than starting a new thread each time you might be better using a pool of long running threads that trigger from a time taken from queue.

The number of threads required to ensure prompt service will depend upon the time they take to do what they have to do. If the time is always less than 1 second, then you could get away with a pool of 1, but if they might take longer, you'll need more:

#! perl -slw use strict; use Time::HiRes qw[ time sleep ]; use threads; use Thread::Queue; $| = 1; our $N ||= 10; our $THREADS ||= 2; our $DELAY ||= 2; my $running : shared = 0; sub t{ $running++; my( $Q )= @_; my $tid = threads->self->tid; while( my $time = $Q->dequeue ) { sleep .1 until time() >= $time; print "$tid ($time) : ", time(); sleep $DELAY; } $running--; } my $Q = new Thread::Queue; threads->create( \&t, $Q )->detach for 1 .. $THREADS; my $start = int time(); $Q->enqueue( $_ ) for $start + 2 .. $start + 2 + $N; $Q->enqueue( undef ) for 1 .. $THREADS; sleep 1 while $running; __END__ P:\test>455833 -THREADS=2 -N=10 -DELAY=1 1 (1115781909) : 1115781909 2 (1115781910) : 1115781910.09375 1 (1115781911) : 1115781911.09375 2 (1115781912) : 1115781912.07813 1 (1115781913) : 1115781913.07813 2 (1115781914) : 1115781914.0625 1 (1115781915) : 1115781915.0625 2 (1115781916) : 1115781916.04688 1 (1115781917) : 1115781917.04688 2 (1115781918) : 1115781918.03125 1 (1115781919) : 1115781919.03125 P:\test>455833 -THREADS=2 -N=10 -DELAY=3 1 (1115781925) : 1115781925.07813 2 (1115781926) : 1115781926.0625 1 (1115781927) : 1115781928.07813 2 (1115781928) : 1115781929.0625 1 (1115781929) : 1115781931.07813 2 (1115781930) : 1115781932.0625 1 (1115781931) : 1115781934.07813 2 (1115781932) : 1115781935.0625 1 (1115781933) : 1115781937.07813 2 (1115781934) : 1115781938.0625 1 (1115781935) : 1115781940.07813 P:\test>455833 -THREADS=3 -N=10 -DELAY=3 1 (1115781957) : 1115781957.07813 2 (1115781958) : 1115781958.0625 3 (1115781959) : 1115781959.04688 1 (1115781960) : 1115781960.07813 2 (1115781961) : 1115781961.0625 3 (1115781962) : 1115781962.04688 1 (1115781963) : 1115781963.07813 2 (1115781964) : 1115781964.0625 3 (1115781965) : 1115781965.04688 1 (1115781966) : 1115781966.07813 2 (1115781967) : 1115781967.0625

Note how in the second run above, there were not enough threads running for the delay and so they gradually get further and further out of sync each timeslot. And also, how adding one more thread brings that back in the third run.

Also, you'll get better accuracy by sleeping for a smaller time period and checking whether the timeslot has arrived than sleeping for an absolute time. The smaller the sleep, the greater the accuracy, but it will still never be always exact (within some small descrepancy) because you have to wait for the scheduler to give the appropriate thread a timeslice.

Also, reducing the sleep below a certain minimum will greatly increase the cpu time spent polling. A tenth of a second works well on my system, giving an accuracy of < 1/10th whilst burning negligable amounts of cpu.


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
"Science is about questioning the status quo. Questioning authority".
The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.

Replies are listed 'Best First'.
Re^2: a question on threads
by thcsoft (Monk) on May 11, 2005 at 08:47 UTC
    thank you very much for your precious advice, and the example code. to give you an update on my code, as i've done it so far:
    sub event_handler { my $worker_thread = sub { my $row = shift; my $event = SOD::EventCooker->new($row); if (exists $p_ids{$row->{'ev_p_id'}}) { lock $sessions{$p_ids{$row->{'ev_p_id'}}}; my $session = thaw sessions{$p_ids{$row->{'ev_p_id'}}}; $event->evaluate($session->{PLAYER}->{GAMESTATS}); $sessions{$p_ids{$row->{'ev_p_id'}}} = freeze $session; } else { $event->evaluate; } }; my $dist_thread = sub { my @threads; my $thread_time = time; my $dbh = DBI->connect("DBI:mysql:database=$appdata{DBBASE}" +, $appdata{DBUSER}, $appdata{DBPASS}, { RaiseError => 1, AutoCommit => 1 }) || die $dbh->errstr; my $res = $dbh->selectall_hashref("SELECT * FROM event WHERE + ev_time<=$thread_time", 'ev_time'); $dbh->do("DELETE FROM event WHERE ev_time<=$thread_time"); map { push @threads, threads->new($worker_thread->($res->{$_}) +); } sort keys(%$res); map { $_->detach; } @threads; $dbh->disconnect; undef $dbh; }; while (1) { sleep 1; last if $tflag; threads->new($dist_thread)->detach; } }
    i assume that a combination of our two approaches might be sufficient even for time of high traffic & loads.

    language is a virus from outer space.
      assume that a combination of our two approaches might be sufficient even for time of high traffic & loads.

      It really depends upon how you combine them :)

      What I can say is that your current code that starts two threads every second with one of them making a new connection to a database each time and the other freezing and thawing a compound structure to and from a shared hash is not going to run quickly.

      Perl's threads are quite different from the kind threads found in say Java or Ruby where this kind of "spawn a thread, do a little and throw it away" coding style is common. and practical. Even there, creating new connections to MySql at the rate of one per second and then abandoning them is very likely to consume (leak) resources both in the DBI layers and at the MySQL server end.

      I seem to recall that (at v3.xomething), MySQL would not reuse a dropped connection for something like 15 minutes or so? This may have been fixed or be configurable or I may have remembered it wrong, but in any event it is a far from ideal strategy if you are hoping to handle high data rates.

      Using a single, long running thread that makes the connection to the DB when it starts and then reuses the handle to issue queries is a much better idea. Without seeing where, how and when your event_handler routine is called; and what SOD::EventCooker is; and what is populating your 'event' table, it is quite hard to advise further.

      A high level pseudo-code description of your app might allow us to advise further.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
        the code snippet i posted is part of a daemon process, which has mainly the purpose of serving and cacheing sessions. as now it is going to become (at least an interface to) the engine of a browser game, the amount of work it has to do has grown immensely. the process consists of mainly 4 threads: the main thread listens to a TCP socket and replies to requests coming from ModPerl::Registry scripts ('CGI'). a second thread observes the validity of sessions and invalidates them, if necessary. the third thread is what we're talking about. fourth there is a logging thread.

        the problem i have to face is that perl references may not be shared between threads. that's why session objects are being kept frozen on the shared hashtable. and even worse: that's also why the distributor subthread in the event handler has to explicitly connect each time. it would have been a lot less difficult, if i could have opened a pool of cached connections to the database and pass them around my subthreads. but that's a dream. i even tried freezing the db-handle. :(

        the EventCooker is a thing which i have umm... borrowed from ModPerl::RegistryCooker. events will be put into the database at relevant user requests: each event is represented by one table row. in order to keep the engine flexible, it wouldn't be appropriate if i packed all event handling code into a bundle of libraries - as then i would have to restart the daemon each time i modified one line. so now, the code which handles a certain type of event, may now be a perl script, which then will be executed by the EventCooker.

        what even worsens the story is, that most probably all of these scripts will have to connect again to the database, as they will need further information. but maybe there'll be a workaround for this problem with a more diligent query from the distributor thread.

        language is a virus from outer space.
Re^2: a question on threads
by thcsoft (Monk) on May 12, 2005 at 06:34 UTC
    ok, i modified my code according to your suggestions. i put the snippet on http://no-subject.org/event_handler.snippet (because i'm too lazy re-editing it here. ;)
    i haven't tested it yet, so there might be some smaller bugs, but in principle, it represents the compromise we were talking about.

    many thanks for your help! :)

    language is a virus from outer space.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://455849]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (4)
As of 2024-04-16 07:22 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found