Announcement

Collapse
No announcement yet.

re-entrance, threading and multi-user

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • jeroen brouwers
    replied
    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).]

    Leave a comment:


  • jeroen brouwers
    replied
    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:


  • Eric Pearson
    replied
    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:


  • jeroen brouwers
    replied
    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
    So in my DLL , I don't use 'var& = ThrStmt(0, Internal.ThrStmt)' but I use 'StmtNr = StmtNrAdd(Internal.ThrStmt, 2)' to make sure the global data are protected.


    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:


  • Florent Heyworth
    replied
    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:


  • jeroen brouwers
    replied
    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:


  • Florent Heyworth
    replied
    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
    The whole issue here is that you are passing string arrays to a
    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:


  • jeroen brouwers
    replied
    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:


  • Tom Hanlin
    replied
    "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:


  • jeroen brouwers
    started a topic re-entrance, threading and multi-user

    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



    ------------------
Working...
X