Hi,
Thanks for the explanation Eric, it works better already.
I'm stripping my code to see where the further problems are.
One of the things I encounter is that as soon as start doing actual SQL statements (mostly select) the context switches go up and the request executed go down.
So I stripped all of them and it provided quite an improvement (from an average of 6,5 request executed / sec. to about 25).
Still there are context switches. So I'm stripping my code to see where the problem is.
This brings me to my question: what general code design considerations do I have to apply to optimize my code for multi-threading. I can't find much info on this on the net (maybe I'm not searching right..)?
What can I do / shouldn't do? For instance, if I leave a recursive function out of the code, it improves the requests executed / sec. Does this means that recursion techniques are better not to be used in multi-threading applications?
Is there a good book on this? I know I learned most of the stuff myself but it seems I'm missing some basics.
Hope someone can help a little,
Regards
Jeroen
------------------
[This message has been edited by jeroen brouwers (edited February 09, 2001).]
Announcement
Collapse
No announcement yet.
re-entrance, threading and multi-user
Collapse
X
-
OK!
I was already doubting that and have tried something similar (without results then) but I'll give it another try. Thanks.
I'll let you know how it goes
Sincerely
Jeroen
------------------
Leave a comment:
-
Jeroen --
I have not looked at your code in detail, but your use of Critical Sections is clearly not correct.
Since your Critical Section is LOCAL, every time you call your function a new Critical Section will be created. The function with then "claim" it, do its work, "release" it, and then destroy it. That's useless because no other thread can possibly attempt to enter the Critical Section, since it is LOCAL. It will never block any thread from doing anything at all. In other words, if a second thread calls that function, it will get its own LOCAL critical section. Critical Sections must be GLOBAL in order to work. (Ok, I suppose you could use a STATIC CRITICAL_SECTION, but it would be hard to write, and why do it? The whole idea is to create a structure that all threads can access.)
Here's a thumbnail sketch of the way you should use a Critical Section...
1) In WinMain, DIM gtCritSect AS GLOBAL CRITICAL_SECTION then, also in WinMain, InitializeCriticalSection gtCritSect
2) In the sub or function you want to protect, use EnterCriticalSection gtCritSect at the very beginning and LeaveCriticalSection gtCritSect at the very end. Make sure there is no way to exit from from the sub/function without using LeaveCriticalSection, or the next time ANY thread calls that function it will lock up forever.
3) In WinMain, at the very end of your program, DestroyCriticalSection gtCritSect.
An arrangement like that will allow a thread to call that function, but if another thread tries to call it while the first thread is still using it, the second thread will be "paused" until the first thread executes LeaveCriticalSection. At that moment, the second thread will be un-paused and allowed to continue.
-- Eric
------------------
Perfect Sync: Perfect Sync Development Tools
Email: mailto:[email protected][email protected]</A>
[This message has been edited by Eric Pearson (edited February 07, 2001).]
Leave a comment:
-
Thanks Florent,
If you could spare a little time later on, I would be most happy.
At my Entry Function :
FUNCTION RunThrough_Order ALIAS "RUNTHROUGH_ORDER" (BYREF VBSession AS SessionInfo) EXPORT AS LONG
right in the beginning I declare two types
LOCAL Session AS SessionInfo
LOCAL INTERNAL AS INTERNALINFO
I pass the incoming info
session = VBSession
and fill the internal with general values
result1& = GetGlobalVars(Internal)
Then I call the function that handles the one global array ThrStmt :
LOCAL St AS DOUBLE
result& = StmtNrAdd(St, 1)
Internal.FileNr = St
Internal.ThrStmt = St
The function StmtNrAdd is used to add, delete and get the value from the array:
Code:FUNCTION StmtNrAdd(StmtNr AS DOUBLE, Code AS DOUBLE) AS LONG LOCAL GSC1 AS CRITICAL_SECTION InitializeCriticalSection GSC1 EnterCriticalSection GSC1 IF Code = 1 THEN ' add IF UBOUND(ThrStmt) = -1 THEN ' 1e keer REDIM ThrStmt(1,1) ThrStmt(0,0) = 0 ThrStmt(1,0) = TIMER END IF REDIM PRESERVE ThrStmt(1, UBOUND(ThrStmt, 2) + 1) ThrStmt(0,UBOUND(ThrStmt,2)) = ThrStmt(0,UBOUND(ThrStmt, 2) - 1) + 2 ThrStmt(1,UBOUND(ThrStmt,2)) = TIMER StmtNr = UBOUND(ThrStmt,2) IF ThrStmt(0,StmtNr) >= 256 THEN '?? END IF ELSEIF Code = 0 THEN ' set to nul zetten ThrStmt(0,StmtNr) = 0 IF StmtNr = UBOUND(ThrStmt,2) THEN ' delete if possible LOCAL i AS INTEGER, Hit& FOR i = UBOUND(ThrStmt,2) TO 1 STEP -1 IF ThrStmt(0,i) <> 0 THEN IF TIMER - ThrStmt(1,i) > 15 THEN ThrStmt(0,i) = 0 ELSE Hit& = 1 EXIT FOR END IF END IF NEXT i IF Hit& = 1 THEN REDIM PRESERVE ThrStmt(1,i) ELSE REDIM ThrStmt(1,0) END IF END IF ELSE ' get value FUNCTION = ThrStmt(0, StmtNr) END IF LeaveCriticalSection GSC1 DeleteCriticalSection GSC1 END FUNCTION
About the VB Class:
Yes, I used and ActiveX DLL that is called from an ASP webpage using
Set VBObj = Server.CreateObject(<project name>, <class name> )
as to create the object using
Temp = VbObj.<function name> ( <inputvalues> )
to activate the function.
The ActiveX DLL also has a module where the type is set.
The actual declaration is done inside the function, i.e. local. The incoming values in the function are passed to the Type, which is passed to the PBDLL. The results of the PB DLL are stored in this type and come back in the ActiveX which - in turn - passes the values from the Type to the original values that came in. The ASP can then read these and correspond accordingly. Simple, no?
This works OK (as far as I can oversee).
The ActiveX is apartment threaded (not single) which should allow for the IIS to create a thread for each instance, i.e. web request for the page.
COM uses only 1 thread per page, which make it difficult to test manually. So we use WCAT which generates users and uses a GET to get the results from the page that calls the object.
If you get into that stuff, it gets kind of confusing (to me at least).
I cleared the registy and recompiled to check for version issues and properties.
Does this still make sense?
Regards
Jeroen
------------------
Leave a comment:
-
Hi Jeroen
you mentioned you used a TYPE to pass information around - is the
type declared using dynamic memory? Where is the type declared from?
You also mention using a Global array to hold statement number
information. How do use use it? Do you Redim this Global array?
You mentioned that access to the array is serialized with a critical
section - how do you know which statement belongs to which thread, etc?
I take it you are using VB and that the DLL is written in PB. Did
you encapsulate the DLL using a VB Class?
As I said difficult to really offer concrete advice in this situation.
I'm pressed for time at the moment but if you get really stuck you
could email me your code and I could throw a quick look at it to
see if something stands out.
My mail is mailto:[email protected][email protected]</A>
Cheers
Florent
------------------
Leave a comment:
-
Thanks Florent,
I was almost rewriting my code already ...
I can't really put all of my code on the web (is a lot already), but I'm getting stuck on the things to look for.
Fact is the IIS crashes as soon as I link my script engine to it. That is, if I call it one hundred times, it's OK; make it 500 and it crashes.
The more I add, the higher the context switches / sec. and the lower the request executed / sec.
So I've stripped all i/o, I've made as many variables local as possible, I've protected the 2 arrays that are still global by critical section, I've left out the internal threading for testing purposes, I've checked that at all times arrays are checked on their ubound (being -1).
If it is a call to a non existing dimension of an array, why would it appear in multi-threading and not in a single request format?
I don't know what to look for anymore. Getting lost here.
Any tips / suggestions what to check for a appreciated.
Hope you can help
Sincerely
Jeroen
------------------
Leave a comment:
-
Jeroen
the code examples you posted are incorrect - neither one nor the
other is more or less thread safe than the other however your test2
function will not compile since you should not use a stack
parameter as a loop counter....
Additionally you are using string arrays and attempt to assign
the entire array to the array subscript which will not work.
This code will compile:
Code:FUNCTION Test1 ( InputString() AS STRING ) AS LONG IF UBOUND (InputString) = -1 THEN EXIT FUNCTION LOCAL i AS INTEGER FOR i = 0 TO UBOUND(InputString) IF UCASE$(InputString(i)) <> InputString(i) THEN InputString(i) = UCASE$(InputString(i)) END IF NEXT i END FUNCTION FUNCTION Test2 (InputString() AS STRING, OutputString() AS STRING) AS LONG IF UBOUND (InputString) = -1 THEN EXIT FUNCTION LOCAL i AS LONG FOR i = 0 TO UBOUND(InputString) IF UCASE$(InputString(i)) <> InputString(i) THEN OutputString(i) = UCASE$(InputString(i)) END IF NEXT i END FUNCTION
function - how are the arrays declared - is there any risk in your
code that the arrays will get overwritten, does each array point
to separate memory, etc...
Having different input/output parameters will not make your code
any more or less thread safe. You need to remember: stack and local
variables are automatically thread-safe.
The whole issue of writing thread safe code is making sure memory
/ variables do not get overwritten/corrupted from thread to thread.
That means essentially no globals or static variables unless you
synchronize those variables.
A type passed on the stack is no different from any other stack
variable.
This is essentially a design issue and without knowing the design
of your app it's difficult to give any specific advice. The GPFs
you're seeing could just as well be provoked by invalid memory
access (for example accessing an array past its bounds, etc).
Cheers
Florent
[This message has been edited by Florent Heyworth (edited February 07, 2001).]
Leave a comment:
-
Thanks Tom,
'Apartment threaded' is the ActiveX variation that handles some form of multi-threading for the web via CoMarshalling in IIS.
We tested it with a smaller component which works just fine (WCAT : 250 user simultanious).
The thing is: the final goal is to have 8 interlinked DLLs (response time ~ 1 sec) linked to a web page. So speed is of great important (reason for PB in the first place).
This will not work (somehow). I wrote a script engine which is called about 700 times per request. What we see in IIS is - besides the crash - huge context switches (we're talking more than 75.000!).
If you start reading on context switches you will mostly find that they are caused by doing i/o work (so I cut it out) or some other form of 'shared resource'. So I thought: like database statement numbers. Solved it (each request gets its unique statement number).
Tried the trick with copying the DLL(s) for each request, it got even worse!
So then I found that a context switch is - in basis - a switch between threads because one thread has to wait for the other, so it provides control to the next one in the queuing list. Which causes a context switch. Like critical sections (skipped as many globals - that have to be protected - as possible), no change.
The thing is: there's a strong correlation between context switches and the likelyness of IIS to crash!
So back to the drawing table.
The idea that they (the people from the mentioned website) pose is this:
Code:'Code examples (re-write of the given functions in C++ to PB) ' non re-entrant ' reason1: input to function = output of function ' reason2: local initialization of i FUNCTION Test1 ( InputString() AS STRING ) AS LONG IF UBOUND (InputString) = -1 THEN EXIT FUNCTION LOCAL i AS INTEGER FOR i = 0 TO UBOUND(InputString) IF UCASE$(InputString(i)) <> InputString(i) THEN InputString(i) = UCASE$(InputString) END IF NEXT i END FUNCTION ' perfectly re-entrant ' solution by a change of 'interface', i.e. design FUNCTION Test2 (InputString() AS STRING, OutputString() AS STRING, i AS INTEGER) AS LONG IF UBOUND (InputString) = -1 THEN EXIT FUNCTION FOR i = 0 TO UBOUND(InputString) IF UCASE$(InputString(i)) <> InputString(i) THEN OutputString(i) = UCASE$(InputString) END IF NEXT i END FUNCTION
I would like to now if this is true (before I start recoding about 1 Mb of compiled code).
If so, what nuances can be made regarding storing values in a type: does the storage principle apply to members of a type or to the type as a whole?
Regards
Jeroen
------------------
[This message has been edited by jeroen brouwers (edited February 07, 2001).]
Leave a comment:
-
"Apartment threaded", I am not familiar with. If this involves VB, please
be advised that Visual Basic provides only limited support for threading,
which may be the root of your problem.
1) and 2) seem to involve some confusion? What do you mean by "the
interface"? I can think of no point to storing a value in a different
variable. Certainly, there should never be any need to change the type
of a value.
3) No, local variables are ideal for reentrant routines. You just need
to avoid global or STATIC variables unless you fully understand all the
implications of threading. There is no automatic blocking implied here.
Keep studying! It sounds like you're making progress. I will be glad to
help with any specific questions.
------------------
Tom Hanlin
PowerBASIC Staff
Leave a comment:
-
re-entrance, threading and multi-user
Hi,
I've been working on a DLL for some time now and - as a beginner - not realized the consequences of programming for multi-user applications.
As soon as I link my dll to an ASP page using a COM (apartment threaded), I'm running into problems (i.e. crash of the web server).
I've read Rector & Newcomer on synchronization, so I skipped all the globals from my DLL and put them into a type which I pass around from one function to another. I only use one global array to store a value that I use for statement numbers. The function that handles all changes and reading is starting with a critical section.
It still didn't work. I've left out the threading that I use within my dll, no change.
I've tried to copy the DLL (trick I found on this BBS), no real change.
Now I've found this : http://www.unet.univie.ac.at/aix/aix..._safe_code.htm
Basically, they tell in a simple matters what you should and shouldn't do (finally found one!).
Anyway, just some questions about this (assuming the 'rules' for 'perfect' re-entrance):
1) It is said that it's better to change the interface to have different incoming and outgoing variables.
Am I correct if this goes as far as : as soon as a var changes in a function, I should store the values in a differnt variable?
2) What about a Type then? Is it applying to the Type as a whole or just a member? To put it differently : When Type1 is passed to a function that changes member1, can I store it in member2 or should I store it in a differnt Type alltogetter.
3) The use of local vars is - if I'm not mistaken - not really recommended if you want to have a perfect re-entrance situation. The initialization of the local is blocking the next thread.
How much is this affecting the performance? Is it really that bad?
How do you do this then? I can see it for one function but you have to initialise the variables somewhere! Is there a 'best practice' in this regard?
Hope someone can help.
Sincerely
Jeroen Brouwers
------------------
Tags: None
Leave a comment: