in reply to multi thread problem in an simple HTTP server
The first thing you need to fix is this:
lock %{ $cnf{'lock'} };
(Hint: enabling warnings would tell you this!)
You never create a key in %cnf called 'lock'. (And when you do, it would have to contain a reference to a shared variable for that to work.)
Beyond that, there are several pretty dubious pieces of code:
Which almost certainly doesn't do what you think it does, and just serves to confuse things.
Updated in the light of Corion's post below. It seems that END() subs will be treated as END{} blocks...though the sub annotation seems to serve little purpose other than to confused. Making END-blocks compatible with ancient versions of Perl seems to serve little purpose in a program that uses threads.
You appear to think that your sub END will get called automatically (as you never call it directly), but that is not the case.
END{} blocks get called automatically, not subs called END().
# fix me, not work in win32, why? #$SIG{'INT'} = $SIG{'TERM'} = sub { lock %stat; $stat{'done'}++ };
What do you mean by "not work in win32"?
If uncommented, that statement (under win32) does install signal handlers for 'INT' and 'TERM' that do get called. So, in what way "not work"?
# fix me, the thread blocks here, why? # if I lock %{ $cnf{'lock'} } untill close the $c, things +will be OK, # but then we can't gain advantage of the multi-threads no +r the pre-threads # design. if ( my $r = $c->get_request() ) {
It is not at all clear what you mean by that?
To get to that point, this thread has accepted a connection and is now about to fetch the http request that connecting client made. How could it block?
Overall, your post poses no questions. The source code contains several 'fix me' annotations, but the details are terse and sketchy and seem to reflect your misunderstandings more than problem descriptions that can be addressed.
To progress this, you're going to have to detail each of the problems explaining what you think should happen and what actually happens.
You should also explain the format of the urls the clients will send, because expecting us to reverse engineer them from the code is pushing your luck.
In general, dumping a big chunk of code with a few embedded "fix me"s, is not a great way to go about getting help.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re^2: multi thread problem in an simple HTTP server
by Corion (Patriarch) on Apr 09, 2009 at 12:00 UTC | |
A small niggle. I guess that for compatibility reasons, there are special names for subroutines that make them get called implicitly:
outputs
I've used this trick when writing a module for a version of Perl that didn't understand CHECK or INIT blocks (5.04_03 or something like that). I declared these blocks as subroutines and then had another subroutine to execute them if the Perl version was too low. | [reply] [d/l] [select] |
by BrowserUk (Patriarch) on Apr 09, 2009 at 12:12 UTC | |
Just goes to show that there is always something new to learn, I've never knowingly encountered this form before, but there it is documented. Albiet that it seems to be a non-feature as they are still not callable in the normal way. But, I sit corrected :) Five specially named code blocks are executed at the beginning and at the end of a running Perl program. These are the BEGIN, UNITCHECK, CHECK, INIT, and END blocks. 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.
| [reply] |
|
Re^2: multi thread problem in an simple HTTP server
by bravesoul (Initiate) on Apr 10, 2009 at 02:44 UTC | |
Thanks for you quick suggestion, and excuse me for my confusing comment. Actually it is the first time I asking for help on the website, I do miss a lot of things. I am learning HTTP server in Perl, following the instruction of the book <Network Programming with Perl, By Lincoln D. Stein, December 15, 2000>, and doing a lot of test, it is my first attempt to implement a server with adaptive pre-threading technology. I am working on Microsoft Windows 2000 with Activestate perl v5.8.6 built for MSWin32-x86-multi-thread, and send HTTP request on Firefox web browser with the standard 'GET' method. | [reply] [d/l] [select] |
by BrowserUk (Patriarch) on Apr 10, 2009 at 14:14 UTC | |
Okay. Let's get the easy ones out the way first. 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.
| [reply] [d/l] [select] |
by bravesoul (Initiate) on Apr 12, 2009 at 15:58 UTC | |
| [reply] [d/l] |
by BrowserUk (Patriarch) on Apr 10, 2009 at 15:38 UTC | |
"if I do not add the last line of code, the main thread do not spawn enough 'worker' threads at the first time, only 2 ones created, but it should be 8 at this time. I guess the current thread is switched to the main thread at that time so I add threads->yield(), then one more 'worker' threads is spawned at the first time, but it is still not what I want. At last I add sleep(1), it is OK then, why?" You are misunderstanding what is going on here. Without the sleep, all the threads do get started--it just takes a minute or so for it to happen. The reason for the delay comes down to the locking you are using, but I'll come back to that. There is a far more fundamental issue to deal with first. That of how you are creating your worker threads: You are spawning each thread, using the thread handle return to obtain the thread ID, and using that to set up the control structures used by the thread:
But, the first thing your thread does is attempt to use the control structure:
Which your main thread hasn't yet created! And, you are also accessing that shared control structure without having locked it! To correct these problems, you could move the per-thread control structure creation inside the thread itself, and apply locking to the accesses:
But that raises another question: As these are 'per-thread' control structures, why are you having to lock them? Looking at the places outside of the worker thread where they are accessed, it all happens in you main thread loop. But your main thread loop is controlled by a message queue, and the messages are being sent from the worker threads. So the question then becomes: why send a message from a worker thread to the main thread to have it modify the shared, per-thread control structures when the worker threads could make those modifications directly? Indeed, the more I think about that logic, the more questionable the reasons for having a queue at all. There are other questions about your architecture that need answering, but I'll get back to them later. Now back to the question of why worker threads start up so slowly unless you add the sleep 1;. The problem arises here:
That lock ($stat{'lock'}) is a global lock(*). And accept() is a blocking call. So, once one thread has entered that lock and is waiting for a connection, no other thread will be able to progress past that point until the first one has either: received a connection; or the accept() times out. Basically, the workers start slowly because you have programmed them to do so. You can demonstrate this to yourself by adjusting the timeout and set the timeout to 1, then you'll see that the 8 workers start much more quickly. One per second (as the accept() times out) instead of one every 10 seconds. Other problems:(*)Why are you using a shared hash for locking? You never put anything inside that hash, so a shared scalar would do the job just as well. And probably with less overheads. Your whole locking strategy needs review. For example, you are using code like this:
You are applying a global lock, when you only need to access per-thread data.That means all threads will be blocked for the duration of that lock which will have a detrimental affect on the overall performance. You could avoid that, but only applying a lock to the per-thread that you wish to modify:
which would leave all the other thread free to run. However, there is still the question of whether this per-thread data needs to be shared in the first place. Enough for this post. More to follow. 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.
| [reply] [d/l] [select] |
by bravesoul (Initiate) on Apr 13, 2009 at 16:02 UTC | |
At last it works perfectly on my Cygwin environment, with perl v5.8.7 built for cywin-thread-multi-64int, so I think the 'per thread accept' architecture can't run well in the windows 2000/xp system Applying you suggestions I have reconstruct the script as below.
I still confused with threads::share, how can I share an item in the shared hash? For example, if I change as it cause 'thread failed to start: lock can only be used on shared values', at lock $self{'lock'} | [reply] [d/l] [select] |
by BrowserUk (Patriarch) on Apr 10, 2009 at 17:30 UTC | |
the biggest problem is in the code: ...when running, one thread get the 'lock' and accept a new connection, then 'unlock', and it is blocked at the last line. I had done following test, This will be affected by the changes required to address the other problems I've already tackled, but there is a particularly important point to make. Your basic architecture has one big flaw at its core: You are calling $listener->accept() in all your threads. The normal multi-processing (be it threading or forking), server architecture has a main loop and a single point where incoming connects are accepted. It then either: spawns a thread or process to handle the newly connected client; or passes the client handle to a pre-existing (pre-forked) thread or process to deal with. With your architecture as it stands, although each worker will be using a CLONED copy of the listener socket at the perl-level, underlying the perl-level structures and data, at some point within the C runtime, the OS, or the tcpip stack, they are all trying to use and control a single socket And whilst you are applying Perl-level locking on the resource which should prevent any Perl-level sharing problems, it is not at all clear to me what the effect of calling accept() on that single socket from multiple threads will be. Basically, I've never seen it done that way in either Perl or C. Maybe it is fine if you only enter into the accept state on one thread concurrently (per your locking). But maybe not. And it is quite likely to be affected by the C runtime and/or OS you are running. Maybe it is a clever way of dodging the 'socket passing' problem, but I'd have to either see a (fully working) example of the technique in use, or code up a (greatly simplified) example of my own to convince myself that it works correctly under load. Bottom line: there are a lot of basic errors in your code and some questionable architecture. It's not easy to suggest how to fix it up completely, as how you would correct the basic problems, (and whether they would be still be needed), really depends on how you decide to address the architectural issues. For example, if you take my advise about modifying the per-thread control structures within the threads themselves, rather than passing messages to the main thread asking it to do it, then the reason for having the queue pretty much disappears. All that would leave your main thread to do, is adjusting the number of workers in the pool. But even the way you are doing that is questionable. The normal method with a pre-forking server is to start a new thread if there is no idle thread available to field a new connection. I see what you are aiming for with your low-water mark mechanism--always having at least two idle threads available--but I haven't managed to push your code sufficiently hard to create the situation where that comes into play. And I am dubious abut he way you have it coded. Each connection is so brief that its pretty much impossible to test multiple concurrent connections, when creating them manually via browser. It would require some kind of automated client set up to drive it hard enough to test that scenario. But given all the other problems that need fixing, plus the open architectural questions, it is not worth the effort of testing that as things stand. So, the balls in your court to decide how you are going to proceed from here? I may get time to throw something together to explore the multiple-listeners question sometime, but the rest will have to wait until you decide how you are going to proceed. 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.
| [reply] [d/l] [select] |