Announcement

Collapse
No announcement yet.

Thread question

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

  • Thread question

    Hi,

    How many levels deep can a thread call other procedures which call other procedures. Is it save ??

    Any tips on threads in general are welcome.

    So here we are, this is the end.
    But all that dies, is born again.
    - From The Ashes (In This Moment)

  • #2
    The limit is really the amount of stack space you will use up with the deep nesting. Local scalar and ASCIIZ variables are created on the stack frame for a sub/function, so these can have the greatest impact on stack consumption. If large ASCIIZ strings are required by subs/functions, use static or global's instead (these do not occupy the stack frame).

    PB apps are given a 1Mb stack at start-up. This is likely to be definable in a future version of the compiler.

    Threads themselves are extremely handy and useful, but their very nature means that a strict approach to code design is required.

    LOCAL variables in thread functions are not a problem as they are private to each thread. However any use of GLOBAL variables from within a thread _can_ be dangerous if there is any possibility that two threads may try to write or read to any global variable (that is bigger than 32-bits).

    If a context change (task or thread switch) occurs when a memory block is only *partially* updated by one thread, and then this "partially updated" data is read by another thread, all sorts of problems can occur - the exact nature of such a problem is app dependent.

    This type of problem can be the *hardest* to solve as it cannot be duplicated at will or predicted with ease. The solution is to use "synchronization" between threads - traffic-lights for threads if you will.

    Synchronization is a complete topic in it's own right and opens up another minefield for the unwary - a appreciation of Starvation, Deadlock, and Mutual Exclusion must be understood.

    My advice is to get yourself a copy of "Win32 Programming" (Rector/Newcomer) and have a good read up on threading and synchronization - the expense of the book may save you from a lot of headaches.

    ------------------
    Lance
    PowerBASIC Support
    mailto:[email protected][email protected]</A>
    Lance
    mailto:[email protected]

    Comment


    • #3
      Lance

      Thanks for the prompt and elaborate explanation. BTW, I have that Win32 Programming book. Maybe I should re-read the table of contents of all my books again . Hopefully the 'rules' don't apply here

      Regards

      Steven.

      ------------------
      So here we are, this is the end.
      But all that dies, is born again.
      - From The Ashes (In This Moment)

      Comment


      • #4
        Lance,

        However any use of GLOBAL variables from within a thread _can_ be dangerous if there is any possibility that two threads may try to write or read to any global variable (that is bigger than 32-bits).
        What is the significance of 32-bits. Is this true even if you have synchronised your threads. Does this also apply to strings >32 bits?

        Regards,

        Bern


        ------------------
        Bern


        http://www.insighttrader.com.au

        Comment


        • #5
          If you always synchronize your threads to access GLOBAL variables, then you won't have any problems.

          The point at which synchronization becomes necessary is when such a variable may be wider than 32-bits... it becomes possible that a context switch could occur before all of the bytes of a piece of data were updated.

          At 32-bits (and below), the CPU is unable to perform a context switch during the update so any 32-bit value will always contain valid data. This may not be true for strings, because they are more likely to be processed as an array of bytes, rather than a single numeric value. It is guaranteed to be untrue if the string is larger than 32-bits!

          For those lurking here, I'll try to explain the situation with threads and GLOBAL variables in a bit more detail (my explanation is simplistic, but should give an idea of the concepts involved)...

          If we look at the QUAD word variable X&&, and assign it the value &H00000000FFFFFFFF and then we add 1 to this variable.

          Sounds simple, but if a context switch occurs part way though this transaction, X&& may contain part only part of the new value it is to receive, ie, the high dword, making X&& appear to be &H00000001FFFFFFFF during the context change.

          When the context switch reverts back, the lower dword is finally updates and X&& contains the correct value &H0000000100000000.

          The problem can arise is another thread reads the value of X&& at the point it contained an "undefined" value.

          For more information, I'll again refer people to "Win32 Programming" by Rector/Newcomer.



          ------------------
          Lance
          PowerBASIC Support
          mailto:[email protected][email protected]</A>
          Lance
          mailto:[email protected]

          Comment


          • #6
            Lance is correct, but there is a tiny bit to add. There are rare occasions when 32-bit variables need synchronization.

            If we had a global g_lFlag as LONG and two threads check to see if its raised, raise it, and do something.
            IF g_lFlag = 0 Then g_lFlag = 1 : CALL DoSomeWork()

            This is not safe because the thread could switch out between the IF and the g_lFlag = 1 and the other thread may be doing the exact same thing. Now both threads are going to call DoSomeWork(). Without having to resort to a slow CriticalSection or Mutexes, we can use the lightweight Interlocked API.

            If InterlockedCompareExchange(g_lFlag, 1, 0) = 0 Then CALL DoSomeWork()


            ------------------

            Comment


            • #7
              Good point. The problem you faced is a general synchronization problem, and not specific to GLOBAL variables per-se.

              This is a good reason why the most common approach does not work:
              Code:
              FUNCTION MyThread(...)
                STATIC Busy&
                IF Busy& THEN EXIT FUNCTION
                Busy& = -1
                ...some meat and potatoes code goes here
                Busy& = 0
              END FUNCTION
              Again, "Win32 Programming" discusses these aspects, plus thread local storage, synchronization objects, etc.



              ------------------
              Lance
              PowerBASIC Support
              mailto:[email protected][email protected]</A>
              Lance
              mailto:[email protected]

              Comment


              • #8
                Lance --

                > the most common approach does not work:

                I did some experiments, and this seems to improve the reliability.

                Code:
                FUNCTION MyThread(...)
                  STATIC Busy&
                  SLEEP 1
                  IF Busy& THEN EXIT FUNCTION
                  Busy& = -1
                  ...some meat and potatoes code goes here
                  Busy& = 0
                END FUNCTION
                The SLEEP 1 guarantees that the next line (IF) will be executed at the very beginning of a time slice, so the possibility of a switch-away at the wrong moment is eliminated.

                Is this a valid technique if speed isn't a major issue?+

                -- Eric

                ------------------
                Perfect Sync: Perfect Sync Development Tools
                Email: mailto:[email protected][email protected]</A>

                "Not my circus, not my monkeys."

                Comment


                • #9
                  The problem as I see it is that there are no guarantees of how/when Windows will actually perform context switching, so I can't see how this could guarantee synchronization success. Especially as MS can change the way Windows multi-tasks under severe load conditions. Secondly, there is no way to specify a timeout so that deadlock can occur.

                  I personally would not trust this sort of technique if the project was a commercial-grade or industrial-strength - I'd rather use a "CriticalSection".

                  ------------------
                  Lance
                  PowerBASIC Support
                  mailto:[email protected][email protected]</A>
                  Lance
                  mailto:[email protected]

                  Comment


                  • #10
                    > I can't see how this could guarantee
                    > synchronization success

                    I thought that since SLEEP 0 means "give up the rest of my time slice" and larger values mean "give up the rest of my slice, and keep giving up slices until at least x milliseconds have elapsed", that the first line of code after a SLEEP would be guaranteed to execute at the very beginning of a slice.

                    I guess it wouldn't work on multiple-processor machines, though. Oh well, I thought it might be a reasonable alternative for quick-and-dirty apps, but probably not.

                    Thanks!

                    -- Eric


                    ------------------
                    Perfect Sync: Perfect Sync Development Tools
                    Email: mailto:[email protected]sup[email protected]</A>

                    "Not my circus, not my monkeys."

                    Comment


                    • #11
                      I think this approach should work:

                      Code:
                      FUNCTION MyThread(...)
                        STATIC Busy&
                      
                        INCR Busy&
                        WHILE Busy& > 1
                          SLEEP 1
                        WEND
                        'Do you stuff
                        '
                        DECR Busy&
                      END FUNCTION

                      Peter.


                      ------------------
                      [email protected]

                      Comment


                      • #12
                        Peter --

                        That can fail if this happens:

                        1) Thread 1 calls the function and does INCR Busy&, making it 1, but then Windows switches to thread 2 before the WHILE test can be performed for the first time.

                        2) Thread 2 calls the function and does INCR Busy&, making it 2.

                        Both threads will then wait forever. True, this is pretty unlikely, but it will happen eventually.

                        Lance is absolutely right, then only "right" way to deal with multithreading is to use the "synchronization objects" that the API provides, like Critical Sections and Mutexes.

                        Whatever you try to figure out, Windows will out-smart you and you'll end up using a synchronization object. The only thing you'll gain is a headache, trying to figure out a different way.

                        -- Eric


                        ------------------
                        Perfect Sync: Perfect Sync Development Tools
                        Email: mailto:[email protected][email protected]</A>



                        [This message has been edited by Eric Pearson (edited June 07, 2000).]
                        "Not my circus, not my monkeys."

                        Comment


                        • #13
                          Thanks Lance (and others) for the reply. That is indeed very clear.

                          I currently have a process (consisting of three threads synchronised by a number of critical sections) which creates and maintains a database of 12 000 files. These files are in turn accessed by a separate second single thread process.

                          The synchronisation of the reading and writing of the files between the two processes is performed via a system where one process locks the files for reading and/or writing and then the second process tests whether the files are locked or not before it accesses them. Also the archive attribute is manipulated(either set or unset) to indicate whether the file has recently been updated.

                          I am not sure whether this is the most efficient method for synchronising files between processes or whether something like a mutex would be a better way to go. I have never used mutexes but understand they are rather slow.

                          Any experiences or suggestions would be welcome.

                          Thanks,

                          Bern

                          ------------------
                          Bern


                          http://www.insighttrader.com.au

                          Comment


                          • #14
                            I've got a similar "problem" with threads and I'm thinking about a way to solve the syncronisation problem without a "cretical section".

                            This is Lance Edmond's code:
                            Code:
                            FUNCTION MyThread(...)  
                               STATIC Busy&
                            A: IF Busy& THEN EXIT FUNCTION
                            B: Busy& = -1  ...some meat and potatoes code goes here
                               Busy& = 0
                            END FUNCTION
                            As I can see, the ONLY problem is that execution might be interrupted between "A" and "B", so between the comparison and the value assingment
                            So why not swap these two lines?

                            Code:
                            FUNCTION MyThread(...)  
                                STATIC Busy&  
                            	
                                INCR Busy&
                                IF Busy& = 2 THEN EXIT FUNCTION  
                            
                                ...some meat and potatoes code goes here  	
                                DECR Busy&
                            
                            END FUNCTION
                            Could this be a possible solution?

                            Best Regards
                            Gregor

                            ------------------

                            Comment


                            • #15
                              Gregor --

                              That won't work either. The exact problem that I described above still applies. Like I said, no matter how hard you try... If it could be done with code, there would be no need for synchronization objects!

                              Critical sections are not difficult to use, and they are very fast and efficient. Just do this at the very beginning of your program (once for each Critical Section that you need to create.)

                              Code:
                              DIM gMyCritSect01 AS GLOBAL CRITICAL_SECTION 
                               
                              InitializeCriticalSection gMyCritSect01
                              Then do this in a FUNCTION or SUB that can be called by multiple threads...

                              Code:
                              EnterCriticalSection gMyCritSect01
                               
                              '"protected" meat and potatoes code goes here
                               
                              LeaveCriticalSection gMyCritSect01
                              And just before your program exits...

                              Code:
                              DeleteCriticalSection gMyCritSect01
                              There is also a TryCriticalSection which is useful too.

                              -- Eric


                              ------------------
                              Perfect Sync: Perfect Sync Development Tools
                              Email: mailto:[email protected][email protected]</A>

                              "Not my circus, not my monkeys."

                              Comment


                              • #16
                                oh thanks, I didn't know it's that easy

                                Regards
                                Gregor

                                ------------------

                                Comment


                                • #17
                                  One more tip... Make sure that your program does not EXIT FUNCTION or EXIT SUB out of the critical block of code, or skip the LeaveCriticalSection step in any other way. If you do, the function will be permanently locked out.

                                  The most reliable way is to use a "double function" like this:

                                  Code:
                                  'Your program would call this function...
                                  FUNCTION CallMe AS whatever
                                      EnterCriticalSection ...
                                      FUNCTION = ProtectedCode
                                      LeaveCriticalSection ...
                                  END FUNCTION
                                   
                                  'Never call this function "directly"...
                                  FUNCTION ProtectedCode AS whatever
                                      'Thai food (I'm tired of meat and potatoes)
                                  END FUNCTION
                                  You should use functions like that in "pairs", i.e. the CallMe function should not be used as a wrapper for multiple sub-functions unless you need to make sure that a group of functions are protected from simultaneous use in a "mutually exclusive" way. If that is the case, you can do something like this...

                                  Code:
                                  FUNCTION FunctionGroup(X AS LONG) AS whatever
                                      EnterCriticalSection ...
                                      FUNCTION = ProtectedGroup(X)
                                      LeaveCriticalSection ...
                                  END FUNCTION
                                   
                                  FUNCTION ProtectedGroupX AS LONG) AS whatever
                                      IF X = 1 THEN
                                         FUNCTION = BurgerAndFries
                                      ELSEIF X = 2 THEN
                                         FUNCTION = ChickenAndPasta
                                      ELSEIF X = 3 THEN
                                         FUNCTION = GyroAndRice
                                      END IF
                                  END FUNCTION
                                  That's all for now... I need to go find something to eat now...

                                  -- Eric
                                  ------------------
                                  Perfect Sync: Perfect Sync Development Tools
                                  Email: mailto:[email protected][email protected]</A>



                                  [This message has been edited by Eric Pearson (edited June 08, 2000).]
                                  "Not my circus, not my monkeys."

                                  Comment


                                  • #18
                                    thanks eric,

                                    Very helpful examples. But I'm hungry now *gg*

                                    Regards
                                    Gregor

                                    ------------------

                                    Comment


                                    • #19
                                      More useful info from the Win32.HLP file:

                                      Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process. Event, mutex, and semaphore objects can also be used in a single-process application, but critical section objects provide a slightly faster, more efficient mechanism for mutual-exclusion synchronization.

                                      So Critical Sections are your best bet if you are tring to synchronize the operation of the threads in a single program, but you'll need to use a "mutex" object if you need to synchronize the operation of two or more programs. A simple example of this would be when two programs need to use the same file or device, but they must not use it at the same time. A mutex could also be used to provide "instance control", to allow an application to detect that another instance of itself is already running.

                                      -- Eric



                                      ------------------
                                      Perfect Sync: Perfect Sync Development Tools
                                      Email: mailto:[email protected][email protected]</A>

                                      "Not my circus, not my monkeys."

                                      Comment

                                      Working...
                                      X