Announcement

Collapse
No announcement yet.

LOCAL String Memory

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

  • #21
    Originally posted by Mike Doty View Post
    See post #15
    Looking at it from the viewpoint of the compiled executable rather than the source code, forget about function and variable names, they just confuse the issue. They disappear at compile time It's probably clearer if you say:

    "A STATIC variable's storage is the same as for a GLOBAL; but the variable's address is available only within the procedure in which it is defined."

    When a named procedure is compiled it is allocated a starting address and any references to that procedure name in the entire module are replaced by that address. The address of the procedure never changes.

    When a named static variable is declared in a procedure, it is allocated a memory location and any subsequent use of that name in the procedure is replaced with that address. The address of that variable never changes, regardless of whether the procedure address is called consecutively in the main thread or concurrently in multiple threads. All references to that variable in that procedure are to the same memory location

    Comment


    • #22
      Don't forget about making static variable thread safe.
      A task-switch could occur anytime.
      Since we are discussing threads and static variables this is very important.
      Code:
      #INCLUDE "win32api.inc"
      GLOBAL gCS AS Critical_Section
      GLOBAL gs  AS STRING
      %MakeThreadSafe=0  '0=unpredictable
      
      FUNCTION PBMAIN () AS LONG
       LOCAL x, h1,h2 AS LONG
      InitializeCriticalSection gCS
       THREAD CREATE MyThread(1) TO h1:THREAD CLOSE h1 TO h1
       THREAD CREATE MyThread(2) TO h2:THREAD CLOSE h2 TO h2
       DO:SLEEP 50:LOOP UNTIL THREADCOUNT=1
       ? gs
      DeleteCriticalSection gcs
      END FUNCTION
      
      THREAD FUNCTION MyThread(BYVAL num AS LONG) AS LONG
       STATIC static_x AS LONG
       LOCAL i AS LONG
       'placement of Enter/LeaveCriticalSection affects results
       'execute 10 times before leaving critical section or leave critical section inside loop?
       FOR i = 1 TO 10
        SLEEP 10 'force task-switch while testing
        IF %MakeThreadSafe THEN EnterCriticalSection gcs
        INCR static_x
        gs+=USING$("# #",num,static_x) + $CR
        IF %MakeThreadSafe THEN LeaveCriticalSection gcs
       NEXT
      END FUNCTION
      
      ' could be used 
      THREAD FUNCTION MyThread2(BYVAL num AS LONG) AS LONG
       STATIC x AS LONG
       LOCAL i AS LONG
       FOR i = 1 TO 10
        INCR x
        gs+=USING$("# #",num,x) + $CR  'this is not thread safe
       NEXT
      END FUNCTION
      How long is an idea? Write it down.

      Comment


      • #23
        Originally posted by Mike Doty View Post
        Don't forget about making static variable thread safe.
        THREAD FUNCTION MyThread(BYVAL num AS LONG) AS LONG
        STATIC static_x AS LONG
        LOCAL i AS LONG

        FOR i = 1 TO 10
        SLEEP 10 'force task-switch
        IF %MakeThreadSafe THEN EnterCriticalSection gcs
        INCR static_x
        gs+=USING$("# #",num,static_x) + $CR
        IF %MakeThreadSafe THEN LeaveCriticalSection gcs
        NEXT
        END FUNCTION

        That depends on the exact meaning of thread safe.

        If you mean avoiding actual data corruption, you are correct. If you mean ensuring reproducable results, then not so.

        There is nothing to stop h2 from incrementing static_x while h1 is outside the EnterCriticalSection and LeaveCriticalSection but is still inside the For...Next loop and vice versa
        It is quite possible that you would get consecutive values of static_x in h1 of 1,2,3,5,6,7,9,10,11,13
        and h2 get values of 4,8,12,14,15,16,17,18,19,20 on one run and a different mix the next time the application is run

        i.e. on one run, gs may be:
        1 1
        1 2
        1 3
        2 4
        1 5
        1 6
        1 7
        2 8
        ....
        but the next run may be
        1 1
        1 2
        2 3
        1 4
        1 5
        2 6
        1 7
        ...

        Comment


        • #24
          No static_x will be skipped.
          That is why I mentioned placement of EnterCriticalSection is important.
          That was added while you were posting.
          I do not see an error.

          Code:
          THREAD FUNCTION MyThread(BYVAL num AS LONG) AS LONG
           STATIC static_x AS LONG
           LOCAL i AS LONG
           'placement of Enter/LeaveCriticalSection affects results
           'execute 10 times before leaving critical section or leave critical section inside loop?
           FOR i = 1 TO 10
            SLEEP 10 'force task-switch while testing
            IF %MakeThreadSafe THEN EnterCriticalSection gcs
            INCR static_x
            gs+=USING$("# #",num,static_x) + $CR
            IF %MakeThreadSafe THEN LeaveCriticalSection gcs
           NEXT
          END FUNCTION
          How long is an idea? Write it down.

          Comment


          • #25
            Persistent, STATIC, persistent and thread safe THREADED.
            Dale

            Comment


            • #26
              It just occurred to me last night ... what/how are you writing that this can be an issue?

              When you invoke a thread function ("THREAD CREATE") it will run until the thread is complete... meaning for the life of the thread object, all LOCAL variables retain their value and will be unique to the thread context. .

              About all you need to do is make sure if you are calling a user-defined function from your thread function using these LOCAL variables, you need to pass them as parameters
              AND
              either
              Define the support function with the THREADSAFE attribute
              *OR*
              Use a synchronization object.

              I did not delve into your code very deeply, but if you write as suggested you should not be having issues with data integrity.

              If your problem - or perhaps "challenge" would be a better word here ? - is getting values to the thread function when first invoked, I have provided several examples of using a UDT as the thread parameter in the THREAD CREATE statement, allowing multiple values to be passed. . Do a search on keyword THREAD, forum source code, author Michael Mattias, check "threads started by this member" and you will find what you need quite quickly
              Michael Mattias
              Tal Systems Inc. (retired)
              Racine WI USA
              [email protected]
              http://www.talsystems.com

              Comment


              • #27
                Again, I'm not trying to solve a problem, I'm trying to understand a behavior (post #19). What I believe is that memory for STATIC vars is shared between multiple threads that are started with THREAD CREATE or CreateThread and point to the same THREAD FUNCTION (a code pointer is used with CreateThread). Memory for STATIC vars used in the MAIN function and other procedures (SUB/FUNCTION) does not seem to be shared - which would seem to make it persistent and thread safe.

                It's important for me to have vars in procedures with a persistent value that are thread safe but not locked to a thread (e.g. support routines that can be called my any thread).

                Anyway, here's some more code that I've been playing with. It launches two groups of threads that call a different THREAD FUNCTION. The code in the threads is identical. I use the latest version of PBCC as it makes it easy to slap together code for testing. Note that the threads will run until manually stopped (clunky use of event). There are comments in the code that explain what I'm trying to do. Run it and see what you think.

                Code:
                #COMPILE EXE
                #DIM ALL
                
                #INCLUDE "win32api.inc"  ' Need this for event stuff
                
                GLOBAL ghStop AS LONG  ' Please no GLOBAL bashing - this is quick-n-dirty
                
                '-- This routine provides thread-safe screen logging and supports all threads. Note that
                ' it uses a STATIC var as a flag for an initial display. The var appears to be persistent
                ' and thread safe.
                SUB tPrint(BYVAL tNum AS LONG, BYREF sMsg AS STRING, BYREF dVal AS LONG) THREADSAFE
                  STATIC n AS LONG
                  STATIC init AS LONG
                  IF init <> 100000 THEN
                    PRINT "tNum  " "tVal", "tPtr", "ThreadID", "nVal", "nPtr", "GetNum"
                    PRINT "----  " "----", "----", "--------", "----", "----", "------"
                    init = 100000
                  END IF
                  INCR n
                  PRINT FORMAT$(tNum, " 000  ")  dVal, THREADID, VARPTR(dVal), n, VARPTR(n), GetNumber
                END SUB
                
                '-- This routine simply increments a STATIC var and returns it's value to calling routines in
                '   multiple threads. The var does not seem to be shared with other threads/routines (thread
                '   safe).
                FUNCTION GetNumber() THREADSAFE AS LONG
                  STATIC n AS LONG
                  IF ISFALSE(n) THEN n = 100 ' Seed local STATIC var with
                  INCR n
                  FUNCTION = n
                END FUNCTION
                
                THREAD FUNCTION myThread1 (BYVAL tNum AS LONG) AS LONG
                  STATIC n AS LONG                ' STATIC var
                  n = tNum                        ' Overwrite any STATIC var values with our start value (tNum)
                  tPrint tNum, "n =", n           ' Display initial var value (threads start sequentially)
                  WaitForSingleObject ghStop, -1  ' Wait to be signaled to continue
                  ResetEvent ghStop               ' Reset event so we can be signaled again later
                  SLEEP tNum * 20                 ' Pause for threads to run in numerical sequence
                  tPrint tNum, "n =", n           ' Display final value (value from last thread called)
                  WaitForSingleObject(ghStop, -1) ' Wait to be signaled to stop thread
                END FUNCTION
                
                THREAD FUNCTION myThread2 (BYVAL tNum AS LONG) AS LONG
                  STATIC n AS LONG                ' STATIC var
                  n = tNum                        ' Overwrite any STATIC var values with our start value (tNum)
                  tPrint tNum, "n =", n           ' Display initial var value (threads start sequentially)
                  WaitForSingleObject ghStop, -1  ' Wait to be signaled to continue
                  ResetEvent ghStop               ' Reset event so we can be signaled again later
                  SLEEP tNum * 20                 ' Pause for threads to run in numerical sequence
                  tPrint tNum, "n =", n           ' Display final value (value from last thread called)
                  WaitForSingleObject(ghStop, -1) ' Wait to be signaled to stop thread
                END FUNCTION
                
                FUNCTION PBMAIN () AS LONG
                  STATIC n AS LONG
                  LOCAL i AS LONG
                  LOCAL ret AS LONG
                  LOCAL tid AS LONG
                
                  '-- Event to signal threads.
                  ghStop = CreateEvent("", 1, 0, "" )
                
                  '-- Set value for our local STATIC var. tNum = 0 for logging. The var value is unique for
                  '   logging.
                  n = 201
                  tPrint 0, "n =", n
                
                  '-- Start threads in numerical sequence (short pauses to allow prev threads to start).
                  FOR i = 1 TO 5
                    THREAD CREATE myThread1(i) TO ret
                    SLEEP 5
                  NEXT i
                  SLEEP 5
                  FOR i = 11 TO 15
                    THREAD CREATE myThread2(i) TO ret
                '    THREAD CREATE myThread1(i) TO ret
                    SLEEP 5
                  NEXT i
                
                  '-- Control thread steps with key presses. Clunky but works.
                  WAITKEY$
                  SetEvent ghStop   ' Signal threads to continue
                  WAITKEY$
                  SetEvent ghStop   ' Signal threads to stop
                  PRINT "Finished"
                  WAITKEY$
                END FUNCTION

                Comment


                • #28
                  I just used the group calling myThread1 to test the event and combined some code as helper functions.
                  Might close thread handles and wait for threads to finish before ending.

                  Could get to hang by taking out all WAITKEY$'s (race condition) which could occur in a production program.
                  Code:
                  #DIM ALL
                  #INCLUDE "win32api.inc"
                  GLOBAL ghStop AS LONG
                  '--------------------------------------------------------------------------------
                  THREAD FUNCTION myThread1 (BYVAL tNum AS LONG) AS LONG
                    Restart(tNum)
                    Restart(tNum )
                    p "Thread" + STR$(tNum) + " has finished"
                  END FUNCTION
                  '--------------------------------------------------------------------------------
                  SUB P(s AS STRING) THREADSAFE
                   ? s
                  END SUB
                  '--------------------------------------------------------------------------------
                  FUNCTION PBMAIN () AS LONG
                    LOCAL i,ret AS LONG
                    InitEvent
                    FOR i = 1 TO 3
                     THREAD CREATE myThread1(i) TO ret
                     SLEEP 5
                    NEXT i
                    NextStep
                    NextStep
                    P USING$("--- Threadcount # ---",THREADCOUNT)
                    P "Waiting for threads to finish"
                    DO:LOOP UNTIL THREADCOUNT = 1
                    P "Done":SLEEP 2000
                  END FUNCTION
                  '--------------------------------------------------------------------------------
                  SUB InitEvent
                   ghStop = CreateEvent("", 1, 0, "" ) '-- Event to signal threads.
                  END SUB
                  '--------------------------------------------------------------------------------
                  SUB NextStep
                   WAITKEY$
                   SetEvent ghStop
                  END SUB
                  '--------------------------------------------------------------------------------
                  SUB Restart(tNum AS LONG)
                   P USING$("StartTop #",tNum)
                   WaitForSingleObject ghStop, -1  ' Wait to be signaled to continue
                   ResetEvent ghStop               ' Reset event so we can be signaled again later
                  END SUB
                  '--------------------------------------------------------------------------------
                  #IF 0
                  SUB tPrint(BYVAL tNum AS LONG, BYREF sMsg AS STRING, BYREF dVal AS LONG) THREADSAFE
                    PRINT FORMAT$(tNum, " 000  ")  dVal, THREADID
                  END SUB
                  #ENDIF
                  How long is an idea? Write it down.

                  Comment

                  Working...
                  X